├── .gitignore ├── LICENSE ├── README.md ├── 第10章 怎么出试卷?——模版方法模式.ipynb ├── 第11章 迪米特法则——最少知识原则.ipynb ├── 第12章 基金理财更省事——外观模式.ipynb ├── 第13章 造小人——建造者模式.ipynb ├── 第14章 老板来了——观察者模式.ipynb ├── 第15章 如何兼容各种DB——抽象工厂模式.ipynb ├── 第16章 上班,干活,下班,加班——状态模式.ipynb ├── 第17章 程序中的翻译官——适配器模式.ipynb ├── 第18章 游戏角色备份——备忘录模式.ipynb ├── 第19章 分公司=部门?——组合模式.ipynb ├── 第1章 计算器——简单工厂模式.ipynb ├── 第20章 挨个买票——迭代器模式.ipynb ├── 第21章 计划生育——单例模式.ipynb ├── 第22章 手机型号&软件版本——桥接模式.ipynb ├── 第23章 烤串的哲学——命令模式.ipynb ├── 第24章 加薪审批——职责链模式.ipynb ├── 第25章 联合国维护世界和平——中介者模式.ipynb ├── 第26章 千人千面,内在共享——享元模式.ipynb ├── 第27章 正则表达式——解释器模式.ipynb ├── 第28章 男人和女人——访问者模式.ipynb ├── 第2章 商场收银软件——策略模式.ipynb ├── 第3章 单一职责原则.ipynb ├── 第4章 开放-封闭原则.ipynb ├── 第5章 依赖倒转原则.ipynb ├── 第6章 衣服搭配系统——装饰模式.ipynb ├── 第7章 找人帮忙追美眉——代理模式.ipynb ├── 第8章 学习雷锋好榜样——工厂方法模式.ipynb └── 第9章 如何准备多份简历——原型模式.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python_design_patterns 2 | 设计模式Python版 3 | -------------------------------------------------------------------------------- /第10章 怎么出试卷?——模版方法模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 题目\n", 8 | "\n", 9 | "小时候数学老师的随堂测验,都是老师在黑板上写题目,学生在下边抄,然后再做题目。设计一个程序,模拟学生A和B抄题目做试卷的过程。\n", 10 | "\n", 11 | "## 基础版本" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 3, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "学生A抄的试卷以及答案\n", 24 | "题目1: !+1=?,a.2 b.3 c.4. d.1\n", 25 | "我选:a\n", 26 | "题目2: 2+1=?,a.2 b.3 c.4. d.1\n", 27 | "我选:b\n", 28 | "题目3: 2+2=?,a.2 b.3 c.4. d.1\n", 29 | "我选:c\n", 30 | "学生B抄的试卷以及答案\n", 31 | "题目1: !+1=?,a.2 b.3 c.4. d.1\n", 32 | "我选:a\n", 33 | "题目2: 2+1=?,a.2 b.3 c.4. d.1\n", 34 | "我选:c\n", 35 | "题目3: 2+2=?,a.2 b.3 c.4. d.1\n", 36 | "我选:d\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "\n", 42 | "class TestPaperA():\n", 43 | "\n", 44 | " def test_question_1(self):\n", 45 | " print(\"题目1: !+1=?,a.2 b.3 c.4. d.1\")\n", 46 | " print(\"我选:a\")\n", 47 | " \n", 48 | " def test_question_2(self):\n", 49 | " print(\"题目2: 2+1=?,a.2 b.3 c.4. d.1\")\n", 50 | " print(\"我选:b\")\n", 51 | " \n", 52 | " def test_question_3(self):\n", 53 | " print(\"题目3: 2+2=?,a.2 b.3 c.4. d.1\")\n", 54 | " print(\"我选:c\")\n", 55 | " \n", 56 | "class TestPaperB():\n", 57 | "\n", 58 | " def test_question_1(self):\n", 59 | " print(\"题目1: !+1=?,a.2 b.3 c.4. d.1\")\n", 60 | " print(\"我选:a\")\n", 61 | " \n", 62 | " def test_question_2(self):\n", 63 | " print(\"题目2: 2+1=?,a.2 b.3 c.4. d.1\")\n", 64 | " print(\"我选:c\")\n", 65 | " \n", 66 | " def test_question_3(self):\n", 67 | " print(\"题目3: 2+2=?,a.2 b.3 c.4. d.1\")\n", 68 | " print(\"我选:d\")\n", 69 | " \n", 70 | "def main():\n", 71 | " print(\"学生A抄的试卷以及答案\")\n", 72 | " paper_a = TestPaperA()\n", 73 | " paper_a.test_question_1()\n", 74 | " paper_a.test_question_2()\n", 75 | " paper_a.test_question_3()\n", 76 | " print(\"学生B抄的试卷以及答案\")\n", 77 | " paper_b = TestPaperB()\n", 78 | " paper_b.test_question_1()\n", 79 | " paper_b.test_question_2()\n", 80 | " paper_b.test_question_3()\n", 81 | " \n", 82 | "main()" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "### 点评\n", 90 | "- 学生A和学生B的考卷题目完全一样,重复代码太多\n", 91 | "- 如果老师修改题目,那所有学生都需要改试卷\n", 92 | "- 把试卷和答案分离,抽象一个试卷父类,然后学生A和学生B的试卷继承这个父类即可\n", 93 | "\n", 94 | "## 改进版本1.0——提炼父类" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 4, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "name": "stdout", 104 | "output_type": "stream", 105 | "text": [ 106 | "学生A抄的试卷以及答案\n", 107 | "题目1: !+1=?,a.2 b.3 c.4. d.1\n", 108 | "我选:a\n", 109 | "题目2: 2+1=?,a.2 b.3 c.4. d.1\n", 110 | "我选:b\n", 111 | "题目3: 2+2=?,a.2 b.3 c.4. d.1\n", 112 | "我选:c\n", 113 | "学生B抄的试卷以及答案\n", 114 | "题目1: !+1=?,a.2 b.3 c.4. d.1\n", 115 | "我选:a\n", 116 | "题目2: 2+1=?,a.2 b.3 c.4. d.1\n", 117 | "我选:c\n", 118 | "题目3: 2+2=?,a.2 b.3 c.4. d.1\n", 119 | "我选:d\n" 120 | ] 121 | } 122 | ], 123 | "source": [ 124 | "class TestPaper():\n", 125 | " \n", 126 | " def test_question_1(self):\n", 127 | " print(\"题目1: !+1=?,a.2 b.3 c.4. d.1\")\n", 128 | " \n", 129 | " def test_question_2(self):\n", 130 | " print(\"题目2: 2+1=?,a.2 b.3 c.4. d.1\")\n", 131 | " \n", 132 | " def test_question_3(self):\n", 133 | " print(\"题目3: 2+2=?,a.2 b.3 c.4. d.1\")\n", 134 | " \n", 135 | "class TestPaperA(TestPaper):\n", 136 | " \n", 137 | " def test_question_1(self):\n", 138 | " super().test_question_1()\n", 139 | " print(\"我选:a\")\n", 140 | " \n", 141 | " def test_question_2(self):\n", 142 | " super().test_question_2()\n", 143 | " print(\"我选:b\")\n", 144 | " \n", 145 | " def test_question_3(self):\n", 146 | " super().test_question_3()\n", 147 | " print(\"我选:c\")\n", 148 | " \n", 149 | " \n", 150 | "class TestPaperB(TestPaper):\n", 151 | " \n", 152 | " def test_question_1(self):\n", 153 | " super().test_question_1()\n", 154 | " print(\"我选:a\")\n", 155 | " \n", 156 | " def test_question_2(self):\n", 157 | " super().test_question_2()\n", 158 | " print(\"我选:c\")\n", 159 | " \n", 160 | " def test_question_3(self):\n", 161 | " super().test_question_3()\n", 162 | " print(\"我选:d\")\n", 163 | "\n", 164 | "main()" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "### 点评\n", 172 | "\n", 173 | "这还是只初步的泛化,两个类中还有类似的代码。比如都有`super().test_question_1()`,还有`print(\"我选:a\")`,除了选项不同,其他都相同。\n", 174 | "\n", 175 | "我们既然用了继承,并且认为这个继承是有意义的,那么父类就应该成为子类的模版,所有重复的代码都应该要上升到父类去,而不是让每个子类都去重复。\n", 176 | "\n", 177 | "这就需要使用模版方法来处理。\n", 178 | "\n", 179 | "当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模版方法来处理。\n", 180 | "\n", 181 | "## 改进版本2.0——提炼细节" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 6, 187 | "metadata": {}, 188 | "outputs": [ 189 | { 190 | "name": "stdout", 191 | "output_type": "stream", 192 | "text": [ 193 | "学生A抄的试卷以及答案\n", 194 | "题目1: !+1=?,a.2 b.3 c.4. d.1\n", 195 | "我选:a\n", 196 | "题目2: 2+1=?,a.2 b.3 c.4. d.1\n", 197 | "我选:b\n", 198 | "题目3: 2+2=?,a.2 b.3 c.4. d.1\n", 199 | "我选:c\n", 200 | "学生B抄的试卷以及答案\n", 201 | "题目1: !+1=?,a.2 b.3 c.4. d.1\n", 202 | "我选:a\n", 203 | "题目2: 2+1=?,a.2 b.3 c.4. d.1\n", 204 | "我选:c\n", 205 | "题目3: 2+2=?,a.2 b.3 c.4. d.1\n", 206 | "我选:d\n" 207 | ] 208 | } 209 | ], 210 | "source": [ 211 | "from abc import ABCMeta, abstractmethod\n", 212 | "\n", 213 | "\n", 214 | "class TestPaper():\n", 215 | " \n", 216 | " __metaclass__ = ABCMeta\n", 217 | " \n", 218 | " def test_question_1(self):\n", 219 | " print(\"题目1: !+1=?,a.2 b.3 c.4. d.1\")\n", 220 | " print(\"我选:{}\".format(self.answer_1()))\n", 221 | " \n", 222 | " @abstractmethod\n", 223 | " def answer_1(self):\n", 224 | " pass\n", 225 | " \n", 226 | " def test_question_2(self):\n", 227 | " print(\"题目2: 2+1=?,a.2 b.3 c.4. d.1\")\n", 228 | " print(\"我选:{}\".format(self.answer_2()))\n", 229 | " \n", 230 | " @abstractmethod\n", 231 | " def answer_2(self):\n", 232 | " pass\n", 233 | " \n", 234 | " def test_question_3(self):\n", 235 | " print(\"题目3: 2+2=?,a.2 b.3 c.4. d.1\")\n", 236 | " print(\"我选:{}\".format(self.answer_3()))\n", 237 | "\n", 238 | " @abstractmethod\n", 239 | " def answer_3(self):\n", 240 | " pass \n", 241 | " \n", 242 | "class TestPaperA(TestPaper):\n", 243 | " \n", 244 | " def answer_1(self):\n", 245 | " return \"a\"\n", 246 | " \n", 247 | " def answer_2(self):\n", 248 | " return \"b\"\n", 249 | " \n", 250 | " def answer_3(self):\n", 251 | " return \"c\"\n", 252 | " \n", 253 | " \n", 254 | "class TestPaperB(TestPaper):\n", 255 | " \n", 256 | " def answer_1(self):\n", 257 | " return \"a\"\n", 258 | " \n", 259 | " def answer_2(self):\n", 260 | " return \"c\"\n", 261 | " \n", 262 | " def answer_3(self):\n", 263 | " return \"d\"\n", 264 | "\n", 265 | "main()" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "### 点评\n", 273 | "此时要有更多的学生来答试卷,只是在试卷的模版上填写选择题的选项答案,即可。\n", 274 | "\n", 275 | "## 模版方法\n", 276 | "\n", 277 | "模版方法,定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤[DP]。" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 7, 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stdout", 287 | "output_type": "stream", 288 | "text": [ 289 | "具体类A的操作1\n", 290 | "具体类A的操作2\n", 291 | "具体类B的操作1\n", 292 | "具体类B的操作2\n" 293 | ] 294 | } 295 | ], 296 | "source": [ 297 | "from abc import ABCMeta, abstractmethod\n", 298 | "\n", 299 | "\n", 300 | "class AbstractClass():\n", 301 | " \"\"\"\n", 302 | " 抽象模版类,定义并实现了一个模版方法,这个模版方法一般是一个具体的算法,\n", 303 | " 它定义了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到\n", 304 | " 子类实现。当然,顶级逻辑也可能调用一些具体方法。\n", 305 | " \"\"\"\n", 306 | " __metaclass__ = ABCMeta\n", 307 | " \n", 308 | " @abstractmethod\n", 309 | " def primitive_operation_1(self):\n", 310 | " \"\"\"\n", 311 | " 抽象操作1,放到子类去实现\n", 312 | " \"\"\"\n", 313 | " pass\n", 314 | " \n", 315 | " @abstractmethod\n", 316 | " def primitive_operation_2(self):\n", 317 | " \"\"\"\n", 318 | " 抽象操作2,放到子类去实现\n", 319 | " \"\"\"\n", 320 | " pass\n", 321 | " \n", 322 | " def template_method(self):\n", 323 | " \"\"\"\n", 324 | " 具体模版方法,定义了顶级逻辑骨架\n", 325 | " \"\"\"\n", 326 | " self.primitive_operation_1()\n", 327 | " self.primitive_operation_2()\n", 328 | " \n", 329 | "\n", 330 | "class ConcreteClassA(AbstractClass):\n", 331 | " \"\"\"\n", 332 | " 具体类A,给出抽象方法的不同实现\n", 333 | " \"\"\"\n", 334 | " def primitive_operation_1(self):\n", 335 | " print(\"具体类A的操作1\")\n", 336 | " \n", 337 | " def primitive_operation_2(self):\n", 338 | " print(\"具体类A的操作2\")\n", 339 | " \n", 340 | " \n", 341 | "class ConcreteClassB(AbstractClass):\n", 342 | " \"\"\"\n", 343 | " 具体类B,给出抽象方法的不同实现\n", 344 | " \"\"\"\n", 345 | " def primitive_operation_1(self):\n", 346 | " print(\"具体类B的操作1\")\n", 347 | " \n", 348 | " def primitive_operation_2(self):\n", 349 | " print(\"具体类B的操作2\")\n", 350 | " \n", 351 | "cls = ConcreteClassA()\n", 352 | "cls.template_method()\n", 353 | "\n", 354 | "cls = ConcreteClassB()\n", 355 | "cls.template_method()" 356 | ] 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "metadata": {}, 361 | "source": [ 362 | "### 点评\n", 363 | "- 模版方法通过把不变的行为搬移到超类,去除子类中的重复代码来体现它的优势\n", 364 | "- 模版方法提供了一个很好的代码复用平台\n", 365 | "- 当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模版方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠" 366 | ] 367 | } 368 | ], 369 | "metadata": { 370 | "kernelspec": { 371 | "display_name": "Python 3", 372 | "language": "python", 373 | "name": "python3" 374 | }, 375 | "language_info": { 376 | "codemirror_mode": { 377 | "name": "ipython", 378 | "version": 3 379 | }, 380 | "file_extension": ".py", 381 | "mimetype": "text/x-python", 382 | "name": "python", 383 | "nbconvert_exporter": "python", 384 | "pygments_lexer": "ipython3", 385 | "version": "3.6.4" 386 | } 387 | }, 388 | "nbformat": 4, 389 | "nbformat_minor": 2 390 | } 391 | -------------------------------------------------------------------------------- /第11章 迪米特法则——最少知识原则.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 迪米特法则\n", 10 | "迪米特法则(LoD),也叫最少知识原则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一类的某一个方法的话,可以通过第三者转发这个调用。\n", 11 | "\n", 12 | "举个例子,在一个规模较大的研发团队中,产品经理想给软件加个新功能,那么产品经理应该向开发组长提出需求,由开发组长统筹安排开发资源,安排哪几个程序员去实现新功能,如果产品经理直接找到写代码的具体程序员,那就可能造成好说话的程序员背负很多需求,不好说话的程序员闲着没事,最终造成项目交付延期。\n", 13 | "\n", 14 | "迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行文就不要公开。\n", 15 | "\n", 16 | "迪米特法则其根本思想,是强调了类之间的松耦合。在设计程序的时候,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.6.4" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 2 48 | } 49 | -------------------------------------------------------------------------------- /第12章 基金理财更省事——外观模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目1\n", 10 | "用程序模拟股民直接炒股的代码,比如股民投资了股票1,股票2,股票3,国债1,房地产1." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 5, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "买入股票1\n", 23 | "买入股票2\n", 24 | "买入股票3\n", 25 | "买入国债1\n", 26 | "买入房地产1\n", 27 | "卖出股票1\n", 28 | "卖出股票2\n", 29 | "卖出股票3\n", 30 | "卖出国债1\n", 31 | "卖出房地产1\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "class Stock1():\n", 37 | " def sell(self):\n", 38 | " print(\"卖出股票1\")\n", 39 | " def buy(self):\n", 40 | " print(\"买入股票1\")\n", 41 | " \n", 42 | "class Stock2():\n", 43 | " def sell(self):\n", 44 | " print(\"卖出股票2\")\n", 45 | " def buy(self):\n", 46 | " print(\"买入股票2\")\n", 47 | " \n", 48 | "class Stock3():\n", 49 | " def sell(self):\n", 50 | " print(\"卖出股票3\")\n", 51 | " def buy(self):\n", 52 | " print(\"买入股票3\")\n", 53 | " \n", 54 | "class NationalDebt1():\n", 55 | " def sell(self):\n", 56 | " print(\"卖出国债1\")\n", 57 | " def buy(self):\n", 58 | " print(\"买入国债1\")\n", 59 | " \n", 60 | "class Realty1():\n", 61 | " def sell(self):\n", 62 | " print(\"卖出房地产1\")\n", 63 | " def buy(self):\n", 64 | " print(\"买入房地产1\")\n", 65 | " \n", 66 | "def user_action():\n", 67 | " \"\"\"\n", 68 | " 模拟股民的操作,股民需要了解各个理财产品的走势,进而买入卖出\n", 69 | " \"\"\"\n", 70 | " gu1 = Stock1()\n", 71 | " gu2 = Stock2()\n", 72 | " gu3 = Stock3()\n", 73 | " nd1 = NationalDebt1()\n", 74 | " rt1 = Realty1()\n", 75 | " \n", 76 | " gu1.buy()\n", 77 | " gu2.buy()\n", 78 | " gu3.buy()\n", 79 | " nd1.buy()\n", 80 | " rt1.buy()\n", 81 | " \n", 82 | " gu1.sell()\n", 83 | " gu2.sell()\n", 84 | " gu3.sell()\n", 85 | " nd1.sell()\n", 86 | " rt1.sell()\n", 87 | " \n", 88 | "user_action()" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "## 题目2\n", 96 | "用程序模拟股民通过基金理财的代码,股民只需要购买&卖出基金即可,不需要对具体的股票等有了解." 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 6, 102 | "metadata": {}, 103 | "outputs": [ 104 | { 105 | "name": "stdout", 106 | "output_type": "stream", 107 | "text": [ 108 | "买入股票1\n", 109 | "买入股票2\n", 110 | "买入股票3\n", 111 | "买入国债1\n", 112 | "买入房地产1\n", 113 | "卖出股票1\n", 114 | "卖出股票2\n", 115 | "卖出股票3\n", 116 | "卖出国债1\n", 117 | "卖出房地产1\n" 118 | ] 119 | } 120 | ], 121 | "source": [ 122 | "class Fund():\n", 123 | " def __init__(self):\n", 124 | " self.gu1 = Stock1()\n", 125 | " self.gu2 = Stock2()\n", 126 | " self.gu3 = Stock3()\n", 127 | " self.nd1 = NationalDebt1()\n", 128 | " self.rt1 = Realty1()\n", 129 | " \n", 130 | " def sell(self):\n", 131 | " self.gu1.sell()\n", 132 | " self.gu2.sell()\n", 133 | " self.gu3.sell()\n", 134 | " self.nd1.sell()\n", 135 | " self.rt1.sell()\n", 136 | " \n", 137 | " def buy(self):\n", 138 | " self.gu1.buy()\n", 139 | " self.gu2.buy()\n", 140 | " self.gu3.buy()\n", 141 | " self.nd1.buy()\n", 142 | " self.rt1.buy()\n", 143 | " \n", 144 | "def user_action():\n", 145 | " \"\"\"\n", 146 | " 模拟股民的操作,股民只需要买进卖出基金即可\n", 147 | " \"\"\"\n", 148 | " fund = Fund()\n", 149 | " \n", 150 | " fund.buy()\n", 151 | " \n", 152 | " fund.sell()\n", 153 | " \n", 154 | "user_action()" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "## 外观模式\n", 162 | "\n", 163 | "外观模式,为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用[DP]。\n", 164 | "\n", 165 | "### 多个子系统类" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 7, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [ 174 | "class SubSystemOne():\n", 175 | " def method_one(self):\n", 176 | " print(\"子系统方法1\")\n", 177 | " \n", 178 | "class SubSystemTwo():\n", 179 | " def method_two(self):\n", 180 | " print(\"子系统方法2\")\n", 181 | " \n", 182 | "class SubSystemThree():\n", 183 | " def method_three(self):\n", 184 | " print(\"子系统方法3\")" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "### 外观类" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 9, 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "name": "stdout", 201 | "output_type": "stream", 202 | "text": [ 203 | "子系统方法1\n", 204 | "子系统方法2\n", 205 | "子系统方法2\n", 206 | "子系统方法3\n" 207 | ] 208 | } 209 | ], 210 | "source": [ 211 | "class Facade():\n", 212 | " def __init__(self):\n", 213 | " self.one = SubSystemOne()\n", 214 | " self.two = SubSystemTwo()\n", 215 | " self.three = SubSystemThree()\n", 216 | " \n", 217 | " def method_a(self):\n", 218 | " self.one.method_one()\n", 219 | " self.two.method_two()\n", 220 | " \n", 221 | " def method_b(self):\n", 222 | " self.two.method_two()\n", 223 | " self.three.method_three()\n", 224 | "\n", 225 | "def main():\n", 226 | " facade = Facade()\n", 227 | " \n", 228 | " facade.method_a()\n", 229 | " \n", 230 | " facade.method_b()\n", 231 | " \n", 232 | "main()" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "### 点评\n", 240 | "外观模式完美的体现了依赖倒转原则和迪米特法则的思想,是非常常用的模式之一。\n", 241 | "\n", 242 | "那么什么时候使用外观模式最好?\n", 243 | "\n", 244 | "- 在设计阶段,应该有意识的将不同的两个层分离,比如经典的三层架构,需要考虑在数据访问层和业务逻辑层,业务逻辑层和表示层的层与层之间简历外观模式,这样可以为复杂的子系统提供一个简单的接口,使得耦合度降低;\n", 245 | "- 在开发阶段,子系统往往因为不断重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但是也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖;\n", 246 | "- 在维护阶段,可能一个大型的系统已经非常难以维护和扩展,但是因为它包含了重要功能,新的开发需求必须依赖它,此时也可以使用外观模式。可以为新系统开发一个Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统和Facade对象交互。" 247 | ] 248 | } 249 | ], 250 | "metadata": { 251 | "kernelspec": { 252 | "display_name": "Python 3", 253 | "language": "python", 254 | "name": "python3" 255 | }, 256 | "language_info": { 257 | "codemirror_mode": { 258 | "name": "ipython", 259 | "version": 3 260 | }, 261 | "file_extension": ".py", 262 | "mimetype": "text/x-python", 263 | "name": "python", 264 | "nbconvert_exporter": "python", 265 | "pygments_lexer": "ipython3", 266 | "version": "3.6.4" 267 | } 268 | }, 269 | "nbformat": 4, 270 | "nbformat_minor": 2 271 | } 272 | -------------------------------------------------------------------------------- /第13章 造小人——建造者模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目1\n", 10 | "用程序模拟一个画小人的过程,要求小人要有头,身子,左手,右手,左脚,右脚。\n", 11 | "\n", 12 | "## 基础代码" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [ 20 | { 21 | "name": "stdout", 22 | "output_type": "stream", 23 | "text": [ 24 | "画头\n", 25 | "画身子\n", 26 | "画左手\n", 27 | "画右手\n", 28 | "画左脚\n", 29 | "画右脚\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "class PersonDrawer():\n", 35 | " \n", 36 | " def draw(self):\n", 37 | " print(\"画头\")\n", 38 | " print(\"画身子\")\n", 39 | " print(\"画左手\")\n", 40 | " print(\"画右手\")\n", 41 | " print(\"画左脚\")\n", 42 | " print(\"画右脚\")\n", 43 | " \n", 44 | "def main():\n", 45 | " drawer = PersonDrawer()\n", 46 | " drawer.draw()\n", 47 | " \n", 48 | "main()" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "## 题目2\n", 56 | "再画一个身子胖一些的小人。" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 3, 62 | "metadata": {}, 63 | "outputs": [ 64 | { 65 | "name": "stdout", 66 | "output_type": "stream", 67 | "text": [ 68 | "画头\n", 69 | "画身子(胖一些)\n", 70 | "画左手\n", 71 | "画右手\n", 72 | "画右脚\n" 73 | ] 74 | } 75 | ], 76 | "source": [ 77 | "class FatPersonDrawer():\n", 78 | " \n", 79 | " def draw(self):\n", 80 | " print(\"画头\")\n", 81 | " print(\"画身子(胖一些)\")\n", 82 | " print(\"画左手\")\n", 83 | " print(\"画右手\")\n", 84 | " print(\"画右脚\")\n", 85 | " \n", 86 | "def main():\n", 87 | " drawer = FatPersonDrawer()\n", 88 | " drawer.draw()\n", 89 | " \n", 90 | "main()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### 点评\n", 98 | "画胖一些的小人时候,稍不注意,漏掉了左脚。画小人的步骤很繁琐,稍不注意就会漏掉一步,导致小人缺胳膊少腿。最好的办法就是规定,建造小人的时候,必须要有头,身子,左手,右手,左脚,右脚。这时候就可以使用建造这模式。\n", 99 | "\n", 100 | "## 建造者模式\n", 101 | "\n", 102 | "建造小人的`过程`是稳定的,都需要头身手脚,但是具体建造的`细节`是不同的,高矮胖瘦都有。\n", 103 | "\n", 104 | "为了将一个复杂的对象的`构建`与它的`表示`分离,使得`同样的构建`过程可以创建`不同的表示`,就可以使用建造者模式,又叫“生成器模式”。\n", 105 | "\n", 106 | "实现的方法就是定义抽象的建造者类,通过抽象函数的方法把构建的过程固定下来,这样就每一步也不能少,不会出现缺胳膊少腿的情况了。" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 4, 112 | "metadata": {}, 113 | "outputs": [ 114 | { 115 | "name": "stdout", 116 | "output_type": "stream", 117 | "text": [ 118 | "画头——瘦小人\n", 119 | "画身子——瘦小人\n", 120 | "画左手——瘦小人\n", 121 | "画右手——瘦小人\n", 122 | "画左腿——瘦小人\n", 123 | "画头——胖小人\n", 124 | "画身子——胖小人\n", 125 | "画左手——胖小人\n", 126 | "画右手——胖小人\n", 127 | "画左腿——胖小人\n" 128 | ] 129 | } 130 | ], 131 | "source": [ 132 | "from abc import ABCMeta, abstractmethod\n", 133 | "\n", 134 | "\n", 135 | "class PersonBuilder():\n", 136 | " \"\"\"\n", 137 | " 抽象建造者,定义创建对象各个部件的接口\n", 138 | " \"\"\"\n", 139 | " __metaclass__ = ABCMeta\n", 140 | " \n", 141 | " @abstractmethod\n", 142 | " def draw_head(self):\n", 143 | " pass\n", 144 | " \n", 145 | " @abstractmethod\n", 146 | " def draw_body(self):\n", 147 | " pass\n", 148 | " \n", 149 | " @abstractmethod\n", 150 | " def draw_left_arm(self):\n", 151 | " pass\n", 152 | " \n", 153 | " @abstractmethod\n", 154 | " def draw_right_arm(self):\n", 155 | " pass\n", 156 | " \n", 157 | " @abstractmethod\n", 158 | " def draw_left_leg(self):\n", 159 | " pass\n", 160 | " \n", 161 | " @abstractmethod\n", 162 | " def draw_right_leg(self):\n", 163 | " pass\n", 164 | "\n", 165 | " \n", 166 | "class ThinPersonBuilder(PersonBuilder):\n", 167 | " \"\"\"\n", 168 | " 具体建造者,实现创建各个部件的接口\n", 169 | " \"\"\"\n", 170 | " def draw_head(self):\n", 171 | " print(\"画头——瘦小人\")\n", 172 | " \n", 173 | " def draw_body(self):\n", 174 | " print(\"画身子——瘦小人\")\n", 175 | " \n", 176 | " def draw_left_arm(self):\n", 177 | " print(\"画左手——瘦小人\")\n", 178 | " \n", 179 | " def draw_right_arm(self):\n", 180 | " print(\"画右手——瘦小人\")\n", 181 | " \n", 182 | " def draw_left_leg(self):\n", 183 | " print(\"画左腿——瘦小人\")\n", 184 | " \n", 185 | " def draw_rihgt_leg(self):\n", 186 | " print(\"画右腿——瘦小人\")\n", 187 | " \n", 188 | "\n", 189 | "class FatPersonBuilder(PersonBuilder):\n", 190 | " \"\"\"\n", 191 | " 具体建造者,实现创建各个部件的接口\n", 192 | " \"\"\"\n", 193 | " def draw_head(self):\n", 194 | " print(\"画头——胖小人\")\n", 195 | " \n", 196 | " def draw_body(self):\n", 197 | " print(\"画身子——胖小人\")\n", 198 | " \n", 199 | " def draw_left_arm(self):\n", 200 | " print(\"画左手——胖小人\")\n", 201 | " \n", 202 | " def draw_right_arm(self):\n", 203 | " print(\"画右手——胖小人\")\n", 204 | " \n", 205 | " def draw_left_leg(self):\n", 206 | " print(\"画左腿——胖小人\")\n", 207 | " \n", 208 | " def draw_rihgt_leg(self):\n", 209 | " print(\"画右腿——胖小人\")\n", 210 | " \n", 211 | " \n", 212 | "class PersonDirector():\n", 213 | " \"\"\"\n", 214 | " 指挥者,是构建一个使用Builder接口的对象\n", 215 | " \"\"\"\n", 216 | " def __init__(self, person_builder):\n", 217 | " self.person_builder = person_builder\n", 218 | " \n", 219 | " def create_person(self):\n", 220 | " self.person_builder.draw_head()\n", 221 | " self.person_builder.draw_body()\n", 222 | " self.person_builder.draw_left_arm()\n", 223 | " self.person_builder.draw_right_arm()\n", 224 | " self.person_builder.draw_left_leg()\n", 225 | " self.person_builder.draw_right_leg()\n", 226 | " \n", 227 | "def main():\n", 228 | " \n", 229 | " thin_person_builder = ThinPersonBuilder()\n", 230 | " thin_person_director = PersonDirector(thin_person_builder)\n", 231 | " thin_person_director.create_person()\n", 232 | " \n", 233 | " fat_person_builder = FatPersonBuilder()\n", 234 | " fat_person_director = PersonDirector(fat_person_builder)\n", 235 | " fat_person_director.create_person()\n", 236 | " \n", 237 | "main()" 238 | ] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "### 点评\n", 245 | "建造者模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以需要改变一个产品的内部表示,只需要再定义一个具体的建造者即可。\n", 246 | "\n", 247 | "建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。" 248 | ] 249 | } 250 | ], 251 | "metadata": { 252 | "kernelspec": { 253 | "display_name": "Python 3", 254 | "language": "python", 255 | "name": "python3" 256 | }, 257 | "language_info": { 258 | "codemirror_mode": { 259 | "name": "ipython", 260 | "version": 3 261 | }, 262 | "file_extension": ".py", 263 | "mimetype": "text/x-python", 264 | "name": "python", 265 | "nbconvert_exporter": "python", 266 | "pygments_lexer": "ipython3", 267 | "version": "3.6.4" 268 | } 269 | }, 270 | "nbformat": 4, 271 | "nbformat_minor": 2 272 | } 273 | -------------------------------------------------------------------------------- /第14章 老板来了——观察者模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目\n", 10 | "\n", 11 | "用程序模拟以下情景,在一个办公室里,当老板进门的时候,前台秘书就偷偷通知办公室里的同事:“老板来了”,办公室里的同事就会停止观看股票,继续工作。\n", 12 | "\n", 13 | "## 基础版本" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "老板来了! 贺贺关闭股票行情, 继续工作.\n", 26 | "老板来了! 曼曼关闭股票行情, 继续工作.\n" 27 | ] 28 | } 29 | ], 30 | "source": [ 31 | "class Secretary():\n", 32 | " \"\"\"\n", 33 | " 通知者\n", 34 | " \"\"\"\n", 35 | " def __init__(self):\n", 36 | " self.observers = []\n", 37 | " self.action = None\n", 38 | " \n", 39 | " def attach(self, observer):\n", 40 | " self.observers.append(observer)\n", 41 | " \n", 42 | " def notify(self):\n", 43 | " for observer in self.observers:\n", 44 | " observer.update()\n", 45 | " \n", 46 | "class Observers():\n", 47 | " \"\"\"\n", 48 | " 观察者\n", 49 | " \"\"\"\n", 50 | " def __init__(self, name, informer):\n", 51 | " self.name = name\n", 52 | " self.informer = informer\n", 53 | " \n", 54 | " def update(self):\n", 55 | " print(\"{} {}关闭股票行情, 继续工作.\".format(self.informer.action, self.name))\n", 56 | " \n", 57 | "def main():\n", 58 | " informer = Secretary()\n", 59 | " \n", 60 | " hh = Observers(\"贺贺\", informer)\n", 61 | " mm = Observers(\"曼曼\", informer)\n", 62 | " \n", 63 | " informer.attach(hh)\n", 64 | " informer.attach(mm)\n", 65 | " \n", 66 | " informer.action = \"老板来了!\"\n", 67 | " \n", 68 | " informer.notify()\n", 69 | " \n", 70 | "main()" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "### 点评\n", 78 | "- 上述代码基本能够表示设定的场景,但是有一个问题,“通知者”和“观察者”这两个类互相耦合,即双向耦合;\n", 79 | "- 假设另一类“观察者”不看股票,而是在看NBA比赛,那么这类“观察者”对应的update操作就不应该是“关闭股票行情”,而是“关闭NBA直播”;\n", 80 | "- 此外,除了前台秘书可以作为“通知者”,老板其实也是“通知者”,当老板来了,各个“观察者”也应该及时回到工作状态;\n", 81 | "- 综上,需要对“观察者”和“通知者”进行进一步抽象\n", 82 | "\n", 83 | "## 改进版本——双向解耦" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 8, 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "name": "stdout", 93 | "output_type": "stream", 94 | "text": [ 95 | "聘用:贺贺\n", 96 | "聘用:曼曼\n", 97 | "--老板发出强大的气场--\n", 98 | "我回来了! 贺贺关闭股票行情, 继续工作.\n", 99 | "我回来了! 曼曼关闭NBA直播, 继续工作.\n", 100 | "关系和谐:鸣人\n", 101 | "关系和谐:佐助\n", 102 | "关系和谐:小樱\n", 103 | "产生矛盾:小樱\n", 104 | "--秘书发送即时消息--\n", 105 | "老板回来了! 鸣人关闭股票行情, 继续工作.\n", 106 | "老板回来了! 佐助关闭NBA直播, 继续工作.\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "from abc import ABCMeta,abstractmethod\n", 112 | "\n", 113 | "\n", 114 | "class Informer():\n", 115 | " \"\"\"\n", 116 | " 抽象通知者\n", 117 | " \"\"\"\n", 118 | " __metaclass__ = ABCMeta\n", 119 | " \n", 120 | " @abstractmethod\n", 121 | " def attach(self, observer):\n", 122 | " pass\n", 123 | " \n", 124 | " @abstractmethod\n", 125 | " def detach(self, observer):\n", 126 | " pass\n", 127 | " \n", 128 | " @abstractmethod\n", 129 | " def notify(self):\n", 130 | " pass\n", 131 | " \n", 132 | "class Boss(Informer):\n", 133 | " \"\"\"\n", 134 | " 具体通知者\n", 135 | " \"\"\"\n", 136 | " def __init__(self):\n", 137 | " self.observers = []\n", 138 | " self.action = None\n", 139 | " # 老板特有的初始化操作\n", 140 | " \n", 141 | " def attach(self, observer):\n", 142 | " print(\"聘用:{}\".format(observer.name))\n", 143 | " self.observers.append(observer)\n", 144 | " \n", 145 | " def detach(self, observer):\n", 146 | " print(\"解聘:{}\".format(observer.name))\n", 147 | " self.observers.remove(observer)\n", 148 | " \n", 149 | " def notify(self):\n", 150 | " print(\"--老板发出强大的气场--\")\n", 151 | " for o in self.observers:\n", 152 | " o.update()\n", 153 | " \n", 154 | "\n", 155 | "class Secretary(Informer):\n", 156 | " \"\"\"\n", 157 | " 具体通知者\n", 158 | " \"\"\"\n", 159 | " def __init__(self):\n", 160 | " self.observers = []\n", 161 | " self.action = None\n", 162 | " # 秘书特有的初始化操作\n", 163 | " \n", 164 | " def attach(self, observer):\n", 165 | " print(\"关系和谐:{}\".format(observer.name))\n", 166 | " self.observers.append(observer)\n", 167 | " \n", 168 | " def detach(self, observer):\n", 169 | " print(\"产生矛盾:{}\".format(observer.name))\n", 170 | " self.observers.remove(observer)\n", 171 | " \n", 172 | " def notify(self):\n", 173 | " print(\"--秘书发送即时消息--\")\n", 174 | " for o in self.observers:\n", 175 | " o.update() \n", 176 | "\n", 177 | "\n", 178 | "class Observer():\n", 179 | " \"\"\"\n", 180 | " 抽象观察者\n", 181 | " \"\"\"\n", 182 | " __mataclass__ = ABCMeta\n", 183 | " \n", 184 | " @abstractmethod\n", 185 | " def __init__(self, name, informer): # 之所以要有informer,是为了在观察者内部访问到informer的对象\n", 186 | " pass\n", 187 | " \n", 188 | " @abstractmethod\n", 189 | " def update(self):\n", 190 | " pass\n", 191 | " \n", 192 | "\n", 193 | "class StockObserver(Observer):\n", 194 | " \"\"\"\n", 195 | " 具体观察者\n", 196 | " \"\"\"\n", 197 | " def __init__(self, name, informer):\n", 198 | " self.name = name\n", 199 | " self.informer = informer\n", 200 | " \n", 201 | " def update(self):\n", 202 | " print(\"{} {}关闭股票行情, 继续工作.\".format(self.informer.action, self.name))\n", 203 | " \n", 204 | " \n", 205 | "class NBAObserver(Observer):\n", 206 | " \"\"\"\n", 207 | " 具体观察者\n", 208 | " \"\"\"\n", 209 | " def __init__(self, name, informer):\n", 210 | " self.name = name\n", 211 | " self.informer = informer\n", 212 | " \n", 213 | " def update(self):\n", 214 | " print(\"{} {}关闭NBA直播, 继续工作.\".format(self.informer.action, self.name))\n", 215 | " \n", 216 | "\n", 217 | "def main():\n", 218 | " boss = Boss()\n", 219 | " \n", 220 | " hh = StockObserver(\"贺贺\", boss)\n", 221 | " mm = NBAObserver(\"曼曼\", boss)\n", 222 | "\n", 223 | " boss.attach(hh)\n", 224 | " boss.attach(mm)\n", 225 | " \n", 226 | " \n", 227 | " boss.action = \"我回来了!\"\n", 228 | " boss.notify()\n", 229 | " \n", 230 | " secretary = Secretary()\n", 231 | " \n", 232 | " mr = StockObserver(\"鸣人\", secretary)\n", 233 | " zz = NBAObserver(\"佐助\", secretary)\n", 234 | " xy = NBAObserver(\"小樱\", secretary)\n", 235 | "\n", 236 | " secretary.attach(mr)\n", 237 | " secretary.attach(zz)\n", 238 | " secretary.attach(xy)\n", 239 | "\n", 240 | " secretary.detach(xy)\n", 241 | " secretary.action = \"老板回来了!\"\n", 242 | " secretary.notify()\n", 243 | " \n", 244 | "main()\n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " " 249 | ] 250 | }, 251 | { 252 | "cell_type": "markdown", 253 | "metadata": {}, 254 | "source": [ 255 | "## 观察者模式(发布-订阅模式)\n", 256 | "\n", 257 | "观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。\n", 258 | "\n", 259 | "Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。\n", 260 | "\n", 261 | "Observer类,即抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update方法。\n", 262 | "\n", 263 | "ConcreteSubject类,叫做具体主题或者具体通知者,将有关状态存入具体观察者对象。在具体主题的内部状态改变时,给所有登记过的观察者发出通知。\n", 264 | "\n", 265 | "ConcreteObserver类,即具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如有需要,具体观察者角色可以保存一个指向具体主题对象的引用,以便获取具体主题对象的状态。\n", 266 | "\n", 267 | "### 观察者模式特点\n", 268 | "\n", 269 | "讲一个系统分割成一系列相互写作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护,扩展和重用都带来不便[DP]。而观察者的关键对象使主题Subject和观察者Observer,一个主题可以有任意树木的依赖它的Observer,一旦Subject状态发生变化,所有的Observer都可以得到通知。\n", 270 | "\n", 271 | "那么什么时候使用观察者模式呢?\n", 272 | "\n", 273 | "当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象有待改变时,需要考虑使用观察者模式。另外,当一个抽象模型有两个方面,其中一个方面依赖另一个方面,这时候使用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。\n", 274 | "\n", 275 | "总地来将,观察者模式所做的工作其实就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体实现。从而使得各自的变化不会影响另一边的变化。" 276 | ] 277 | } 278 | ], 279 | "metadata": { 280 | "kernelspec": { 281 | "display_name": "Python 3", 282 | "language": "python", 283 | "name": "python3" 284 | }, 285 | "language_info": { 286 | "codemirror_mode": { 287 | "name": "ipython", 288 | "version": 3 289 | }, 290 | "file_extension": ".py", 291 | "mimetype": "text/x-python", 292 | "name": "python", 293 | "nbconvert_exporter": "python", 294 | "pygments_lexer": "ipython3", 295 | "version": "3.6.4" 296 | } 297 | }, 298 | "nbformat": 4, 299 | "nbformat_minor": 2 300 | } 301 | -------------------------------------------------------------------------------- /第15章 如何兼容各种DB——抽象工厂模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目\n", 10 | "\n", 11 | "如何让一个程序,可以灵活替换数据库?\n", 12 | "\n", 13 | "## 基础版本" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "向SQL Server中添加一个User\n", 26 | "从SQL Server中搜索User 1\n" 27 | ] 28 | } 29 | ], 30 | "source": [ 31 | "class User():\n", 32 | " \"\"\"\n", 33 | " 用户类,模拟用户表,假设只有ID和name两个字段\n", 34 | " \"\"\"\n", 35 | " def __init__(self):\n", 36 | " self.id = None\n", 37 | " self.name = None\n", 38 | " \n", 39 | "\n", 40 | "class SqlServerUser():\n", 41 | " \"\"\"\n", 42 | " sqlserveruser类,用于操作User表\n", 43 | " \"\"\"\n", 44 | " def insert(self, user):\n", 45 | " print(\"向SQL Server中添加一个User\")\n", 46 | " \n", 47 | " def get_user(self, id):\n", 48 | " print(\"从SQL Server中搜索User\", id)\n", 49 | " \n", 50 | " \n", 51 | "def main():\n", 52 | " user = User()\n", 53 | " \n", 54 | " su = SqlServerUser()\n", 55 | " su.insert(user)\n", 56 | " su.get_user(1)\n", 57 | " \n", 58 | "main()\n", 59 | " " 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "### 点评\n", 67 | "这里之所以不能灵活更换数据库,是因为`su = SqlServerUser()` 将客户端和SQL Server绑定在一起,如果这里是“多态的”,那么就不需要考虑是SQL Server还是Access了。\n", 68 | "\n", 69 | "这里可以用“工厂方法模式”改进,工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。\n", 70 | "\n", 71 | "## 改进版本1.0——工厂方法模式" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 3, 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "name": "stdout", 81 | "output_type": "stream", 82 | "text": [ 83 | "在SQL Server中添加一个User\n", 84 | "从SQL Server中搜索User 1\n" 85 | ] 86 | } 87 | ], 88 | "source": [ 89 | "from abc import ABCMeta, abstractmethod\n", 90 | "\n", 91 | "class IUser():\n", 92 | " __metaclass__ = ABCMeta\n", 93 | " \n", 94 | " @abstractmethod\n", 95 | " def insert(self, user):\n", 96 | " pass\n", 97 | " \n", 98 | " @abstractmethod\n", 99 | " def get_user(self, id):\n", 100 | " pass\n", 101 | " \n", 102 | " \n", 103 | "class SqlServerUser(IUser):\n", 104 | " \n", 105 | " def insert(self, user):\n", 106 | " print(\"在SQL Server中添加一个User\")\n", 107 | " \n", 108 | " def get_user(self, id):\n", 109 | " print(\"从SQL Server中搜索User\", id)\n", 110 | " \n", 111 | " \n", 112 | "class AccessUser(IUser):\n", 113 | " \n", 114 | " def insert(self, user):\n", 115 | " print(\"在Access中添加一个User\")\n", 116 | " \n", 117 | " def get_user(self, id):\n", 118 | " print(\"从Access中搜索User\", id)\n", 119 | " \n", 120 | " \n", 121 | "class IFactory():\n", 122 | " __metaclass__ = ABCMeta\n", 123 | " \n", 124 | " @abstractmethod\n", 125 | " def create_user(self):\n", 126 | " pass\n", 127 | " \n", 128 | "\n", 129 | "class SqlServerFactory(IFactory):\n", 130 | " def create_user(self):\n", 131 | " return SqlServerUser()\n", 132 | " \n", 133 | " \n", 134 | "class AccessFactory(IFactory):\n", 135 | " def create_user(self):\n", 136 | " return AccessUser()\n", 137 | " \n", 138 | " \n", 139 | "def main():\n", 140 | " user = User()\n", 141 | " factory = SqlServerFactory()\n", 142 | " iuser = factory.create_user()\n", 143 | "\n", 144 | " iuser.insert(user)\n", 145 | " iuser.get_user(1)\n", 146 | " \n", 147 | "main()\n", 148 | " " 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "### 点评\n", 156 | "\n", 157 | "现在如果要更换数据库,只需要把`factory = SqlServerFactory()`更改成`factory = AccessFactory()`即可。这里由于多态的关系,使得声明IUser接口的对象iuser事先并不知道在访问哪个数据库,却可以在运行时很好的完成工作,这就是业务逻辑与数据访问解耦。\n", 158 | "\n", 159 | "但是,数据库中不可能只有一个User表,还可能有其他表,比如Department,那就需要增加好多个新的类。" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 4, 165 | "metadata": {}, 166 | "outputs": [ 167 | { 168 | "name": "stdout", 169 | "output_type": "stream", 170 | "text": [ 171 | "在SQL Server中添加一个User\n", 172 | "从SQL Server中搜索User 1\n", 173 | "在SQL Server中添加一个Department\n", 174 | "从SQL Server中搜索Department 1\n" 175 | ] 176 | } 177 | ], 178 | "source": [ 179 | "class Department():\n", 180 | " def __init__(self):\n", 181 | " self.id = None\n", 182 | " self.name = None\n", 183 | " \n", 184 | " \n", 185 | "class IDepartment():\n", 186 | " __metaclass__ = ABCMeta\n", 187 | " \n", 188 | " @abstractmethod\n", 189 | " def insert(self, department):\n", 190 | " pass\n", 191 | " \n", 192 | " @abstractmethod\n", 193 | " def get_department(self, id):\n", 194 | " pass\n", 195 | " \n", 196 | " \n", 197 | "class SqlServerDepartment(IDepartment):\n", 198 | " def insert(self, department):\n", 199 | " print(\"在SQL Server中添加一个Department\")\n", 200 | " \n", 201 | " def get_department(self, id):\n", 202 | " print(\"从SQL Server中搜索Department\", id)\n", 203 | " \n", 204 | "\n", 205 | "class AccessDepartment(IDepartment):\n", 206 | " def insert(self, department):\n", 207 | " print(\"在Access中添加一个Department\")\n", 208 | " \n", 209 | " def get_department(self, id):\n", 210 | " print(\"从Access中搜索Department\", id)\n", 211 | " \n", 212 | " \n", 213 | "class IFactory():\n", 214 | " __metaclass__ = ABCMeta\n", 215 | " \n", 216 | " @abstractmethod\n", 217 | " def create_user(self):\n", 218 | " pass\n", 219 | " \n", 220 | " @abstractmethod\n", 221 | " def create_department(self):\n", 222 | " pass\n", 223 | " \n", 224 | "\n", 225 | "class SqlServerFactory(IFactory):\n", 226 | " def create_user(self):\n", 227 | " return SqlServerUser()\n", 228 | " \n", 229 | " def create_department(self):\n", 230 | " return SqlServerDepartment()\n", 231 | " \n", 232 | " \n", 233 | "class AccessFactory(IFactory):\n", 234 | " def create_user(self):\n", 235 | " return AccessUser()\n", 236 | " \n", 237 | " def create_department(self):\n", 238 | " return AccessDepartment()\n", 239 | " \n", 240 | " \n", 241 | "def main():\n", 242 | " user = User()\n", 243 | " dept = Department()\n", 244 | " \n", 245 | " factory = SqlServerFactory()\n", 246 | " \n", 247 | " iuser = factory.create_user()\n", 248 | " iuser.insert(user)\n", 249 | " iuser.get_user(1)\n", 250 | " \n", 251 | " idept = factory.create_department()\n", 252 | " idept.insert(dept)\n", 253 | " idept.get_department(1)\n", 254 | " \n", 255 | "main()" 256 | ] 257 | }, 258 | { 259 | "cell_type": "markdown", 260 | "metadata": {}, 261 | "source": [ 262 | "### 点评\n", 263 | "\n", 264 | "这样就可以做到,只需要更改`factory = SqlServerFactory()`,就可以随便切换数据库了。\n", 265 | "\n", 266 | "当只有一个User类和User操作类的时候,只需要工厂方法模式就可以了。但是数据库中显然有很多的表,而SQL Server和Acess又是两大不同的类,所以解决这种涉及多个产品系列的问题,就需要使用抽象工厂模式。\n", 267 | "\n", 268 | "## 抽象工厂模式\n", 269 | "\n", 270 | "抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。\n", 271 | "\n", 272 | "在上述问题中:\n", 273 | "- User和Department相当于两个抽象产品;\n", 274 | "- SqlServerUser和AccessUser是抽象产品User的具体产品实现;\n", 275 | "- IFactory是一个抽象工厂接口,里边包含所有的产品创建的抽象方法;\n", 276 | "- SqlServerFactory和AccessFactory是具体工厂;\n", 277 | "\n", 278 | "通常的过程是,在运行时刻创建一个ConcretFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。\n", 279 | "\n", 280 | "### 抽象工厂模式的优点是什么?\n", 281 | "\n", 282 | "最大的好处便是易于交换产品系列,由于具体工厂类在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。\n", 283 | "\n", 284 | "其次的好处就是让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。\n", 285 | "\n", 286 | "### 抽象工厂模式的缺点是什么?\n", 287 | "\n", 288 | "抽象工厂模式可以很方便地切换两个数据库的访问代码,但是当需要增加功能,比如增加项目表Project,那就需要增加三个类IProject,SqlServerProject,AccessProject,还要更改IFactory,SqlServerFactory和AccessFactory。如果有100个调用数据访问的类,那要更改100次才能切换数据库,这是非常丑陋的做法。\n", 289 | "\n", 290 | "## 用简单工厂改进抽象工厂\n", 291 | "\n", 292 | "去除IFactory,SqlServerFactory和AccessFactory三个工厂类,取而代之的是DataAccess类。" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 11, 298 | "metadata": {}, 299 | "outputs": [ 300 | { 301 | "name": "stdout", 302 | "output_type": "stream", 303 | "text": [ 304 | "在SQL Server中添加一个User\n", 305 | "从SQL Server中搜索User 1\n", 306 | "在SQL Server中添加一个Department\n", 307 | "从SQL Server中搜索Department 1\n" 308 | ] 309 | } 310 | ], 311 | "source": [ 312 | "class DataAcess():\n", 313 | " \n", 314 | " # 类变量,通过`类名.变量名`访问\n", 315 | " db = \"sql_server\"\n", 316 | " \n", 317 | " @classmethod\n", 318 | " def create_user(self):\n", 319 | " if DataAcess.db == \"sql_server\":\n", 320 | " return SqlServerUser()\n", 321 | " elif DataAcess.db == \"access\":\n", 322 | " return AccessUser()\n", 323 | "\n", 324 | " @classmethod\n", 325 | " def create_department(self):\n", 326 | " if DataAcess.db == \"sql_server\":\n", 327 | " return SqlServerDepartment()\n", 328 | " elif DataAcess.db == \"access\":\n", 329 | " return AccessDepartment()\n", 330 | " \n", 331 | " \n", 332 | "def main():\n", 333 | " user = User()\n", 334 | " dept = Department()\n", 335 | " \n", 336 | " iu = DataAcess.create_user()\n", 337 | " iu.insert(user)\n", 338 | " iu.get_user(1)\n", 339 | " \n", 340 | " idept = DataAcess.create_department()\n", 341 | " idept.insert(dept)\n", 342 | " idept.get_department(1)\n", 343 | " \n", 344 | "main()" 345 | ] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "metadata": {}, 350 | "source": [ 351 | "### 点评\n", 352 | "所有用到简单工厂的地方,都可以考虑使用反射技术来去除swith或if-else,接触分支带来的耦合。\n", 353 | "\n", 354 | "## 反射版本" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": 20, 360 | "metadata": {}, 361 | "outputs": [ 362 | { 363 | "name": "stdout", 364 | "output_type": "stream", 365 | "text": [ 366 | "在Access中添加一个User\n", 367 | "从Access中搜索User 1\n", 368 | "在Access中添加一个Department\n", 369 | "从Access中搜索Department 1\n" 370 | ] 371 | } 372 | ], 373 | "source": [ 374 | "import sys\n", 375 | "\n", 376 | "def createInstance(module_name, class_name, *args, **kwargs):\n", 377 | " class_meta = getattr(module_name, class_name)\n", 378 | " obj = class_meta(*args, **kwargs)\n", 379 | " return obj\n", 380 | "\n", 381 | "\n", 382 | "def main():\n", 383 | " db = \"Access\" # load from config file\n", 384 | " user = User()\n", 385 | " dept = Department()\n", 386 | " \n", 387 | " iuser = createInstance(sys.modules[__name__], \"{}User\".format(db))\n", 388 | " iuser.insert(user)\n", 389 | " iuser.get_user(1)\n", 390 | " \n", 391 | " idept = createInstance(sys.modules[__name__], \"{}Department\".format(db))\n", 392 | " idept.insert(dept)\n", 393 | " idept.get_department(1)\n", 394 | " \n", 395 | "main()" 396 | ] 397 | } 398 | ], 399 | "metadata": { 400 | "kernelspec": { 401 | "display_name": "Python 3", 402 | "language": "python", 403 | "name": "python3" 404 | }, 405 | "language_info": { 406 | "codemirror_mode": { 407 | "name": "ipython", 408 | "version": 3 409 | }, 410 | "file_extension": ".py", 411 | "mimetype": "text/x-python", 412 | "name": "python", 413 | "nbconvert_exporter": "python", 414 | "pygments_lexer": "ipython3", 415 | "version": "3.6.4" 416 | } 417 | }, 418 | "nbformat": 4, 419 | "nbformat_minor": 2 420 | } 421 | -------------------------------------------------------------------------------- /第16章 上班,干活,下班,加班——状态模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目\n", 10 | "用代码模拟一天的工作状态,上午状态好,中午想睡觉,下午渐恢复,加班苦煎熬。\n", 11 | "\n", 12 | "## 基础版本——函数版" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 3, 18 | "metadata": {}, 19 | "outputs": [ 20 | { 21 | "name": "stdout", 22 | "output_type": "stream", 23 | "text": [ 24 | "当前时间: 9 点, 上午工作,精神百倍\n", 25 | "当前时间: 10 点, 上午工作,精神百倍\n", 26 | "当前时间: 12 点, 饿了,午饭,犯困,午休\n", 27 | "当前时间: 13 点, 下午状态还可以,继续努力\n", 28 | "当前时间: 14 点, 下午状态还可以,继续努力\n", 29 | "当前时间: 17 点, 收工,下班\n", 30 | "当前时间: 19 点, 收工,下班\n", 31 | "当前时间: 22 点, 收工,下班\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "hour = 0\n", 37 | "work_finished = False\n", 38 | "\n", 39 | "def write_program():\n", 40 | " if hour < 12:\n", 41 | " print(\"当前时间: {} 点, 上午工作,精神百倍\".format(hour))\n", 42 | " elif hour < 13:\n", 43 | " print(\"当前时间: {} 点, 饿了,午饭,犯困,午休\".format(hour))\n", 44 | " elif hour < 17:\n", 45 | " print(\"当前时间: {} 点, 下午状态还可以,继续努力\".format(hour))\n", 46 | " elif work_finished == True:\n", 47 | " print(\"当前时间: {} 点, 收工,下班\".format(hour))\n", 48 | " elif hour < 21:\n", 49 | " print(\"当前时间: {} 点, 加班中,好累\".format(hour))\n", 50 | " else:\n", 51 | " print(\"当前时间: {} 点, 不行了,睡着了\".format(hour))\n", 52 | " \n", 53 | "hour = 9\n", 54 | "write_program()\n", 55 | "\n", 56 | "hour = 10\n", 57 | "write_program()\n", 58 | "\n", 59 | "hour = 12\n", 60 | "write_program()\n", 61 | "\n", 62 | "hour = 13\n", 63 | "write_program()\n", 64 | "\n", 65 | "hour = 14\n", 66 | "write_program()\n", 67 | "\n", 68 | "hour = 17\n", 69 | "work_finished = True\n", 70 | "# work_finished = False\n", 71 | "write_program()\n", 72 | "\n", 73 | "hour = 19\n", 74 | "write_program()\n", 75 | "\n", 76 | "hour = 22\n", 77 | "write_program()" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "## 改进版本1.0——初步封装" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "metadata": {}, 91 | "outputs": [ 92 | { 93 | "name": "stdout", 94 | "output_type": "stream", 95 | "text": [ 96 | "当前时间: 9 点, 上午工作,精神百倍\n", 97 | "当前时间: 10 点, 上午工作,精神百倍\n", 98 | "当前时间: 12 点, 饿了,午饭,犯困,午休\n", 99 | "当前时间: 13 点, 下午状态还可以,继续努力\n", 100 | "当前时间: 14 点, 下午状态还可以,继续努力\n", 101 | "当前时间: 17 点, 收工,下班\n", 102 | "当前时间: 19 点, 收工,下班\n", 103 | "当前时间: 22 点, 收工,下班\n" 104 | ] 105 | } 106 | ], 107 | "source": [ 108 | "class Work():\n", 109 | " def __init__(self):\n", 110 | " self.hour = 0\n", 111 | " self.task_finished = False\n", 112 | " \n", 113 | " def write_program(self):\n", 114 | " if self.hour < 12:\n", 115 | " print(\"当前时间: {} 点, 上午工作,精神百倍\".format(self.hour))\n", 116 | " elif self.hour < 13:\n", 117 | " print(\"当前时间: {} 点, 饿了,午饭,犯困,午休\".format(self.hour))\n", 118 | " elif self.hour < 17:\n", 119 | " print(\"当前时间: {} 点, 下午状态还可以,继续努力\".format(self.hour))\n", 120 | " elif self.work_finished == True:\n", 121 | " print(\"当前时间: {} 点, 收工,下班\".format(self.hour))\n", 122 | " elif self.hour < 21:\n", 123 | " print(\"当前时间: {} 点, 加班中,好累\".format(self.hour))\n", 124 | " else:\n", 125 | " print(\"当前时间: {} 点, 不行了,睡着了\".format(self.hour))\n", 126 | " \n", 127 | "work = Work()\n", 128 | "work.hour = 9\n", 129 | "work.write_program()\n", 130 | "\n", 131 | "work.hour = 10\n", 132 | "work.write_program()\n", 133 | "\n", 134 | "work.hour = 12\n", 135 | "work.write_program()\n", 136 | "\n", 137 | "work.hour = 13\n", 138 | "work.write_program()\n", 139 | "\n", 140 | "work.hour = 14\n", 141 | "work.write_program()\n", 142 | "\n", 143 | "work.hour = 17\n", 144 | "work.work_finished = True\n", 145 | "# work_finished = False\n", 146 | "work.write_program()\n", 147 | "\n", 148 | "work.hour = 19\n", 149 | "work.write_program()\n", 150 | "\n", 151 | "work.hour = 22\n", 152 | "work.write_program()" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "metadata": {}, 158 | "source": [ 159 | "### 点评\n", 160 | "- 这个类中的write_program方法过长,而且有很多判断分支,意味着它的责任过大了。面向对象设计其实就是希望做到代码的责任分解。所以这个类违背了`单一职责原则`;\n", 161 | "- 此外,write_program方法里有这么多判断,使得任何需求的改动或增加,都需要去更改这个方法。所以这个类也违背了`开放-封闭原则`;\n", 162 | "\n", 163 | "## 状态模式\n", 164 | "\n", 165 | "状态模式,当一个对象的内在状态改变是允许改变其行为,这个对象看起来像是改变了其类。[DP]\n", 166 | "\n", 167 | "状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 9, 173 | "metadata": {}, 174 | "outputs": [ 175 | { 176 | "name": "stdout", 177 | "output_type": "stream", 178 | "text": [ 179 | "当前状态: \n", 180 | "当前状态: \n", 181 | "当前状态: \n", 182 | "当前状态: \n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "from abc import ABCMeta, abstractmethod\n", 188 | "\n", 189 | "\n", 190 | "class State():\n", 191 | " __metaclass__ = ABCMeta\n", 192 | " \n", 193 | " @abstractmethod\n", 194 | " def handle(self, context):\n", 195 | " pass\n", 196 | " \n", 197 | " \n", 198 | "class StateA(State):\n", 199 | " \n", 200 | " def handle(self, context):\n", 201 | " context.set_state(StateB())\n", 202 | " \n", 203 | " \n", 204 | "class StateB(State):\n", 205 | " \n", 206 | " def handle(self, context):\n", 207 | " context.set_state(StateA())\n", 208 | " \n", 209 | "\n", 210 | "class Context():\n", 211 | " \n", 212 | " def __init__(self, state):\n", 213 | " self.state = state\n", 214 | " \n", 215 | " def set_state(self, state):\n", 216 | " self.state = state\n", 217 | " print(\"当前状态: {}\".format(self.state.__class__))\n", 218 | " \n", 219 | " def request(self):\n", 220 | " self.state.handle(self) # 精髓\n", 221 | " \n", 222 | " \n", 223 | "def main():\n", 224 | " context = Context(StateA())\n", 225 | " context.request()\n", 226 | " context.request()\n", 227 | " context.request()\n", 228 | " context.request()\n", 229 | " \n", 230 | "main() " 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "### 状态模式的好处与用处\n", 238 | "\n", 239 | "状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。[DP]就是将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcretState中,所以通过定义新的子类可以很容易的增加新的状态和转换[DP]。这样做的目的就是为了消除庞大的条件分支语句,大的分支判断会使得它们难以修改和扩展。状态模式通过把各种状态转移逻辑分不到State的子类之间,来减少相互之间的依赖。\n", 240 | "\n", 241 | "什么时候需要考虑使用状态模式呢?当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为,就可以使用状态模式。另外,如果业务需求某项业务有多个状态,通常都是一些枚举常量,状态的变化都是依靠大量的分支判断语句来实现,此时应该考虑将每一种业务状态定义为一个State子类,这样这些对象就可以不依赖于其他对象而独立变化了,如果某天客户需求改了,增加或减少业务状态或改变状态流程,都不是困难了。\n", 242 | "\n", 243 | "## 改进版本2.0——状态模式" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 13, 249 | "metadata": {}, 250 | "outputs": [ 251 | { 252 | "name": "stdout", 253 | "output_type": "stream", 254 | "text": [ 255 | "当前时间: 9 点, 上午工作,精神百倍\n", 256 | "当前时间: 10 点, 上午工作,精神百倍\n", 257 | "当前时间: 12 点, 饿了,午饭,犯困,午休\n", 258 | "当前时间: 13 点, 下午状态还可以,继续努力\n", 259 | "当前时间: 14 点, 下午状态还可以,继续努力\n", 260 | "当前时间: 17 点, 加班中,好累\n", 261 | "当前时间: 19 点, 加班中,好累\n", 262 | "当前时间: 22 点, 不行了,睡着了\n" 263 | ] 264 | } 265 | ], 266 | "source": [ 267 | "from abc import ABCMeta, abstractmethod\n", 268 | "\n", 269 | "\n", 270 | "class State():\n", 271 | " __metaclass__ = ABCMeta\n", 272 | " \n", 273 | " @abstractmethod\n", 274 | " def write_program(self, work):\n", 275 | " pass\n", 276 | " \n", 277 | "class ForenoonState(State):\n", 278 | " \n", 279 | " def write_program(self, work):\n", 280 | " if work.hour < 12:\n", 281 | " print(\"当前时间: {} 点, 上午工作,精神百倍\".format(work.hour))\n", 282 | " else:\n", 283 | " work.set_state(NoonState())\n", 284 | " work.write_program()\n", 285 | " \n", 286 | " \n", 287 | "class NoonState(State):\n", 288 | " \n", 289 | " def write_program(self, work):\n", 290 | " if work.hour < 13:\n", 291 | " print(\"当前时间: {} 点, 饿了,午饭,犯困,午休\".format(work.hour))\n", 292 | " else:\n", 293 | " work.set_state(AfternoonState())\n", 294 | " work.write_program()\n", 295 | " \n", 296 | " \n", 297 | "class AfternoonState(State):\n", 298 | " \n", 299 | " def write_program(self, work):\n", 300 | " if work.hour < 17:\n", 301 | " print(\"当前时间: {} 点, 下午状态还可以,继续努力\".format(work.hour))\n", 302 | " else:\n", 303 | " work.set_state(EveningState())\n", 304 | " work.write_program()\n", 305 | "\n", 306 | " \n", 307 | "class EveningState(State):\n", 308 | " \n", 309 | " def write_program(self, work):\n", 310 | " if work.task_finished == True:\n", 311 | " work.set_state(RestState())\n", 312 | " work.write_program()\n", 313 | " elif work.hour < 21:\n", 314 | " print(\"当前时间: {} 点, 加班中,好累\".format(work.hour))\n", 315 | " else:\n", 316 | " work.set_state(SleepingState())\n", 317 | " work.write_program()\n", 318 | " \n", 319 | "\n", 320 | "class SleepingState(State):\n", 321 | " \n", 322 | " def write_program(self, work):\n", 323 | " print(\"当前时间: {} 点, 不行了,睡着了\".format(work.hour))\n", 324 | " \n", 325 | " \n", 326 | "class RestState(State):\n", 327 | " \n", 328 | " def write_program(self, work):\n", 329 | " print(\"当前时间: {} 点, 收工,下班\".format(work.hour))\n", 330 | " \n", 331 | " \n", 332 | "class Work():\n", 333 | " \n", 334 | " def __init__(self, state):\n", 335 | " self.state = state\n", 336 | " self.hour = 0\n", 337 | " self.task_finished = False\n", 338 | " \n", 339 | " def set_state(self, state):\n", 340 | " self.state = state\n", 341 | " \n", 342 | " def write_program(self):\n", 343 | " self.state.write_program(self) # 精髓\n", 344 | " \n", 345 | "work = Work(ForenoonState())\n", 346 | "work.hour = 9\n", 347 | "work.write_program()\n", 348 | "\n", 349 | "work.hour = 10\n", 350 | "work.write_program()\n", 351 | "\n", 352 | "work.hour = 12\n", 353 | "work.write_program()\n", 354 | "\n", 355 | "work.hour = 13\n", 356 | "work.write_program()\n", 357 | "\n", 358 | "work.hour = 14\n", 359 | "work.write_program()\n", 360 | "\n", 361 | "work.hour = 17\n", 362 | "work.work_finished = True\n", 363 | "# work_finished = False\n", 364 | "work.write_program()\n", 365 | "\n", 366 | "work.hour = 19\n", 367 | "work.write_program()\n", 368 | "\n", 369 | "work.hour = 22\n", 370 | "work.write_program()\n", 371 | " " 372 | ] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "metadata": {}, 377 | "source": [ 378 | "### 点评\n", 379 | "假如老板规定“员工必须在20点之前离开公司”,那么只需要增加一个“强制下班状态”,然后改动一下“傍晚工作状态”就可以了。而这是不影响其他状态的代码的。" 380 | ] 381 | } 382 | ], 383 | "metadata": { 384 | "kernelspec": { 385 | "display_name": "Python 3", 386 | "language": "python", 387 | "name": "python3" 388 | }, 389 | "language_info": { 390 | "codemirror_mode": { 391 | "name": "ipython", 392 | "version": 3 393 | }, 394 | "file_extension": ".py", 395 | "mimetype": "text/x-python", 396 | "name": "python", 397 | "nbconvert_exporter": "python", 398 | "pygments_lexer": "ipython3", 399 | "version": "3.6.4" 400 | } 401 | }, 402 | "nbformat": 4, 403 | "nbformat_minor": 2 404 | } 405 | -------------------------------------------------------------------------------- /第17章 程序中的翻译官——适配器模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 适配器模式\n", 10 | "\n", 11 | "适配器模式,将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作[DP]。\n", 12 | "\n", 13 | "当系统的数据和行为都正确,但是接口不符时,我们应该考虑使用适配器模式,目的就是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "特殊请求\n" 26 | ] 27 | } 28 | ], 29 | "source": [ 30 | "class Target():\n", 31 | " \"\"\"\n", 32 | " Target类,这是客户所期待的接口。可以是具体或抽象的类,也可以是接口。\n", 33 | " \"\"\"\n", 34 | " def request(self):\n", 35 | " print(\"普通请求\")\n", 36 | " \n", 37 | " \n", 38 | "class Adaptee():\n", 39 | " \"\"\"\n", 40 | " 需要适配的类\n", 41 | " \"\"\"\n", 42 | " def specific_request(self):\n", 43 | " print(\"特殊请求\")\n", 44 | " \n", 45 | " \n", 46 | "class Adapter(Target):\n", 47 | " \"\"\"\n", 48 | " 适配器,通过内部包装一个Adaptee对象,把源接口转换成目标接口\n", 49 | " \"\"\"\n", 50 | " def __init__(self):\n", 51 | " self.adaptee = Adaptee()\n", 52 | " \n", 53 | " def request(self):\n", 54 | " self.adaptee.specific_request()\n", 55 | " \n", 56 | " \n", 57 | "def main():\n", 58 | " target = Adapter()\n", 59 | " target.request()\n", 60 | " \n", 61 | "main()" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "### 何时使用适配器模式?\n", 69 | "\n", 70 | "想使用一个已经存在的类,但如果它的接口,也就是它的方法和你的要求不想同时,就应该考虑使用适配器模式。\n", 71 | "\n", 72 | "对于公司内部独立开发的系统,类和方法名的规范应当在设计之初就规定好,当接口不相同时,首先不应该考虑使用适配器,而是应该考虑通过重构统一接口。\n", 73 | "\n", 74 | "只有在双方都不太容易修改的时候再使用适配器模式。\n", 75 | "\n", 76 | "但是如果设计之初,我们准备使用第三方开发组件,而这个组件的接口于我们自己的系统接口是不相同的,而我们也完全没有必要为了迎合它而改动自己的接口,此时尽管在开发的设计阶段,也就是可以考虑用适配器模式来解决接口不同的问题。\n", 77 | "\n", 78 | "## 题目\n", 79 | "\n", 80 | "用程序模拟姚明到国外打NBA初期依赖翻译的场景。" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 3, 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "Forward FFF attack\n", 93 | "Guards GGG defense\n", 94 | "Center CCC jingong\n", 95 | "Center CCC fangshou\n" 96 | ] 97 | } 98 | ], 99 | "source": [ 100 | "from abc import ABCMeta, abstractmethod\n", 101 | "\n", 102 | "\n", 103 | "class Player():\n", 104 | " \n", 105 | " __metaclass__ = ABCMeta\n", 106 | " \n", 107 | " def __init__(self, name):\n", 108 | " self.name = name\n", 109 | " \n", 110 | " @abstractmethod\n", 111 | " def attack(self):\n", 112 | " pass\n", 113 | " \n", 114 | " @abstractmethod\n", 115 | " def defense(self):\n", 116 | " pass\n", 117 | " \n", 118 | " \n", 119 | "class Forwards(Player):\n", 120 | " \n", 121 | " def attack(self):\n", 122 | " print(\"Forward {} attack\".format(self.name))\n", 123 | " \n", 124 | " def defense(self):\n", 125 | " print(\"Forward {} defense\".format(self.name))\n", 126 | " \n", 127 | " \n", 128 | "class Guards(Player):\n", 129 | " \n", 130 | " def attack(self):\n", 131 | " print(\"Guards {} attack\".format(self.name))\n", 132 | " \n", 133 | " def defense(self):\n", 134 | " print(\"Guards {} defense\".format(self.name))\n", 135 | "\n", 136 | " \n", 137 | "class ForeignCenter():\n", 138 | " \n", 139 | " def __init__(self, name):\n", 140 | " self.name = name\n", 141 | " \n", 142 | " def jingong(self):\n", 143 | " print(\"Center {} jingong\".format(self.name))\n", 144 | " \n", 145 | " def fangshou(self):\n", 146 | " print(\"Center {} fangshou\".format(self.name))\n", 147 | "\n", 148 | "\n", 149 | "class Translator(Player):\n", 150 | " \n", 151 | " def __init__(self, name):\n", 152 | " self.foreign_center = ForeignCenter(name)\n", 153 | " \n", 154 | " def attack(self):\n", 155 | " self.foreign_center.jingong()\n", 156 | " \n", 157 | " def defense(self):\n", 158 | " self.foreign_center.fangshou()\n", 159 | " \n", 160 | " \n", 161 | "forwards = Forwards(\"FFF\")\n", 162 | "forwards.attack()\n", 163 | "\n", 164 | "guards = Guards(\"GGG\")\n", 165 | "guards.defense()\n", 166 | "\n", 167 | "center = Translator(\"CCC\")\n", 168 | "center.attack()\n", 169 | "center.defense()\n", 170 | "\n" 171 | ] 172 | } 173 | ], 174 | "metadata": { 175 | "kernelspec": { 176 | "display_name": "Python 3", 177 | "language": "python", 178 | "name": "python3" 179 | }, 180 | "language_info": { 181 | "codemirror_mode": { 182 | "name": "ipython", 183 | "version": 3 184 | }, 185 | "file_extension": ".py", 186 | "mimetype": "text/x-python", 187 | "name": "python", 188 | "nbconvert_exporter": "python", 189 | "pygments_lexer": "ipython3", 190 | "version": "3.6.4" 191 | } 192 | }, 193 | "nbformat": 4, 194 | "nbformat_minor": 2 195 | } 196 | -------------------------------------------------------------------------------- /第18章 游戏角色备份——备忘录模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目\n", 10 | "\n", 11 | "用代码模拟以下场景,一个游戏角色有生命力,攻击力,防御力等数据,在打Boss前后的数据值会发生变化,如果玩家挑战Boss失败,我们允许玩家可以将游戏数据恢复到与Boss决斗之前的状态。\n", 12 | "\n", 13 | "## 基础版本" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "当前角色状态:\n", 26 | "体力: 100\n", 27 | "攻击: 100\n", 28 | "防御: 100\n", 29 | "当前角色状态:\n", 30 | "体力: 0\n", 31 | "攻击: 0\n", 32 | "防御: 0\n", 33 | "当前角色状态:\n", 34 | "体力: 100\n", 35 | "攻击: 100\n", 36 | "防御: 100\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "class GameRole():\n", 42 | " \n", 43 | " def __init__(self):\n", 44 | " self.vitality = 0\n", 45 | " self.attack = 0\n", 46 | " self.defense = 0\n", 47 | " \n", 48 | " def state_display(self):\n", 49 | " print(\"当前角色状态:\")\n", 50 | " print(\"体力:\",self.vitality)\n", 51 | " print(\"攻击:\",self.attack)\n", 52 | " print(\"防御:\",self.defense)\n", 53 | " \n", 54 | " def get_init_state(self):\n", 55 | " \"\"\"\n", 56 | " 从服务器或者本地磁盘加载初始状态\n", 57 | " \"\"\"\n", 58 | " self.vitality = 100\n", 59 | " self.attack = 100\n", 60 | " self.defense = 100\n", 61 | " \n", 62 | " def fight(self):\n", 63 | " \"\"\"\n", 64 | " 与Boss决斗,数据损耗为0\n", 65 | " \"\"\"\n", 66 | " self.vitality = 0\n", 67 | " self.attack = 0\n", 68 | " self.defense = 0\n", 69 | " \n", 70 | " \n", 71 | "def main():\n", 72 | " mario = GameRole() \n", 73 | " mario.get_init_state() # 大战Boss前,获取初始角色状态\n", 74 | " mario.state_display()\n", 75 | " \n", 76 | " backup = GameRole() # 保存游戏进度\n", 77 | " backup.vitality = mario.vitality\n", 78 | " backup.attack = mario.attack\n", 79 | " backup.defense = mario.defense\n", 80 | " \n", 81 | " mario.fight() # 大战Boss\n", 82 | " mario.state_display()\n", 83 | " \n", 84 | " mario.vitality = backup.vitality # 读取进度,重新来过\n", 85 | " mario.attack = backup.attack\n", 86 | " mario.defense = backup.defense\n", 87 | " \n", 88 | " mario.state_display()\n", 89 | " \n", 90 | "main()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### 点评\n", 98 | "主要问题在于客户端调用:\n", 99 | "1. 游戏角色细节暴露给了客户端,客户端需要知道游戏角色的生命力,攻击力,防御力等细节;\n", 100 | "2. 客户端还要对游戏角色进行备份;\n", 101 | "3. 如果以后增加“魔法值”或者修改现有数据,都要更改客户端;\n", 102 | "4. 恢复角色的过程也存在同样问题;\n", 103 | "\n", 104 | "## 备忘录模式\n", 105 | "备忘录模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。主要包括三种类:\n", 106 | "- 发起人(Originator):负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储Originator的哪些内部状态;\n", 107 | "- 备忘录(Memento):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator可以看到一个宽接口,允许它访问返回到先前状态的所有数据;\n", 108 | "- 管理者(Caretaker):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。\n", 109 | "\n", 110 | "\n", 111 | "在题目的场景中,游戏角色类相当于Originator,使用同样的“游戏角色”实例“backup”来做备忘录,这在当需要保存全部信息时,是可以考虑的,而用clone的方式来实现Memento的状态保存可能是更好的方法,但是这样就相当于对上层应用开放了Originator的全部接口,这对于保存备份有时候并不合适。\n", 112 | "\n", 113 | "那么当我们不需要保存全部信息以备使用时,需要怎么办?当我们需要保存的并不是全部信息,而只是部分信息,那么就应该有独立的备忘录类Memento,它只拥有需要保存的信息的属性。\n", 114 | "\n", 115 | "## 改进版本——备忘录模式" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 4, 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "name": "stdout", 125 | "output_type": "stream", 126 | "text": [ 127 | "当前角色状态:\n", 128 | "体力: 100\n", 129 | "攻击: 100\n", 130 | "防御: 100\n", 131 | "当前角色状态:\n", 132 | "体力: 0\n", 133 | "攻击: 0\n", 134 | "防御: 0\n", 135 | "当前角色状态:\n", 136 | "体力: 100\n", 137 | "攻击: 100\n", 138 | "防御: 100\n" 139 | ] 140 | } 141 | ], 142 | "source": [ 143 | "class GameRole():\n", 144 | " \n", 145 | " def __init__(self):\n", 146 | " self.vitality = 0\n", 147 | " self.attack = 0\n", 148 | " self.defense = 0\n", 149 | " \n", 150 | " def state_display(self):\n", 151 | " print(\"当前角色状态:\")\n", 152 | " print(\"体力:\",self.vitality)\n", 153 | " print(\"攻击:\",self.attack)\n", 154 | " print(\"防御:\",self.defense)\n", 155 | " \n", 156 | " def get_init_state(self):\n", 157 | " \"\"\"\n", 158 | " 从服务器或者本地磁盘加载初始状态\n", 159 | " \"\"\"\n", 160 | " self.vitality = 100\n", 161 | " self.attack = 100\n", 162 | " self.defense = 100\n", 163 | " \n", 164 | " def fight(self):\n", 165 | " \"\"\"\n", 166 | " 与Boss决斗,数据损耗为0\n", 167 | " \"\"\"\n", 168 | " self.vitality = 0\n", 169 | " self.attack = 0\n", 170 | " self.defense = 0\n", 171 | " \n", 172 | " def save_state(self):\n", 173 | " \"\"\"\n", 174 | " 新增“保存状态”的方法\n", 175 | " \"\"\"\n", 176 | " return RoleStateMemento(self.vitality, self.attack, self.defense)\n", 177 | " \n", 178 | " def recovery_state(self, memento):\n", 179 | " \"\"\"\n", 180 | " 新增“恢复状态”的方法\n", 181 | " \"\"\"\n", 182 | " self.vitality = memento.vitality\n", 183 | " self.attack = memento.attack\n", 184 | " self.defense = memento.defense\n", 185 | " \n", 186 | "\n", 187 | "class RoleStateMemento():\n", 188 | " \"\"\"\n", 189 | " 游戏角色状态存储箱\n", 190 | " \"\"\"\n", 191 | " def __init__(self, vitality, attack, defense):\n", 192 | " self.vitality = vitality\n", 193 | " self.attack = attack\n", 194 | " self.defense = defense\n", 195 | " \n", 196 | "\n", 197 | "class RoleStateCaretaker():\n", 198 | " \"\"\"\n", 199 | " 角色状态管理者类\n", 200 | " \"\"\"\n", 201 | " def __init__(self):\n", 202 | " self.memento = None\n", 203 | " \n", 204 | "\n", 205 | "def main():\n", 206 | " mario = GameRole()\n", 207 | " mario.get_init_state()\n", 208 | " mario.state_display()\n", 209 | " \n", 210 | " state_admin = RoleStateCaretaker()\n", 211 | " state_admin.memento = mario.save_state() # 游戏角色的保存对客户端是透明的\n", 212 | " \n", 213 | " mario.fight()\n", 214 | " mario.state_display()\n", 215 | " \n", 216 | " mario.recovery_state(state_admin.memento) # 游戏角色的恢复对客户端是透明的\n", 217 | " mario.state_display()\n", 218 | " \n", 219 | "main()" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": {}, 225 | "source": [ 226 | "### 点评\n", 227 | "将需要保存的细节封装在Memento类中,那一天要更改保存的细节也不用影响客户端。\n", 228 | "\n", 229 | "Memento模式比较适用于功能复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。\n", 230 | "\n", 231 | "命令模式也有类似的撤销作用,如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。有时候一些对象的内部信息必须保存在对象以外的地方,但是必须要由对象自己读取,这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来,从而可以恰当的保持封装的边界。\n", 232 | "\n", 233 | "当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。" 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.6.4" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 2 258 | } 259 | -------------------------------------------------------------------------------- /第19章 分公司=部门?——组合模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 组合模式\n", 10 | "\n", 11 | "组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和对组合对象的使用具有一致性[DP]。" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 3, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "- root\n", 24 | "--- Leaf A\n", 25 | "--- Leaf B\n", 26 | "--- Composite X\n", 27 | "----- Leaf XA\n", 28 | "----- Leaf XB\n", 29 | "----- Composite XY\n", 30 | "------- Leaf XYA\n", 31 | "------- Leaf XYB\n", 32 | "--- Leaf C\n" 33 | ] 34 | } 35 | ], 36 | "source": [ 37 | "from abc import ABCMeta, abstractmethod\n", 38 | "\n", 39 | "\n", 40 | "class Component():\n", 41 | " \"\"\"\n", 42 | " Component为组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为。\n", 43 | " 声明一个接口用于访问和管理Component的子部件。\n", 44 | " \"\"\"\n", 45 | " __metaclass__ = ABCMeta\n", 46 | " \n", 47 | " def __init__(self, name):\n", 48 | " self.name = name\n", 49 | " \n", 50 | " @abstractmethod\n", 51 | " def add(self, c):\n", 52 | " \"\"\"\n", 53 | " 通常用add来增加树枝或树叶\n", 54 | " \"\"\"\n", 55 | " pass\n", 56 | " \n", 57 | " @abstractmethod\n", 58 | " def remove(self, c):\n", 59 | " \"\"\"\n", 60 | " 通常用remove来删除树枝或树叶\n", 61 | " \"\"\"\n", 62 | " pass\n", 63 | " \n", 64 | " @abstractmethod\n", 65 | " def display(self, depth):\n", 66 | " pass\n", 67 | " \n", 68 | " \n", 69 | "class Leaf(Component):\n", 70 | " \"\"\"\n", 71 | " 叶子节点\n", 72 | " \"\"\"\n", 73 | " def add(self, c):\n", 74 | " print(\"叶子节点无法添加子节点\")\n", 75 | " \n", 76 | " def remove(self, c):\n", 77 | " print(\"叶子节点无法删除子节点\")\n", 78 | " \n", 79 | " def display(self, depth):\n", 80 | " print(\"-\"*depth, self.name)\n", 81 | " \n", 82 | "\n", 83 | "class Composite(Component):\n", 84 | " \"\"\"\n", 85 | " 子部件节点\n", 86 | " \"\"\"\n", 87 | " def __init__(self, name):\n", 88 | " super().__init__(name)\n", 89 | " self.children = []\n", 90 | " \n", 91 | " def add(self, c):\n", 92 | " self.children.append(c)\n", 93 | " \n", 94 | " def remove(self, c):\n", 95 | " self.children.remove(c)\n", 96 | " \n", 97 | " def display(self, depth):\n", 98 | " print(\"-\"*depth, self.name)\n", 99 | " for c in self.children:\n", 100 | " c.display(depth+2)\n", 101 | " \n", 102 | " \n", 103 | "def main():\n", 104 | " root = Composite(\"root\")\n", 105 | " root.add(Leaf(\"Leaf A\"))\n", 106 | " root.add(Leaf(\"Leaf B\"))\n", 107 | " \n", 108 | " comp = Composite(\"Composite X\")\n", 109 | " comp.add(Leaf(\"Leaf XA\"))\n", 110 | " comp.add(Leaf(\"Leaf XB\"))\n", 111 | " \n", 112 | " root.add(comp)\n", 113 | " \n", 114 | " comp2 = Composite(\"Composite XY\")\n", 115 | " comp2.add(Leaf(\"Leaf XYA\"))\n", 116 | " comp2.add(Leaf(\"Leaf XYB\"))\n", 117 | " \n", 118 | " comp.add(comp2)\n", 119 | " \n", 120 | " root.add(Leaf(\"Leaf C\"))\n", 121 | " \n", 122 | " leaf_d = Leaf(\"Leaf D\")\n", 123 | " root.add(leaf_d)\n", 124 | " root.remove(leaf_d)\n", 125 | " \n", 126 | " root.display(1)\n", 127 | " \n", 128 | "main()\n", 129 | " " 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "### 透明方式与安全方式\n", 137 | "\n", 138 | "Leaf类中也有Add和Reomve,但是树叶不可以再长分枝。这种方式叫做透明方式,也就是说再Component中声明所有用来管理子对象的方法,其中包括add,remove等。这样Component抽象类的所有子类都具备了add和remove。这样的好处在于叶子节点和分枝节点对于外界没有区别,它们具备完全一致的行为接口。但是问题也比较明显,因为Leaf类本身不具备add和remove等功能,所以实现它是没有意义的。\n", 139 | "\n", 140 | "另一种是安全方式,也就是在Component接口中不去声明add和remove方法,那么子类Leaf也就不需要去实现它,而是在Composite声明所有用来管理子类对象的方法,这样做就不会出现刚才提到的问题,不过由于不透明,所以树叶和树枝类将有不同的接口,客户端调用需要做相应的判断,带来了不便。\n", 141 | "\n", 142 | "### 何时使用组合模式\n", 143 | "\n", 144 | "当需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。\n", 145 | "\n", 146 | "## 题目\n", 147 | "使用组合模式,模拟公司管理系统。" 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": 4, 153 | "metadata": {}, 154 | "outputs": [ 155 | { 156 | "name": "stdout", 157 | "output_type": "stream", 158 | "text": [ 159 | "组织架构图\n", 160 | "- 北京总公司\n", 161 | "--- 总公司人力资源部\n", 162 | "--- 总公司财务部\n", 163 | "--- 上海华东分公司\n", 164 | "----- 华东分公司人力资源部\n", 165 | "----- 华东分公司财务部\n", 166 | "----- 南京办事处\n", 167 | "------- 南京办事处人力资源部\n", 168 | "------- 南京办事处财务部\n", 169 | "----- 杭州办事处\n", 170 | "------- 杭州办事处人力资源部\n", 171 | "------- 杭州办事处财务部\n", 172 | "履行职责\n", 173 | "总公司人力资源部负责员工招聘。\n", 174 | "总公司财务部负责财务收支。\n", 175 | "华东分公司人力资源部负责员工招聘。\n", 176 | "华东分公司财务部负责财务收支。\n", 177 | "南京办事处人力资源部负责员工招聘。\n", 178 | "南京办事处财务部负责财务收支。\n", 179 | "杭州办事处人力资源部负责员工招聘。\n", 180 | "杭州办事处财务部负责财务收支。\n" 181 | ] 182 | } 183 | ], 184 | "source": [ 185 | "from abc import ABCMeta, abstractmethod\n", 186 | "\n", 187 | "\n", 188 | "class Company():\n", 189 | " \"\"\"\n", 190 | " 抽象公司类\n", 191 | " \"\"\"\n", 192 | " __metaclass__ = ABCMeta\n", 193 | " \n", 194 | " def __init__(self, name):\n", 195 | " self.name = name\n", 196 | " \n", 197 | " @abstractmethod\n", 198 | " def add(self, c):\n", 199 | " \"\"\"\n", 200 | " 通常用add来增加树枝或树叶\n", 201 | " \"\"\"\n", 202 | " pass\n", 203 | " \n", 204 | " @abstractmethod\n", 205 | " def remove(self, c):\n", 206 | " \"\"\"\n", 207 | " 通常用remove来删除树枝或树叶\n", 208 | " \"\"\"\n", 209 | " pass\n", 210 | " \n", 211 | " @abstractmethod\n", 212 | " def display(self, depth):\n", 213 | " pass\n", 214 | " \n", 215 | " @abstractmethod\n", 216 | " def line_of_duty(self):\n", 217 | " pass\n", 218 | " \n", 219 | "\n", 220 | "class ConcreteCompany(Company):\n", 221 | " \"\"\"\n", 222 | " 具体公司类\n", 223 | " \"\"\"\n", 224 | " def __init__(self, name):\n", 225 | " super().__init__(name)\n", 226 | " self.children = []\n", 227 | " \n", 228 | " def add(self, c):\n", 229 | " self.children.append(c)\n", 230 | " \n", 231 | " def remove(self, c):\n", 232 | " self.children.remove(c)\n", 233 | " \n", 234 | " def display(self, depth):\n", 235 | " print(\"-\"*depth, self.name)\n", 236 | " for c in self.children:\n", 237 | " c.display(depth+2)\n", 238 | " \n", 239 | " def line_of_duty(self):\n", 240 | " for c in self.children:\n", 241 | " c.line_of_duty()\n", 242 | " \n", 243 | "\n", 244 | "class HRDepartment(Company):\n", 245 | " \"\"\"\n", 246 | " 人力资源部\n", 247 | " \"\"\" \n", 248 | " def add(self, c):\n", 249 | " pass\n", 250 | " \n", 251 | " def remove(self, c):\n", 252 | " pass\n", 253 | " \n", 254 | " def display(self, depth):\n", 255 | " print(\"-\"*depth, self.name)\n", 256 | " \n", 257 | " def line_of_duty(self):\n", 258 | " print(\"{}负责员工招聘。\".format(self.name))\n", 259 | " \n", 260 | " \n", 261 | "class FinanceDepartment(Company):\n", 262 | " \"\"\"\n", 263 | " 财务部\n", 264 | " \"\"\" \n", 265 | " def add(self, c):\n", 266 | " pass\n", 267 | " \n", 268 | " def remove(self, c):\n", 269 | " pass\n", 270 | " \n", 271 | " def display(self, depth):\n", 272 | " print(\"-\"*depth, self.name)\n", 273 | " \n", 274 | " def line_of_duty(self):\n", 275 | " print(\"{}负责财务收支。\".format(self.name))\n", 276 | " \n", 277 | " \n", 278 | "def main():\n", 279 | " root = ConcreteCompany(\"北京总公司\")\n", 280 | " root.add(HRDepartment(\"总公司人力资源部\"))\n", 281 | " root.add(FinanceDepartment(\"总公司财务部\"))\n", 282 | " \n", 283 | " comp = ConcreteCompany(\"上海华东分公司\")\n", 284 | " comp.add(HRDepartment(\"华东分公司人力资源部\"))\n", 285 | " comp.add(FinanceDepartment(\"华东分公司财务部\"))\n", 286 | " root.add(comp)\n", 287 | " \n", 288 | " comp1 = ConcreteCompany(\"南京办事处\")\n", 289 | " comp1.add(HRDepartment(\"南京办事处人力资源部\"))\n", 290 | " comp1.add(FinanceDepartment(\"南京办事处财务部\"))\n", 291 | " comp.add(comp1)\n", 292 | " \n", 293 | " comp2 = ConcreteCompany(\"杭州办事处\")\n", 294 | " comp2.add(HRDepartment(\"杭州办事处人力资源部\"))\n", 295 | " comp2.add(FinanceDepartment(\"杭州办事处财务部\"))\n", 296 | " comp.add(comp2)\n", 297 | " \n", 298 | " print(\"组织架构图\")\n", 299 | " root.display(1)\n", 300 | " \n", 301 | " print(\"履行职责\")\n", 302 | " root.line_of_duty()\n", 303 | " \n", 304 | "main()\n", 305 | " " 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "### 点评\n", 313 | "组合模式定义了包含人力资源部和财务部这些基本对象和分公司,办事处等组合对象的类层次结构。基本对象可以被组合成共复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户端代码中,任何用到基本对象的地方都可以使用组合对象了。用户不用关心到底是处理一个叶子节点还是处理一个组合组件,也用不着为定义组合而写一些选择判断语句。\n", 314 | "\n", 315 | "简单的说,组合模式让客户可以一致地使用组合结构和单个对象。" 316 | ] 317 | } 318 | ], 319 | "metadata": { 320 | "kernelspec": { 321 | "display_name": "Python 3", 322 | "language": "python", 323 | "name": "python3" 324 | }, 325 | "language_info": { 326 | "codemirror_mode": { 327 | "name": "ipython", 328 | "version": 3 329 | }, 330 | "file_extension": ".py", 331 | "mimetype": "text/x-python", 332 | "name": "python", 333 | "nbconvert_exporter": "python", 334 | "pygments_lexer": "ipython3", 335 | "version": "3.6.4" 336 | } 337 | }, 338 | "nbformat": 4, 339 | "nbformat_minor": 2 340 | } 341 | -------------------------------------------------------------------------------- /第1章 计算器——简单工厂模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 写在前面的话\n", 8 | "\n", 9 | "读书的时候上过《设计模式》这一门课,当时使用的教材是程杰老师的《大话设计模式》,使用的语言是C#,学过课程之后初期深感面向对象思想的伟大,但是很少应用到实际开发中。后来我接触了Python,现在工作中用到最多的也是Python,或许是因为Python的便利性,我写的很多脚本/程序都还是面向过程编程,缺少面向对象的思想在里边。因此,我打算重读程杰老师的《大话设计模式》并用Python进行实践。\n", 10 | "\n", 11 | "## 题目\n", 12 | "\n", 13 | "用一种面向对象语言实现一个计算器控制台程序, 要求输入两个数和运算符号(+-*/), 得到结果.\n", 14 | "\n", 15 | "## 基础版本" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 4, 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "name": "stdout", 25 | "output_type": "stream", 26 | "text": [ 27 | "input a number:16\n", 28 | "input a operater(+ - * /):*\n", 29 | "input a number:2\n", 30 | "32\n" 31 | ] 32 | } 33 | ], 34 | "source": [ 35 | "a = int(input(\"input a number:\"))\n", 36 | "b = str(input(\"input a operater(+ - * /):\"))\n", 37 | "c = int(input(\"input a number:\"))\n", 38 | "\n", 39 | "if b == \"+\":\n", 40 | " print(a+c)\n", 41 | "elif b == \"-\":\n", 42 | " print(a-c)\n", 43 | "elif b == \"*\":\n", 44 | " print(a*c)\n", 45 | "else b == \"/\":\n", 46 | " print(a/c)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "### 点评\n", 54 | "1. 变量命名不规范\n", 55 | "2. 无用的if条件判断太多\n", 56 | "3. 除法运算中未考虑第二个数字为0的情况\n", 57 | "\n", 58 | "## 改进版本1.0——规范代码" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 6, 64 | "metadata": {}, 65 | "outputs": [ 66 | { 67 | "name": "stdout", 68 | "output_type": "stream", 69 | "text": [ 70 | "input a number:12\n", 71 | "input a operater(+ - * /):/\n", 72 | "input a number:0\n", 73 | "With operator '/', the second number can not be zero.\n" 74 | ] 75 | } 76 | ], 77 | "source": [ 78 | "number_a = int(input(\"input a number:\"))\n", 79 | "operator = str(input(\"input a operater(+ - * /):\"))\n", 80 | "number_b = int(input(\"input a number:\"))\n", 81 | "\n", 82 | "if operator == \"+\":\n", 83 | " print(number_a + number_b)\n", 84 | "elif operator == \"-\":\n", 85 | " print(number_a - number_b)\n", 86 | "elif operator == \"*\":\n", 87 | " print(number_a * number_b)\n", 88 | "elif operator == \"/\":\n", 89 | " if number_b != 0:\n", 90 | " print(number_a / number_b)\n", 91 | " else:\n", 92 | " print(\"With operator '/', the second number can not be zero.\")\n", 93 | "else:\n", 94 | " print(\"Wrong operator.\")" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "### 点评\n", 102 | "1. 没有使用面向对象的思想\n", 103 | "2. 只满足当前需求, 不易维护, 不易扩展, 不易复用, 不够灵活\n", 104 | "\n", 105 | "### 为什么活字印刷术能位列四大发明?主要是其方法的思想。\n", 106 | "1. 文章改字方便, 可维护\n", 107 | "2. 一个字可以重复使用, 可复用\n", 108 | "3. 文章加字容易, 可扩展\n", 109 | "4. 文章改版只需移动活字, 灵活性好\n", 110 | "\n", 111 | "### 复制?复用?\n", 112 | "如果做一个带图形化界面的计算器,上边的代码需要再写一次。为了避免这样,需要将`业务逻辑`与`界面逻辑`分开,降低耦合度。\n", 113 | "\n", 114 | "### 改进版本2.0——利用封装解耦" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 18, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "input a number:12\n", 127 | "input a operater(+ - * /):+\n", 128 | "input a number:12\n", 129 | "24\n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "class Operation():\n", 135 | "\n", 136 | " def __init__(self):\n", 137 | " self.result = None\n", 138 | "\n", 139 | " def get_result(self, number_a, number_b, operator):\n", 140 | " if operator == \"+\":\n", 141 | " self.result = number_a + number_b\n", 142 | " elif operator == \"-\":\n", 143 | " self.result = number_a - number_b\n", 144 | " elif operator == \"*\":\n", 145 | " self.result = number_a * number_b\n", 146 | " elif operator == \"/\":\n", 147 | " if number_b != 0:\n", 148 | " self.result = number_a / number_b\n", 149 | " else:\n", 150 | " print(\"With operator '/', the second number can not be zero.\")\n", 151 | " else:\n", 152 | " print(\"Wrong operator.\")\n", 153 | " return self.result\n", 154 | "\n", 155 | "number_a = int(input(\"input a number:\"))\n", 156 | "operator = str(input(\"input a operater(+ - * /):\"))\n", 157 | "number_b = int(input(\"input a number:\"))\n", 158 | "\n", 159 | "operation = Operation()\n", 160 | "print(operation.get_result(number_a, number_b, operator))" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "### 点评\n", 168 | "1. 仅仅用到了封装, 还没用到继承和多态。\n", 169 | "\n", 170 | "### 紧耦合?松耦合?\n", 171 | "如果要支持一个开根号运算,上边的代码需要改动包括加减乘除在内的`get_result`函数,应该将加减乘除运算分离, 修改其中一个不影响其他的几个。那么就需要定义一个Operator基类, 将`get_result`定义为虚函数,然后通过继承和多态,分别实现加减乘除四个子类,每个子类中定义虚函数的实现逻辑。\n", 172 | "\n", 173 | "参考资料: [Python中的多态与虚函数](https://blog.csdn.net/Tony_Wong/article/details/39638887)\n", 174 | "\n", 175 | "## 改进版本3.0——简单工厂模式" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 17, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "from abc import ABCMeta, abstractmethod\n", 185 | "\n", 186 | "\n", 187 | "class Operation():\n", 188 | " __metaclass__ = ABCMeta\n", 189 | "\n", 190 | " def __init__(self):\n", 191 | " self.result = None\n", 192 | "\n", 193 | " @abstractmethod\n", 194 | " def get_result(self):\n", 195 | " pass\n", 196 | "\n", 197 | " \n", 198 | "class AddOperation(Operation):\n", 199 | " \n", 200 | " def get_result(self, number_a, number_b):\n", 201 | " self.result = number_a + number_b\n", 202 | " return self.result\n", 203 | "\n", 204 | " \n", 205 | "class SubOperation(Operation):\n", 206 | "\n", 207 | " def get_result(self, number_a, number_b):\n", 208 | " self.result = number_a - number_b\n", 209 | " return self.result\n", 210 | "\n", 211 | " \n", 212 | "class MulOperation(Operation):\n", 213 | "\n", 214 | " def get_result(self, number_a, number_b):\n", 215 | " self.result = number_a * number_b\n", 216 | " return self.result\n", 217 | " \n", 218 | " \n", 219 | "class DivOperation(Operation):\n", 220 | "\n", 221 | " def get_result(self, number_a, number_b):\n", 222 | " if number_b == 0:\n", 223 | " print(\"With operator '/', the second number can not be zero.\")\n", 224 | " return self.result\n", 225 | " self.result = number_a / number_b\n", 226 | " return self.result" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "### 如何实例化?——简单工厂模式\n", 234 | "现在加减乘除的实现逻辑已经进一步隔离,之后即使增加一个开根号运算符,也和加减乘除无关。那么如何去实例化这些类呢?可以用`简单工厂模式`。" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 21, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "class OperationFactory():\n", 244 | " \n", 245 | " @classmethod\n", 246 | " def create_operate(self, operator):\n", 247 | " oper = None\n", 248 | " if operator == \"+\":\n", 249 | " oper = AddOperation()\n", 250 | " elif operator == \"-\":\n", 251 | " oper = SubOperation()\n", 252 | " elif operator == \"*\":\n", 253 | " oper = MulOperation()\n", 254 | " elif operator == \"/\":\n", 255 | " oper = DivOperation()\n", 256 | " else:\n", 257 | " print(\"Wrong operator.\")\n", 258 | " return oper" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "通过上边的简单工厂,输入运算符号,就可以实例化出对应的对象。下边是客户端的代码。" 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 23, 271 | "metadata": {}, 272 | "outputs": [ 273 | { 274 | "name": "stdout", 275 | "output_type": "stream", 276 | "text": [ 277 | "input a number:12\n", 278 | "input a operater(+ - * /):-\n", 279 | "input a number:12\n", 280 | "0\n" 281 | ] 282 | } 283 | ], 284 | "source": [ 285 | "number_a = int(input(\"input a number:\"))\n", 286 | "operator = str(input(\"input a operater(+ - * /):\"))\n", 287 | "number_b = int(input(\"input a number:\"))\n", 288 | "\n", 289 | "oper = OperationFactory.create_operate(operator)\n", 290 | "print(oper.get_result(number_a, number_b))" 291 | ] 292 | }, 293 | { 294 | "cell_type": "markdown", 295 | "metadata": {}, 296 | "source": [ 297 | "### 点评\n", 298 | "1. 业务逻辑与界面逻辑隔离,不关心是控制台程序还是GUI程序\n", 299 | "2. 不同运算逻辑隔离,一个运算符的增删改操作不会影响其他运算\n", 300 | "3. 面向对象思想的封装,继承,多态都有所体现\n", 301 | "4. 易维护,易扩展,易复用" 302 | ] 303 | } 304 | ], 305 | "metadata": { 306 | "kernelspec": { 307 | "display_name": "Python 3", 308 | "language": "python", 309 | "name": "python3" 310 | }, 311 | "language_info": { 312 | "codemirror_mode": { 313 | "name": "ipython", 314 | "version": 3 315 | }, 316 | "file_extension": ".py", 317 | "mimetype": "text/x-python", 318 | "name": "python", 319 | "nbconvert_exporter": "python", 320 | "pygments_lexer": "ipython3", 321 | "version": "3.6.4" 322 | } 323 | }, 324 | "nbformat": 4, 325 | "nbformat_minor": 2 326 | } 327 | -------------------------------------------------------------------------------- /第20章 挨个买票——迭代器模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | " \n", 9 | "## 迭代器模式\n", 10 | "\n", 11 | "迭代器模式,提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示[DP]。\n", 12 | "\n", 13 | "当需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑使用迭代器模式。\n", 14 | "\n", 15 | "当需要对聚集有多种方式遍历时,也可以考虑使用迭代器模式。\n", 16 | "\n", 17 | "迭代器为遍历不同的聚集结构提供如开始,下一个,是否结束,当前哪一项等统一接口。" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 6, 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "aa 买车票\n", 30 | "bb 买车票\n", 31 | "cc 买车票\n", 32 | "dd 买车票\n", 33 | "ee 买车票\n" 34 | ] 35 | } 36 | ], 37 | "source": [ 38 | "from abc import ABCMeta, abstractmethod\n", 39 | "\n", 40 | "\n", 41 | "class Iterator():\n", 42 | " \"\"\"\n", 43 | " 迭代器抽象类,定义得到开始对象,得到下一对象,判断是否结尾,得到当前对象等方法\n", 44 | " \"\"\"\n", 45 | " __metaclass__ = ABCMeta\n", 46 | " \n", 47 | " @abstractmethod\n", 48 | " def first(self):\n", 49 | " pass\n", 50 | " \n", 51 | " @abstractmethod\n", 52 | " def next(self):\n", 53 | " pass\n", 54 | " \n", 55 | " @abstractmethod\n", 56 | " def is_done(self):\n", 57 | " pass\n", 58 | " \n", 59 | " @abstractmethod\n", 60 | " def current_item(self):\n", 61 | " pass\n", 62 | " \n", 63 | " \n", 64 | "class Aggregate():\n", 65 | " \"\"\"\n", 66 | " 聚集抽象类\n", 67 | " \"\"\"\n", 68 | " __metaclass__ = ABCMeta\n", 69 | " \n", 70 | " @abstractmethod\n", 71 | " def create_iterator(self):\n", 72 | " pass\n", 73 | " \n", 74 | " \n", 75 | "class ConcreteIterator(Iterator):\n", 76 | " \"\"\"\n", 77 | " 具体迭代器类\n", 78 | " \"\"\"\n", 79 | " def __init__(self, aggregate):\n", 80 | " # 定义一个具体的聚集对象,初始化时将具体的聚集对象传入\n", 81 | " self.aggregate = aggregate\n", 82 | " self.current = 0\n", 83 | " \n", 84 | " def first(self):\n", 85 | " # 得到聚集的第一个对象\n", 86 | " return self.aggregate.get_value(0)\n", 87 | " \n", 88 | " def next(self):\n", 89 | " # 得到聚集的下一个对象\n", 90 | " ret = None\n", 91 | " self.current += 1\n", 92 | " if self.current < self.aggregate.length:\n", 93 | " ret = self.aggregate.get_value(self.current)\n", 94 | " return ret\n", 95 | " \n", 96 | " def is_done(self):\n", 97 | " return True if self.current >= self.aggregate.length else False\n", 98 | " \n", 99 | " def current_item(self):\n", 100 | " return self.aggregate.get_value(self.current)\n", 101 | " \n", 102 | " \n", 103 | "class ConcreteAggregate(Aggregate):\n", 104 | " \"\"\"\n", 105 | " 具体聚集类\n", 106 | " \"\"\"\n", 107 | " def __init__(self):\n", 108 | " self.list = []\n", 109 | " self.length = 0\n", 110 | " \n", 111 | " def create_iterator(self):\n", 112 | " return ConcreteIterator(self)\n", 113 | " \n", 114 | " def create_iterator_desc(self):\n", 115 | " return ConcreteIteratorDesc(self)\n", 116 | " \n", 117 | " def insert_value(self, value):\n", 118 | " self.list.append(value)\n", 119 | " self.length += 1\n", 120 | " \n", 121 | " def get_value(self, index):\n", 122 | " return self.list[index]\n", 123 | "\n", 124 | " \n", 125 | "def main():\n", 126 | " agg = ConcreteAggregate()\n", 127 | " agg.insert_value(\"aa\")\n", 128 | " agg.insert_value(\"bb\")\n", 129 | " agg.insert_value(\"cc\")\n", 130 | " agg.insert_value(\"dd\")\n", 131 | " agg.insert_value(\"ee\")\n", 132 | " \n", 133 | " i = agg.create_iterator()\n", 134 | " \n", 135 | " item = i.first()\n", 136 | " while i.is_done() == False:\n", 137 | " print(\"{} 买车票\".format(i.current_item()))\n", 138 | " i.next()\n", 139 | " \n", 140 | "main()" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "### 逆序遍历" 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": 7, 153 | "metadata": {}, 154 | "outputs": [ 155 | { 156 | "name": "stdout", 157 | "output_type": "stream", 158 | "text": [ 159 | "ee 买车票\n", 160 | "dd 买车票\n", 161 | "cc 买车票\n", 162 | "bb 买车票\n", 163 | "aa 买车票\n" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "class ConcreteIteratorDesc(Iterator):\n", 169 | " \"\"\"\n", 170 | " 具体迭代器类,逆序遍历\n", 171 | " \"\"\"\n", 172 | " def __init__(self, aggregate):\n", 173 | " # 定义一个具体的聚集对象,初始化时将具体的聚集对象传入\n", 174 | " self.aggregate = aggregate\n", 175 | " self.current = self.aggregate.length-1\n", 176 | " \n", 177 | " def first(self):\n", 178 | " # 得到聚集的第一个对象\n", 179 | " return self.aggregate.get_value(self.aggregate.length-1)\n", 180 | " \n", 181 | " def next(self):\n", 182 | " # 得到聚集的下一个对象\n", 183 | " ret = None\n", 184 | " self.current -= 1\n", 185 | " if self.current >= 0:\n", 186 | " ret = self.aggregate.get_value(self.current)\n", 187 | " return ret\n", 188 | " \n", 189 | " def is_done(self):\n", 190 | " return True if self.current < 0 else False\n", 191 | " \n", 192 | " def current_item(self):\n", 193 | " return self.aggregate.get_value(self.current)\n", 194 | " \n", 195 | " \n", 196 | "def main():\n", 197 | " agg = ConcreteAggregate()\n", 198 | " agg.insert_value(\"aa\")\n", 199 | " agg.insert_value(\"bb\")\n", 200 | " agg.insert_value(\"cc\")\n", 201 | " agg.insert_value(\"dd\")\n", 202 | " agg.insert_value(\"ee\")\n", 203 | " \n", 204 | " i = agg.create_iterator_desc()\n", 205 | " \n", 206 | " item = i.first()\n", 207 | " while i.is_done() == False:\n", 208 | " print(\"{} 买车票\".format(i.current_item()))\n", 209 | " i.next()\n", 210 | " \n", 211 | "main()" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": {}, 217 | "source": [ 218 | "### 点评\n", 219 | "总的来说,迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明的访问集合内部的数据。" 220 | ] 221 | } 222 | ], 223 | "metadata": { 224 | "kernelspec": { 225 | "display_name": "Python 3", 226 | "language": "python", 227 | "name": "python3" 228 | }, 229 | "language_info": { 230 | "codemirror_mode": { 231 | "name": "ipython", 232 | "version": 3 233 | }, 234 | "file_extension": ".py", 235 | "mimetype": "text/x-python", 236 | "name": "python", 237 | "nbconvert_exporter": "python", 238 | "pygments_lexer": "ipython3", 239 | "version": "3.6.4" 240 | } 241 | }, 242 | "nbformat": 4, 243 | "nbformat_minor": 2 244 | } 245 | -------------------------------------------------------------------------------- /第22章 手机型号&软件版本——桥接模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 紧耦合程序演化\n", 10 | "\n", 11 | "### 题目1\n", 12 | "\n", 13 | "编程模拟以下情景,有一个N品牌手机,在上边玩一个小游戏。" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "运行N品牌手机游戏\n" 26 | ] 27 | } 28 | ], 29 | "source": [ 30 | "class HandsetNGame():\n", 31 | " \n", 32 | " def run(self):\n", 33 | " print(\"运行N品牌手机游戏\")\n", 34 | " \n", 35 | "def main():\n", 36 | " game = HandsetNGame()\n", 37 | " game.run()\n", 38 | " \n", 39 | "main()" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "### 题目2\n", 47 | "\n", 48 | "现在又有一个M品牌的手机,也有小游戏,客户端也可以调用,需要如何改?" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 2, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "from abc import ABCMeta, abstractmethod\n", 58 | "\n", 59 | "\n", 60 | "class HandsetGame():\n", 61 | " __metaclass__ = ABCMeta\n", 62 | " \n", 63 | " @abstractmethod\n", 64 | " def run(self):\n", 65 | " pass\n", 66 | " \n", 67 | "\n", 68 | "class HandsetNGame(HandsetGame):\n", 69 | " \n", 70 | " def run(self):\n", 71 | " print(\"运行N品牌手机游戏\")\n", 72 | " \n", 73 | " \n", 74 | "class HandsetMGame(HandsetGame):\n", 75 | " \n", 76 | " def run(self):\n", 77 | " print(\"运行M品牌手机游戏\")" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "### 点评\n", 85 | "\n", 86 | "现在不同的手机又要增加通讯录功能,又要如何改?\n", 87 | "\n", 88 | "那就意味着父类应该是“手机品牌”,下有“手机品牌M”和“手机品牌N”,每个子类下边又各有“通讯录”和“小游戏”。从上到下是个1-2-4的层级结构。\n", 89 | "\n", 90 | "如果这样的话,那增加一款新的软件,需要在每个手机品牌下边都实现一次,如果新增加一款手机品牌,又需要把所有软件都重写一次。\n", 91 | "\n", 92 | "### 继承不一定好?\n", 93 | "\n", 94 | "对象的继承关系在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化都必然会导致子类发生变化。但你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或者被其他更合适的类替换。这种依赖关系限制了灵活性并最终限制了复用性[DP]。\n", 95 | "\n", 96 | "## 合成/聚合复用原则\n", 97 | "\n", 98 | "合成/聚合复用原则,尽量使用合成/聚合,尽量不要使用类继承。\n", 99 | "\n", 100 | "聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分,就如同“大雁”和“雁群”;\n", 101 | "\n", 102 | "合成表示一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样,就如同“吃饱”和“大雁”;\n", 103 | "\n", 104 | "优先使用对象的合成/聚合将有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物[DP]。\n", 105 | "\n", 106 | "像“游戏”,“通讯录”等功能都是软件,我们应该让其与手机解耦,也就是再弄一个“手机软件”抽象类。\n", 107 | "\n", 108 | "## 松耦合的程序" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 4, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | "on handset brand N\n", 121 | "run handset game\n", 122 | "on handset brand N\n", 123 | "run handset address list\n", 124 | "on handset brand M\n", 125 | "run handset game\n", 126 | "on handset brand M\n", 127 | "run handset address list\n" 128 | ] 129 | } 130 | ], 131 | "source": [ 132 | "from abc import ABCMeta, abstractmethod\n", 133 | "\n", 134 | "\n", 135 | "class HandsetSoft():\n", 136 | " __metaclass__ = ABCMeta\n", 137 | " \n", 138 | " @abstractmethod\n", 139 | " def run(self):\n", 140 | " pass\n", 141 | " \n", 142 | " \n", 143 | "class HandsetGame(HandsetSoft):\n", 144 | " \n", 145 | " def run(self):\n", 146 | " print(\"run handset game\")\n", 147 | " \n", 148 | " \n", 149 | "class HandsetAddressList(HandsetSoft):\n", 150 | " \n", 151 | " def run(self):\n", 152 | " print(\"run handset address list\")\n", 153 | " \n", 154 | " \n", 155 | "class HandsetBrand():\n", 156 | " __metaclass__ = ABCMeta\n", 157 | " \n", 158 | " @abstractmethod\n", 159 | " def run(slef):\n", 160 | " pass\n", 161 | " \n", 162 | " def set_handset_soft(self, soft):\n", 163 | " self.soft = soft\n", 164 | " \n", 165 | " \n", 166 | "class HandsetBrandN(HandsetBrand):\n", 167 | " \n", 168 | " def run(self):\n", 169 | " print(\"on handset brand N\")\n", 170 | " self.soft.run()\n", 171 | " \n", 172 | " \n", 173 | "class HandsetBrandM(HandsetBrand):\n", 174 | " \n", 175 | " def run(self):\n", 176 | " print(\"on handset brand M\")\n", 177 | " self.soft.run()\n", 178 | " \n", 179 | " \n", 180 | "def main():\n", 181 | " brand = HandsetBrandN()\n", 182 | " brand.set_handset_soft(HandsetGame())\n", 183 | " brand.run()\n", 184 | " \n", 185 | " brand.set_handset_soft(HandsetAddressList())\n", 186 | " brand.run()\n", 187 | " \n", 188 | " brand = HandsetBrandM()\n", 189 | " brand.set_handset_soft(HandsetGame())\n", 190 | " brand.run()\n", 191 | " \n", 192 | " brand.set_handset_soft(HandsetAddressList())\n", 193 | " brand.run()\n", 194 | " \n", 195 | "main()" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "### 点评\n", 203 | "现在如果要增加音乐播放器,那么只要增加这个类就行,不影响其他任何类。\n", 204 | "\n", 205 | "```python\n", 206 | "class HandsetPlayer(HandsetSoft):\n", 207 | " \n", 208 | " def run(self):\n", 209 | " print(\"run handset player\")\n", 210 | "```\n", 211 | "现在如果要增加手机品牌S,也只需要增加一个品牌子类即可。\n", 212 | "\n", 213 | "这显然也符合了我们之前的开放-封闭原则,这样的设计显然不会修改原来的代码,只要扩展类即可。\n", 214 | "\n", 215 | "继承也是一种强耦合的结构,所以优先使用对象的合成或聚合,而不是类继承。\n", 216 | "\n", 217 | "像“手机软件”和“手机品牌”两个抽象类之间进行关联,使得它们的实现部分互相分类,就好像一座桥一样,这就是桥接模式。\n", 218 | "\n", 219 | "## 桥接模式\n", 220 | "\n", 221 | "桥接模式,将抽象部分与它的实现部分分离,使它们可以独立地变化。[DP]\n", 222 | "\n", 223 | "将抽象部分和实现部分分离,并不是指让抽象类和派生类分离,而是指抽象类和它的派生类用来实现自己的对象。就像刚刚的手机品牌&手机软件之间的关系,既可以按照品牌进行实现,又可以按照软件分类进行实现。由于实现的方式有多种,桥接模式的核心意图就是把这些实现独立出来,让它们各自的变化。这使得每种实现的变化都不会影响其他实现,从而达到应对变化的目的。\n", 224 | "\n", 225 | "面对真实需求,实现系统可能有多角度分类,每一种分类都可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。\n", 226 | "\n", 227 | "在我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该考虑使用桥接模式了。\n", 228 | "\n", 229 | "只要真正深入理解设计原则,很多设计模式其实就是原则的应用而已,或许在不经意间就在使用设计模式了。" 230 | ] 231 | } 232 | ], 233 | "metadata": { 234 | "kernelspec": { 235 | "display_name": "Python 3", 236 | "language": "python", 237 | "name": "python3" 238 | }, 239 | "language_info": { 240 | "codemirror_mode": { 241 | "name": "ipython", 242 | "version": 3 243 | }, 244 | "file_extension": ".py", 245 | "mimetype": "text/x-python", 246 | "name": "python", 247 | "nbconvert_exporter": "python", 248 | "pygments_lexer": "ipython3", 249 | "version": "3.6.4" 250 | } 251 | }, 252 | "nbformat": 4, 253 | "nbformat_minor": 2 254 | } 255 | -------------------------------------------------------------------------------- /第23章 烤串的哲学——命令模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | " \n", 9 | "## 题目1\n", 10 | "用程序模拟,顾客直接向烤串师傅提需求。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "烤羊肉串\n", 23 | "烤羊肉串\n", 24 | "烤羊肉串\n", 25 | "烤鸡翅\n", 26 | "烤羊肉串\n", 27 | "烤羊肉串\n", 28 | "烤鸡翅\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "class Barbecuer():\n", 34 | " \n", 35 | " def bake_mutton(self):\n", 36 | " print(\"烤羊肉串\")\n", 37 | " \n", 38 | " def bake_chicken_wing(self):\n", 39 | " print(\"烤鸡翅\")\n", 40 | " \n", 41 | " \n", 42 | "def main():\n", 43 | " boy = Barbecuer()\n", 44 | " boy.bake_mutton()\n", 45 | " boy.bake_mutton()\n", 46 | " boy.bake_mutton()\n", 47 | " boy.bake_chicken_wing()\n", 48 | " boy.bake_mutton()\n", 49 | " boy.bake_mutton()\n", 50 | " boy.bake_chicken_wing()\n", 51 | " \n", 52 | "main()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "### 点评\n", 60 | "客户端程序与“烤串师傅”紧耦合,尽管简单,但是极为僵化,当顾客多了,请求多了,就容易乱了。\n", 61 | "\n", 62 | "## 题目2\n", 63 | "用程序模拟,顾客向服务员提需求,服务员再告知烤串师傅。" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 4, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "增加订单: 烤羊肉串\n", 76 | "增加订单: 烤羊肉串\n", 77 | "鸡翅没了,换点其他的吧\n", 78 | "烤羊肉串\n", 79 | "烤羊肉串\n" 80 | ] 81 | } 82 | ], 83 | "source": [ 84 | "from abc import ABCMeta, abstractmethod\n", 85 | "\n", 86 | "\n", 87 | "class Command():\n", 88 | " \"\"\"\n", 89 | " 抽象命令类\n", 90 | " \"\"\"\n", 91 | " __metaclass__ = ABCMeta\n", 92 | " \n", 93 | " # 需要确定一个命令接收者\n", 94 | " def __init__(self, receiver):\n", 95 | " self.receiver = receiver\n", 96 | " \n", 97 | " @abstractmethod\n", 98 | " def excute_command(self):\n", 99 | " pass\n", 100 | " \n", 101 | " \n", 102 | "class BakeMuttonCommand(Command):\n", 103 | " \"\"\"\n", 104 | " 具体命令类\n", 105 | " \"\"\"\n", 106 | " def excute_command(self):\n", 107 | " self.receiver.bake_mutton()\n", 108 | " \n", 109 | " def to_string(self):\n", 110 | " return \"烤羊肉串\"\n", 111 | " \n", 112 | "\n", 113 | "class BakeChickenWingCommand(Command):\n", 114 | " \"\"\"\n", 115 | " 具体命令类\n", 116 | " \"\"\"\n", 117 | " def excute_command(self):\n", 118 | " self.receiver.bake_chicken_wing()\n", 119 | " \n", 120 | " def to_string(self):\n", 121 | " return \"烤鸡翅\"\n", 122 | " \n", 123 | " \n", 124 | "class Waiter():\n", 125 | " \"\"\"\n", 126 | " 服务员类, 不用管顾客的烤串要怎么烤,对于服务员来说,都当作命令记录下来就行,然后通知“烤串师傅”执行即可。\n", 127 | " \"\"\"\n", 128 | " def __init__(self):\n", 129 | " self.orders = []\n", 130 | " \n", 131 | " def set_order(self, cmd):\n", 132 | " if cmd.to_string() == \"烤鸡翅\":\n", 133 | " print(\"鸡翅没了,换点其他的吧\")\n", 134 | " else:\n", 135 | " self.orders.append(cmd)\n", 136 | " print(\"增加订单:\", cmd.to_string())\n", 137 | " \n", 138 | " def cancel_order(self, cmd):\n", 139 | " self.orders.remove(cmd)\n", 140 | " print(\"取消订单:\", cmd.to_string())\n", 141 | " \n", 142 | " def notify(self):\n", 143 | " for cmd in self.orders:\n", 144 | " cmd.excute_command()\n", 145 | " \n", 146 | "\n", 147 | "def main():\n", 148 | " # 开店准备\n", 149 | " boy = Barbecuer()\n", 150 | " bake_mutton_1 = BakeMuttonCommand(boy)\n", 151 | " bake_mutton_2 = BakeMuttonCommand(boy)\n", 152 | " bake_chicken_wing = BakeChickenWingCommand(boy)\n", 153 | " girl = Waiter()\n", 154 | " \n", 155 | " # 开门营业\n", 156 | " girl.set_order(bake_mutton_1)\n", 157 | " girl.set_order(bake_mutton_2)\n", 158 | " girl.set_order(bake_chicken_wing)\n", 159 | " \n", 160 | " # 开始制作\n", 161 | " girl.notify()\n", 162 | " \n", 163 | "main()" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "## 命令模式\n", 171 | "\n", 172 | "命令模式,讲一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销操作。[DP]\n", 173 | "\n", 174 | "主要包括以下几种类:\n", 175 | "\n", 176 | "- Command类,用来声明执行操作的接口,每个Command类都需要绑定一个命令接收者(执行者);\n", 177 | "- ConcreteCommand类,将一个命令接收者对象绑定与一个动作,调用接收者相应的操作,以实现Execute;\n", 178 | "- Invoder类,比如服务员,维护命令队列,发起执行命令的请求;\n", 179 | "- Receiver类,知道如何实施执行一个与请求相关的操作,任何类都可能作为一个接收者;\n", 180 | "\n", 181 | "命令模式的优点:\n", 182 | "\n", 183 | "- 它能较容易的设计一个命令队列;\n", 184 | "- 在需要的情况下,可以较容易的将命令记入日志;\n", 185 | "- 允许接收请求的一方决定是否要否决请求;\n", 186 | "- 可以容易的实现对请求的撤销和崇左;\n", 187 | "- 加进新的具体命令类不影响其他的类;\n", 188 | "- 把[请求一个操作的对象]和[知道怎么执行一个操作的对象]隔离;" 189 | ] 190 | } 191 | ], 192 | "metadata": { 193 | "kernelspec": { 194 | "display_name": "Python 3", 195 | "language": "python", 196 | "name": "python3" 197 | }, 198 | "language_info": { 199 | "codemirror_mode": { 200 | "name": "ipython", 201 | "version": 3 202 | }, 203 | "file_extension": ".py", 204 | "mimetype": "text/x-python", 205 | "name": "python", 206 | "nbconvert_exporter": "python", 207 | "pygments_lexer": "ipython3", 208 | "version": "3.6.4" 209 | } 210 | }, 211 | "nbformat": 4, 212 | "nbformat_minor": 2 213 | } 214 | -------------------------------------------------------------------------------- /第24章 加薪审批——职责链模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目\n", 10 | "用程序模拟以下情景\n", 11 | "- 员工向经理发起加薪申请,经理无权决定,需要向总监汇报,加薪额度超过总监权力范围,需要向总经理汇报; \n", 12 | "- 员工还可以提交请加申请,经理可以决定2天以下的假,总监可以决定5天以下的假,其余都要上报总经理;\n", 13 | "\n", 14 | "## 基础版本" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [ 22 | { 23 | "name": "stdout", 24 | "output_type": "stream", 25 | "text": [ 26 | "经理:贺贺请求加薪 数量 1000 我无权处理\n", 27 | "总监:贺贺请求加薪 数量 1000 我无权处理\n", 28 | "总经理:贺贺请求加薪 数量 1000 再说吧\n", 29 | "经理:贺贺请求请假 数量 3 我无权处理\n", 30 | "总监:贺贺请求请假 数量 3 被批准\n", 31 | "总经理:贺贺请求请假 数量 3 被批准\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "class Request():\n", 37 | " \n", 38 | " def __init__(self):\n", 39 | " self.type = None # 申请类型\n", 40 | " self.content = None # 申请内容\n", 41 | " self.number = 0 # 申请数量\n", 42 | " \n", 43 | " \n", 44 | "class Manager():\n", 45 | " \n", 46 | " def __init__(self, name):\n", 47 | " self.name = name\n", 48 | " \n", 49 | " def get_result(self, manager_level, request):\n", 50 | " if manager_level == \"经理\":\n", 51 | " if request.type == \"请假\" and request.number <=2:\n", 52 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 53 | " else:\n", 54 | " print(\"{}:{} 数量 {} 我无权处理\".format(self.name, request.content, request.number))\n", 55 | " elif manager_level == \"总监\":\n", 56 | " if request.type == \"请假\" and request.number <=5:\n", 57 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 58 | " else:\n", 59 | " print(\"{}:{} 数量 {} 我无权处理\".format(self.name, request.content, request.number))\n", 60 | " elif manager_level == \"总经理\":\n", 61 | " if request.type == \"请假\":\n", 62 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 63 | " elif request.type == \"加薪\" and request.number <= 500:\n", 64 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 65 | " elif request.type == \"加薪\" and request.number > 500:\n", 66 | " print(\"{}:{} 数量 {} 再说吧\".format(self.name, request.content, request.number))\n", 67 | " \n", 68 | "def main():\n", 69 | " jingli = Manager(\"经理\")\n", 70 | " zongjian = Manager(\"总监\")\n", 71 | " zongjingli = Manager(\"总经理\")\n", 72 | " \n", 73 | " request = Request()\n", 74 | " request.type = \"加薪\"\n", 75 | " request.content = \"贺贺请求加薪\"\n", 76 | " request.number = 1000\n", 77 | " \n", 78 | " jingli.get_result(jingli.name, request)\n", 79 | " zongjian.get_result(zongjian.name, request)\n", 80 | " zongjingli.get_result(zongjingli.name, request)\n", 81 | " \n", 82 | " request.type = \"请假\"\n", 83 | " request.content = \"贺贺请求请假\"\n", 84 | " request.number = 3\n", 85 | " \n", 86 | " jingli.get_result(jingli.name, request)\n", 87 | " zongjian.get_result(zongjian.name, request)\n", 88 | " zongjingli.get_result(zongjingli.name, request)\n", 89 | " \n", 90 | "main()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### 点评\n", 98 | "- Manager类的get_result方法比较长,有太多的分支判断,不是好的设计;\n", 99 | "- Mangeer类有太多的责任,违背了单一职责的原则,增加新的管理者,需要修改这个类,违背了开放封闭原则;\n", 100 | "\n", 101 | "## 职责链模式\n", 102 | "职责链模式,使得多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止[DP]。\n", 103 | "\n", 104 | "### 职责链模式基本结构" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 3, 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "handler1 handle request [ 2 ]\n", 117 | "handler1 handle request [ 5 ]\n", 118 | "handler2 handle request [ 14 ]\n", 119 | "handler3 handle request [ 22 ]\n", 120 | "handler2 handle request [ 18 ]\n", 121 | "handler1 handle request [ 3 ]\n", 122 | "handler3 handle request [ 27 ]\n", 123 | "handler3 handle request [ 20 ]\n" 124 | ] 125 | } 126 | ], 127 | "source": [ 128 | "from abc import ABCMeta, abstractmethod\n", 129 | "\n", 130 | "\n", 131 | "class Handler():\n", 132 | " __metaclass__ = ABCMeta\n", 133 | " \n", 134 | " def __init__(self):\n", 135 | " self.successor = None # 设置继任者\n", 136 | " \n", 137 | " @abstractmethod\n", 138 | " def handle_request(self, request):\n", 139 | " pass\n", 140 | " \n", 141 | " \n", 142 | "class ConcretHandler1(Handler):\n", 143 | " \"\"\"\n", 144 | " 处理0-10的请求\n", 145 | " \"\"\"\n", 146 | " def handle_request(self, request):\n", 147 | " if request >=0 and request < 10:\n", 148 | " print(\"handler1 handle request [ {} ]\".format(request))\n", 149 | " elif self.successor != None: # 由继任者处理请求\n", 150 | " self.successor.handle_request(request)\n", 151 | " \n", 152 | "\n", 153 | "class ConcretHandler2(Handler):\n", 154 | " \"\"\"\n", 155 | " 处理10-20的请求\n", 156 | " \"\"\"\n", 157 | " def handle_request(self, request):\n", 158 | " if request >=10 and request < 20:\n", 159 | " print(\"handler2 handle request [ {} ]\".format(request))\n", 160 | " elif self.successor != None: # 由继任者处理请求\n", 161 | " self.successor.handle_request(request)\n", 162 | " \n", 163 | "\n", 164 | "class ConcretHandler3(Handler):\n", 165 | " \"\"\"\n", 166 | " 处理20-30的请求\n", 167 | " \"\"\"\n", 168 | " def handle_request(self, request):\n", 169 | " if request >=20 and request < 30:\n", 170 | " print(\"handler3 handle request [ {} ]\".format(request))\n", 171 | " elif self.successor != None: # 由继任者处理请求\n", 172 | " self.successor.handle_request(request)\n", 173 | "\n", 174 | "\n", 175 | "def main():\n", 176 | " h1 = ConcretHandler1()\n", 177 | " h2 = ConcretHandler2()\n", 178 | " h3 = ConcretHandler3()\n", 179 | " \n", 180 | " h1.successor = h2\n", 181 | " h2.successor = h3\n", 182 | " \n", 183 | " requests = [2, 5, 14, 22, 18, 3, 27, 20]\n", 184 | " \n", 185 | " for i in requests:\n", 186 | " h1.handle_request(i)\n", 187 | " \n", 188 | "main()" 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "metadata": {}, 194 | "source": [ 195 | "### 点评\n", 196 | "- 当客户提交一个请求时,请求是沿着职责链传递直至有一个ConcretHandler对象负责处理它\n", 197 | "- 接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构\n", 198 | "- 职责链可以简化对象的相互链接,它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接收者的引用\n", 199 | "- 可以随时增加或修改处理一个请求的结构,增强了给对象指派职责的灵活性\n", 200 | "- 一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得不到处理\n", 201 | "- 最重要的两点\n", 202 | " - 需要实现给每个具体管理者设置它的上司,也就是它的后继者\n", 203 | " - 需要在每个具体管理者类处理请求时,做出判断,是可以处理请求,还是必须“推卸责任”,转移到后继者\n", 204 | " \n", 205 | "## 职责链模式——加薪代码" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 7, 211 | "metadata": {}, 212 | "outputs": [ 213 | { 214 | "name": "stdout", 215 | "output_type": "stream", 216 | "text": [ 217 | "总经理:贺贺请求加薪 数量 1000 再说吧\n", 218 | "总监:贺贺请求请假 数量 3 被批准\n" 219 | ] 220 | } 221 | ], 222 | "source": [ 223 | "from abc import ABCMeta, abstractmethod\n", 224 | "\n", 225 | "\n", 226 | "class Manager():\n", 227 | " \n", 228 | " __metaclass__ = ABCMeta\n", 229 | " \n", 230 | " def __init__(self, name):\n", 231 | " self.name = name\n", 232 | " self.successor = None\n", 233 | " \n", 234 | " @abstractmethod\n", 235 | " def handle_request(self, request):\n", 236 | " pass\n", 237 | " \n", 238 | "\n", 239 | "class CommonManager(Manager):\n", 240 | " \n", 241 | " def handle_request(self, request):\n", 242 | " if request.type == \"请假\" and request.number <=2:\n", 243 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 244 | " elif self.successor != None:\n", 245 | " self.successor.handle_request(request)\n", 246 | " \n", 247 | " \n", 248 | "class Majordomo(Manager):\n", 249 | " def handle_request(self, request):\n", 250 | " if request.type == \"请假\" and request.number <=5:\n", 251 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 252 | " elif self.successor != None:\n", 253 | " self.successor.handle_request(request)\n", 254 | "\n", 255 | " \n", 256 | "class GeneralManager(Manager):\n", 257 | " def handle_request(self, request):\n", 258 | " if request.type == \"请假\":\n", 259 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 260 | " elif request.type == \"加薪\" and request.number <= 500:\n", 261 | " print(\"{}:{} 数量 {} 被批准\".format(self.name, request.content, request.number))\n", 262 | " elif request.type == \"加薪\" and request.number > 500:\n", 263 | " print(\"{}:{} 数量 {} 再说吧\".format(self.name, request.content, request.number))\n", 264 | " \n", 265 | " \n", 266 | "def main():\n", 267 | " jingli = CommonManager(\"经理\")\n", 268 | " zongjian = Majordomo(\"总监\")\n", 269 | " zongjingli = GeneralManager(\"总经理\")\n", 270 | " \n", 271 | " jingli.successor = zongjian\n", 272 | " zongjian.successor = zongjingli\n", 273 | " \n", 274 | " request = Request()\n", 275 | " request.type = \"加薪\"\n", 276 | " request.content = \"贺贺请求加薪\"\n", 277 | " request.number = 1000\n", 278 | " \n", 279 | " jingli.handle_request(request)\n", 280 | " \n", 281 | " request.type = \"请假\"\n", 282 | " request.content = \"贺贺请求请假\"\n", 283 | " request.number = 3\n", 284 | " \n", 285 | " jingli.handle_request(request)\n", 286 | " \n", 287 | "main()" 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "metadata": {}, 293 | "source": [ 294 | "### 职责链模式和状态模式\n", 295 | "- 职责链模式,主要处理requet和handler的问题,当handler收到requet时,判断是否自己是否可以处理,处理逻辑在handler中;\n", 296 | "- 状态模式,主要处理state和context的问题,当state变化时,改变context的state,继续调用context的处理逻辑,处理逻辑在context中;" 297 | ] 298 | } 299 | ], 300 | "metadata": { 301 | "kernelspec": { 302 | "display_name": "Python 3", 303 | "language": "python", 304 | "name": "python3" 305 | }, 306 | "language_info": { 307 | "codemirror_mode": { 308 | "name": "ipython", 309 | "version": 3 310 | }, 311 | "file_extension": ".py", 312 | "mimetype": "text/x-python", 313 | "name": "python", 314 | "nbconvert_exporter": "python", 315 | "pygments_lexer": "ipython3", 316 | "version": "3.6.4" 317 | } 318 | }, 319 | "nbformat": 4, 320 | "nbformat_minor": 2 321 | } 322 | -------------------------------------------------------------------------------- /第25章 联合国维护世界和平——中介者模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 题目背景\n", 10 | "\n", 11 | "联合国在世界上就是中介者的角色,各国之间的关系复杂,类似不同的对象和对象之间的关系,这就要求对象之间需要知道其他所有对象,尽管将一个系统分割成许多对象通常可以增加其可复用性,但是对象间相互连接的激增优惠降低其可复用性。大量的连接使得一个对象不可能在没有其他对象的支持下工作,系统表现为一个不可分割的整体,所以,对系统的行为进行任何较大的改动就十分困难了。\n", 12 | "\n", 13 | "这里可以应用“迪米特法则”,如果两个类不必彼此直接通讯,那么这两个类就不应该发生直接的相互作用。如果其中一个类需要调用另一个类的某一种方法的话,可以通过第三者转发这个调用。也就是说,国与国之间的关系,完全可以通过联合国这个中介者来维持,而不必直接通信。\n", 14 | "\n", 15 | "## 中介者模式\n", 16 | "\n", 17 | "中介者模式,用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显示的相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。[DP]\n", 18 | "\n", 19 | "中介者模式主要包括以下几个类:\n", 20 | "- Colleague叫做抽象同事类,而ConcretColleague是具体同事类,每个具体同事只知道自己的行为,而不了解其他同事类的情况,但它们却都认识中介者对象;\n", 21 | "- Mediator是抽象中介者,定义了同事对象到中介者对象的接口,ConcretMediator是具体中介者对象,实现抽象类的方法,它需要知道所有具体同事类,并从具体同事接收消息,向具体同事对象发出命令。" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 1, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "name": "stdout", 31 | "output_type": "stream", 32 | "text": [ 33 | "同事2得到消息: 吃饭了吗?\n", 34 | "同事1得到消息: 还没,你请客么?\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "from abc import ABCMeta, abstractmethod\n", 40 | "\n", 41 | "\n", 42 | "class Mediator():\n", 43 | " \"\"\"\n", 44 | " 抽象中介者\n", 45 | " \"\"\"\n", 46 | " \n", 47 | " __metaclass__ = ABCMeta\n", 48 | " \n", 49 | " @abstractmethod\n", 50 | " def send(self, message, colleague):\n", 51 | " \"\"\"\n", 52 | " 定义一个抽象的发送消息方法,得到同事对象和发送消息\n", 53 | " \"\"\"\n", 54 | " pass\n", 55 | " \n", 56 | " \n", 57 | "class Colleague():\n", 58 | " \"\"\"\n", 59 | " 抽象同事类\n", 60 | " \"\"\"\n", 61 | " \n", 62 | " __metaclass__ = ABCMeta\n", 63 | " \n", 64 | " def __init__(self, mediator):\n", 65 | " \"\"\"\n", 66 | " 构造方法,得到中介者对象\n", 67 | " \"\"\"\n", 68 | " self.mediator = mediator\n", 69 | " \n", 70 | " \n", 71 | "class ConcreteMediator(Mediator):\n", 72 | " \"\"\"\n", 73 | " 具体中介者\n", 74 | " \"\"\"\n", 75 | " def __init__(self):\n", 76 | " \"\"\"\n", 77 | " 需要了解所有的具体同事对象\n", 78 | " \"\"\"\n", 79 | " self.colleague1 = None\n", 80 | " self.colleague2 = None\n", 81 | " \n", 82 | " def send(self, message, colleague):\n", 83 | " \"\"\"\n", 84 | " 重写发送消息的方法,根据对象作出选择判断,通知具体同事对象\n", 85 | " \"\"\"\n", 86 | " if colleague == self.colleague1:\n", 87 | " self.colleague2.notify(message)\n", 88 | " else:\n", 89 | " self.colleague1.notify(message)\n", 90 | " \n", 91 | " \n", 92 | "class ConcreteColleague1(Colleague):\n", 93 | " \"\"\"\n", 94 | " 具体同事类1\n", 95 | " \"\"\"\n", 96 | " def send(self, message):\n", 97 | " self.mediator.send(message, self)\n", 98 | " \n", 99 | " def notify(self, message):\n", 100 | " print(\"同事1得到消息:\",message)\n", 101 | " \n", 102 | " \n", 103 | "class ConcreteColleague2(Colleague):\n", 104 | " \"\"\"\n", 105 | " 具体同事类2\n", 106 | " \"\"\"\n", 107 | " def send(self, message):\n", 108 | " self.mediator.send(message, self)\n", 109 | " \n", 110 | " def notify(self, message):\n", 111 | " print(\"同事2得到消息:\",message)\n", 112 | " \n", 113 | " \n", 114 | "def main():\n", 115 | " m = ConcreteMediator()\n", 116 | " \n", 117 | " \"\"\"\n", 118 | " 让两个具体同事类认识中介者对象\n", 119 | " \"\"\"\n", 120 | " c1 = ConcreteColleague1(m)\n", 121 | " c2 = ConcreteColleague2(m)\n", 122 | " \n", 123 | " \"\"\"\n", 124 | " 让中介者认识各个具体同事类\n", 125 | " \"\"\"\n", 126 | " m.colleague1 = c1\n", 127 | " m.colleague2 = c2\n", 128 | " \n", 129 | " \"\"\"\n", 130 | " 具体同事类对象发送消息都是通过中介者转发\n", 131 | " \"\"\"\n", 132 | " c1.send(\"吃饭了吗?\")\n", 133 | " c2.send(\"还没,你请客么?\")\n", 134 | " \n", 135 | "main()\n", 136 | " " 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "### 点评\n", 144 | "\n", 145 | "由于有了Mediator,使得ConcreteColleague1和ConcreteColleague2在发送消息和接收消息时其实是通过中介者来完成,这就减少了它们之间的耦合度。\n", 146 | "\n", 147 | "## 题目\n", 148 | "\n", 149 | "用程序模拟,美国和伊拉克之间的对话都是通过联合国安理会作为中介来完成。" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 2, 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stdout", 159 | "output_type": "stream", 160 | "text": [ 161 | "伊拉克 得到消息: 吃饭了吗?\n", 162 | "美国 得到消息: 还没,你请客么?\n" 163 | ] 164 | } 165 | ], 166 | "source": [ 167 | "from abc import ABCMeta, abstractmethod\n", 168 | "\n", 169 | "\n", 170 | "class UnitedNations():\n", 171 | " \"\"\"\n", 172 | " 联合国机构,抽象中介者\n", 173 | " \"\"\"\n", 174 | " \n", 175 | " __metaclass__ = ABCMeta\n", 176 | " \n", 177 | " @abstractmethod\n", 178 | " def send(self, message, colleague):\n", 179 | " \"\"\"\n", 180 | " 定义一个抽象的发送消息方法,得到同事对象和发送消息\n", 181 | " \"\"\"\n", 182 | " pass\n", 183 | " \n", 184 | " \n", 185 | "class Country():\n", 186 | " \"\"\"\n", 187 | " 国家类,抽象同事类\n", 188 | " \"\"\"\n", 189 | " \n", 190 | " __metaclass__ = ABCMeta\n", 191 | " \n", 192 | " def __init__(self, mediator):\n", 193 | " \"\"\"\n", 194 | " 构造方法,得到中介者对象\n", 195 | " \"\"\"\n", 196 | " self.mediator = mediator\n", 197 | " \n", 198 | " \n", 199 | "class UnitedNationsSecurityCouncil(Mediator):\n", 200 | " \"\"\"\n", 201 | " 联合国安全理事会,具体中介者\n", 202 | " \"\"\"\n", 203 | " def __init__(self):\n", 204 | " \"\"\"\n", 205 | " 需要了解所有的具体同事对象\n", 206 | " \"\"\"\n", 207 | " self.colleague1 = None\n", 208 | " self.colleague2 = None\n", 209 | " \n", 210 | " def send(self, message, colleague):\n", 211 | " \"\"\"\n", 212 | " 重写发送消息的方法,根据对象作出选择判断,通知具体同事对象\n", 213 | " \"\"\"\n", 214 | " if colleague == self.colleague1:\n", 215 | " self.colleague2.notify(message)\n", 216 | " else:\n", 217 | " self.colleague1.notify(message)\n", 218 | " \n", 219 | " \n", 220 | "class USA(Colleague):\n", 221 | " \"\"\"\n", 222 | " 美国,具体同事类1\n", 223 | " \"\"\"\n", 224 | " def send(self, message):\n", 225 | " self.mediator.send(message, self)\n", 226 | " \n", 227 | " def notify(self, message):\n", 228 | " print(\"美国 得到消息:\",message)\n", 229 | " \n", 230 | " \n", 231 | "class Iraq(Colleague):\n", 232 | " \"\"\"\n", 233 | " 伊拉克,具体同事类2\n", 234 | " \"\"\"\n", 235 | " def send(self, message):\n", 236 | " self.mediator.send(message, self)\n", 237 | " \n", 238 | " def notify(self, message):\n", 239 | " print(\"伊拉克 得到消息:\",message)\n", 240 | " \n", 241 | " \n", 242 | "def main():\n", 243 | " m = UnitedNationsSecurityCouncil()\n", 244 | " \n", 245 | " \"\"\"\n", 246 | " 让两个具体同事类认识中介者对象\n", 247 | " \"\"\"\n", 248 | " c1 = USA(m)\n", 249 | " c2 = Iraq(m)\n", 250 | " \n", 251 | " \"\"\"\n", 252 | " 让中介者认识各个具体同事类\n", 253 | " \"\"\"\n", 254 | " m.colleague1 = c1\n", 255 | " m.colleague2 = c2\n", 256 | " \n", 257 | " \"\"\"\n", 258 | " 具体同事类对象发送消息都是通过中介者转发\n", 259 | " \"\"\"\n", 260 | " c1.send(\"吃饭了吗?\")\n", 261 | " c2.send(\"还没,你请客么?\")\n", 262 | " \n", 263 | "main()\n", 264 | " " 265 | ] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "metadata": {}, 270 | "source": [ 271 | "### 点评\n", 272 | "\n", 273 | "ConcretMediator这个类必须要知道所有ConcreteCollegue,这就使得ConcreteMediator责任太多,如果它出现问题,则整个系统都会出现问题。\n", 274 | "\n", 275 | "中介者模式很容易在系统中应用,也很容易在系统中误用。当系统出现“多对多”交互复杂的对象群时,不要急于使用中介者模式,而要先反思你的系统在设计上是否合理。\n", 276 | "\n", 277 | "中介者模式的优点:\n", 278 | "- Mediator的出现减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague类和Mediator;\n", 279 | "- 由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。\n", 280 | "\n", 281 | "中介者模式的缺点:\n", 282 | "- 由于ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague都复杂。" 283 | ] 284 | } 285 | ], 286 | "metadata": { 287 | "kernelspec": { 288 | "display_name": "Python 3", 289 | "language": "python", 290 | "name": "python3" 291 | }, 292 | "language_info": { 293 | "codemirror_mode": { 294 | "name": "ipython", 295 | "version": 3 296 | }, 297 | "file_extension": ".py", 298 | "mimetype": "text/x-python", 299 | "name": "python", 300 | "nbconvert_exporter": "python", 301 | "pygments_lexer": "ipython3", 302 | "version": "3.6.4" 303 | } 304 | }, 305 | "nbformat": 4, 306 | "nbformat_minor": 2 307 | } 308 | -------------------------------------------------------------------------------- /第26章 千人千面,内在共享——享元模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 背景\n", 10 | "\n", 11 | "有6个客户想做产品展示网站,其中3个想做成天猫商城那样的“电商风格”展示页面,其中3个想做成博客园那样的“博客风格”展示博客。应该如何实现?" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "网站风格: 电商风格\n", 24 | "网站风格: 电商风格\n", 25 | "网站风格: 电商风格\n", 26 | "网站风格: 博客风格\n", 27 | "网站风格: 博客风格\n", 28 | "网站风格: 博客风格\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "class WebSite():\n", 34 | " \n", 35 | " def __init__(self, name):\n", 36 | " self.name = name\n", 37 | " \n", 38 | " def use(self):\n", 39 | " print(\"网站风格:\", self.name)\n", 40 | " \n", 41 | "def main():\n", 42 | " web1 = WebSite(\"电商风格\")\n", 43 | " web1.use()\n", 44 | " \n", 45 | " web2 = WebSite(\"电商风格\")\n", 46 | " web2.use()\n", 47 | " \n", 48 | " web3 = WebSite(\"电商风格\")\n", 49 | " web3.use()\n", 50 | " \n", 51 | " web4 = WebSite(\"博客风格\")\n", 52 | " web4.use()\n", 53 | " \n", 54 | " web5 = WebSite(\"博客风格\")\n", 55 | " web5.use()\n", 56 | " \n", 57 | " web6 = WebSite(\"博客风格\")\n", 58 | " web6.use()\n", 59 | " \n", 60 | "main()" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "### 点评\n", 68 | "\n", 69 | "根据上边的代码,如果要做三个“电商风格”,三个“博客风格”的网站,需要六个网站类的实例,而实际上它们本质上都是一样的代码,如果网站增多,实例数量也会增多,这对服务器的资源浪费很严重。\n", 70 | "\n", 71 | "现在各个大型博客网站,电子商务网站,每一个博客或者商家都是一个小型网站,它们根据用户ID号的不同,来区分不同的用户,具体数据和模版可以不同,但是代码核心和数据库却是共享的。\n", 72 | "\n", 73 | "这就需要用到享元模式。\n", 74 | "\n", 75 | "## 享元模式\n", 76 | "享元模式,运用共享技术有效的支持大量细粒度的对象。主要包括以下几个类:" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "具体Flyweight: 21\n", 89 | "具体Flyweight: 20\n", 90 | "具体Flyweight: 19\n", 91 | "不共享的具体Flyweight: 18\n" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "from abc import ABCMeta, abstractmethod\n", 97 | "\n", 98 | "\n", 99 | "class Flyweight():\n", 100 | " \"\"\"\n", 101 | " Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。\n", 102 | " \"\"\"\n", 103 | " __metaclass__ = ABCMeta\n", 104 | " \n", 105 | " @abstractmethod\n", 106 | " def operation(self, extrinsicstate):\n", 107 | " pass\n", 108 | " \n", 109 | " \n", 110 | "class ConcreteFlyweight(Flyweight):\n", 111 | " \"\"\"\n", 112 | " ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口,并为内部状态增加存储空间\n", 113 | " \"\"\"\n", 114 | " def operation(self, extrinsicstate):\n", 115 | " print(\"具体Flyweight:\", extrinsicstate)\n", 116 | " \n", 117 | " \n", 118 | "class UnsharedConcreteFlyweight(Flyweight):\n", 119 | " \"\"\"\n", 120 | " UnsharedConcreteFlyweight是指那些不需要共享的Flyweight子类。因为Flyweight接口,共享成为可能,但它并不强制共享\n", 121 | " \"\"\"\n", 122 | " def operation(self, extrinsicstate):\n", 123 | " print(\"不共享的具体Flyweight:\", extrinsicstate)\n", 124 | " \n", 125 | " \n", 126 | "class FlyweightFactory():\n", 127 | " \"\"\"\n", 128 | " FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个\n", 129 | " Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。\n", 130 | " \"\"\"\n", 131 | " def __init__(self):\n", 132 | " self.flyweights = dict()\n", 133 | " self.flyweights['X'] = ConcreteFlyweight()\n", 134 | " self.flyweights['Y'] = ConcreteFlyweight()\n", 135 | " self.flyweights['Z'] = ConcreteFlyweight()\n", 136 | " \n", 137 | " def get_flyweight(self, key):\n", 138 | " return self.flyweights[key]\n", 139 | " \n", 140 | "\n", 141 | "def main():\n", 142 | " # 代码外部状态\n", 143 | " extrinsicstate = 22\n", 144 | " \n", 145 | " f = FlyweightFactory()\n", 146 | " \n", 147 | " fx = f.get_flyweight(\"X\")\n", 148 | " extrinsicstate -= 1\n", 149 | " fx.operation(extrinsicstate)\n", 150 | " \n", 151 | " fy = f.get_flyweight(\"Y\")\n", 152 | " extrinsicstate -= 1\n", 153 | " fy.operation(extrinsicstate)\n", 154 | " \n", 155 | " fz = f.get_flyweight(\"Z\")\n", 156 | " extrinsicstate -= 1\n", 157 | " fy.operation(extrinsicstate)\n", 158 | " \n", 159 | " uf = UnsharedConcreteFlyweight()\n", 160 | " extrinsicstate -= 1\n", 161 | " uf.operation(extrinsicstate)\n", 162 | " \n", 163 | "main()" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "### 点评\n", 171 | "\n", 172 | "上述代码中,FlyweightFactory根据客户需求返回早已生成好的对象,但是实际上不一定需要,完全可以初始化时什么也不做,到需要时,再判断对象是否为null来决定是否实例化;\n", 173 | "\n", 174 | "为什么会有UnsharedConcreteFlyweight存在呢?这是因为尽管我们大部分时间都需要共享对象来降低内存损耗,但个别时候也有可能不需要共享的,那么此时的UnsharedConcreteFlyweight就有存在的必要了,它可以解决那些不需要共享对象的问题。\n", 175 | "\n", 176 | "## 网站共享代码" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 11, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "name": "stdout", 186 | "output_type": "stream", 187 | "text": [ 188 | "网站风格: 电商风格\n", 189 | "网站风格: 电商风格\n", 190 | "网站风格: 电商风格\n", 191 | "网站风格: 博客风格\n", 192 | "网站风格: 博客风格\n", 193 | "网站风格: 博客风格\n", 194 | "网站风格总数: 2\n" 195 | ] 196 | } 197 | ], 198 | "source": [ 199 | "from abc import ABCMeta, abstractmethod\n", 200 | "\n", 201 | "\n", 202 | "class WebSite():\n", 203 | " \"\"\"\n", 204 | " 网站抽象类:Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。\n", 205 | " \"\"\"\n", 206 | " __metaclass__ = ABCMeta\n", 207 | " \n", 208 | " @abstractmethod\n", 209 | " def use(self):\n", 210 | " pass\n", 211 | " \n", 212 | " \n", 213 | "class ConcreteWebSite(WebSite):\n", 214 | " \"\"\"\n", 215 | " 具体网站类:ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口,并为内部状态增加存储空间\n", 216 | " \"\"\"\n", 217 | " def __init__(self, name):\n", 218 | " self.name = name\n", 219 | " \n", 220 | " def use(self):\n", 221 | " print(\"网站风格:\", self.name)\n", 222 | " \n", 223 | " \n", 224 | "class WebSiteFactory():\n", 225 | " \"\"\"\n", 226 | " 网站工厂类:FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,\n", 227 | " 当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。\n", 228 | " \"\"\"\n", 229 | " def __init__(self):\n", 230 | " self.flyweights = dict()\n", 231 | " \n", 232 | " def get_website(self, key):\n", 233 | " if key not in self.flyweights:\n", 234 | " self.flyweights[key] = ConcreteWebSite(key)\n", 235 | " return self.flyweights[key]\n", 236 | " \n", 237 | "\n", 238 | "def main():\n", 239 | " f = WebSiteFactory()\n", 240 | " \n", 241 | " fx = f.get_website(\"电商风格\")\n", 242 | " fx.use()\n", 243 | " \n", 244 | " fy = f.get_website(\"电商风格\")\n", 245 | " fy.use()\n", 246 | " \n", 247 | " fz = f.get_website(\"电商风格\")\n", 248 | " fz.use()\n", 249 | " \n", 250 | " fa = f.get_website(\"博客风格\")\n", 251 | " fa.use()\n", 252 | " \n", 253 | " fb = f.get_website(\"博客风格\")\n", 254 | " fb.use()\n", 255 | " \n", 256 | " fc = f.get_website(\"博客风格\")\n", 257 | " fc.use()\n", 258 | " print(\"网站风格总数:\", len(f.flyweights))\n", 259 | " \n", 260 | "main()" 261 | ] 262 | }, 263 | { 264 | "cell_type": "markdown", 265 | "metadata": {}, 266 | "source": [ 267 | "### 点评\n", 268 | "\n", 269 | "这样写基本实现了享元模式共享对象的目的,也就是,无论创建多少个网站,只要是“电商风格”,那就都一样,只要是“博客风格”,也都一样。但是给不同企业创建网站,它们的数据肯定会不同,所以上述代码没有体香对象间的不同,只体现了共享的部分。\n", 270 | "\n", 271 | "## 内部状态和外部状态\n", 272 | "\n", 273 | "在享元对象内部并且不会随环境改变而改变的共享部分,可以成为是享元对象的内部状态;\n", 274 | "\n", 275 | "随着环境改变而改变,不可共享的状态就是享元对象的外部状态;\n", 276 | "\n", 277 | "享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时候就能够大幅度地减少需要实例化的类的数量。如果能把那些参数转移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。\n", 278 | "\n", 279 | "享元模式Flyweight执行时所需的状态是有内部的,也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部状态则应该考虑有客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。\n", 280 | "\n", 281 | "在网站的例子中,客户账号就是外部状态,应该由专门的对象来处理。\n", 282 | "\n", 283 | "## 带有外部状态的版本" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": 13, 289 | "metadata": {}, 290 | "outputs": [ 291 | { 292 | "name": "stdout", 293 | "output_type": "stream", 294 | "text": [ 295 | "贺贺 - 网站风格: 电商风格\n", 296 | "曼曼 - 网站风格: 电商风格\n", 297 | "云云 - 网站风格: 电商风格\n", 298 | "灵灵 - 网站风格: 博客风格\n", 299 | "依依 - 网站风格: 博客风格\n", 300 | "灵依 - 网站风格: 博客风格\n", 301 | "网站风格总数: 2\n" 302 | ] 303 | } 304 | ], 305 | "source": [ 306 | "from abc import ABCMeta, abstractmethod\n", 307 | "\n", 308 | "class User():\n", 309 | " \"\"\"\n", 310 | " 用户类,网站的客户账号,是“网站类”的外部状态\n", 311 | " \"\"\"\n", 312 | " def __init__(self, name):\n", 313 | " self.name = name\n", 314 | "\n", 315 | "class WebSite():\n", 316 | " \"\"\"\n", 317 | " 网站抽象类:Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。\n", 318 | " \"\"\"\n", 319 | " __metaclass__ = ABCMeta\n", 320 | " \n", 321 | " @abstractmethod\n", 322 | " def use(self, user):\n", 323 | " pass\n", 324 | " \n", 325 | " \n", 326 | "class ConcreteWebSite(WebSite):\n", 327 | " \"\"\"\n", 328 | " 具体网站类:ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口,并为内部状态增加存储空间\n", 329 | " \"\"\"\n", 330 | " def __init__(self, name):\n", 331 | " self.name = name\n", 332 | " \n", 333 | " def use(self, user):\n", 334 | " print(user.name, \"- 网站风格:\", self.name)\n", 335 | " \n", 336 | " \n", 337 | "class WebSiteFactory():\n", 338 | " \"\"\"\n", 339 | " 网站工厂类:FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,\n", 340 | " 当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。\n", 341 | " \"\"\"\n", 342 | " def __init__(self):\n", 343 | " self.flyweights = dict()\n", 344 | " \n", 345 | " def get_website(self, key):\n", 346 | " if key not in self.flyweights:\n", 347 | " self.flyweights[key] = ConcreteWebSite(key)\n", 348 | " return self.flyweights[key]\n", 349 | " \n", 350 | "\n", 351 | "def main():\n", 352 | " f = WebSiteFactory()\n", 353 | " \n", 354 | " fx = f.get_website(\"电商风格\")\n", 355 | " fx.use(User(\"贺贺\"))\n", 356 | " \n", 357 | " fy = f.get_website(\"电商风格\")\n", 358 | " fy.use(User(\"曼曼\"))\n", 359 | " \n", 360 | " fz = f.get_website(\"电商风格\")\n", 361 | " fz.use(User(\"云云\"))\n", 362 | " \n", 363 | " fa = f.get_website(\"博客风格\")\n", 364 | " fa.use(User(\"灵灵\"))\n", 365 | " \n", 366 | " fb = f.get_website(\"博客风格\")\n", 367 | " fb.use(User(\"依依\"))\n", 368 | " \n", 369 | " fc = f.get_website(\"博客风格\")\n", 370 | " fc.use(User(\"灵依\"))\n", 371 | " print(\"网站风格总数:\", len(f.flyweights))\n", 372 | " \n", 373 | "main()" 374 | ] 375 | }, 376 | { 377 | "cell_type": "markdown", 378 | "metadata": {}, 379 | "source": [ 380 | "## 总结\n", 381 | "\n", 382 | "什么时候需要考虑使用享元模式呢?\n", 383 | "- 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用\n", 384 | "- 对象的大多数状态都是内部状态,如果可以删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象\n", 385 | "\n", 386 | "在实际使用中,享元模式到底能达到什么效果呢?\n", 387 | "\n", 388 | "因为使用了享元模式,所以有了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。\n", 389 | "\n", 390 | "需要注意的是,享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式会使系统变得更加复杂。" 391 | ] 392 | } 393 | ], 394 | "metadata": { 395 | "kernelspec": { 396 | "display_name": "Python 3", 397 | "language": "python", 398 | "name": "python3" 399 | }, 400 | "language_info": { 401 | "codemirror_mode": { 402 | "name": "ipython", 403 | "version": 3 404 | }, 405 | "file_extension": ".py", 406 | "mimetype": "text/x-python", 407 | "name": "python", 408 | "nbconvert_exporter": "python", 409 | "pygments_lexer": "ipython3", 410 | "version": "3.6.4" 411 | } 412 | }, 413 | "nbformat": 4, 414 | "nbformat_minor": 2 415 | } 416 | -------------------------------------------------------------------------------- /第27章 正则表达式——解释器模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n", 8 | "\n", 9 | "## 解释器模式\n", 10 | "\n", 11 | "解释器模式,给定一个语言,定一个它的文法的一种表示,并定一个一个解释器,这个解释器使用该表示来解释语言中的橘子。\n", 12 | "\n", 13 | "解释其模式需要解决的是,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的橘子。这样就可以构建一个解释器,该解释器通过解释这些橘子来解决该问题。\n", 14 | "\n", 15 | "比如说,我们常常会在字符串中搜索匹配的字符或者判断一个字符串是否符合我们的规定格式,此时我们一般会用正则表达式技术。因为匹配字符串的需求在很多地方都会用到,而且行为类似,与其为每一个特定需求都写一个算法函数,不如使用一种通用的搜索算法来解释执行一个正则表达式,该正则表达式定义了待匹配字符串的集合。\n", 16 | "\n", 17 | "正则表达式就是解释器模式的一种用用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。\n", 18 | "\n", 19 | "解释器模式主要包含以下几个类:" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "终结符表达式\n", 32 | "非终结符表达式\n", 33 | "终结符表达式\n", 34 | "非终结符表达式\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "from abc import ABCMeta, abstractmethod\n", 40 | "\n", 41 | "\n", 42 | "class AbstractExpression():\n", 43 | " \"\"\"\n", 44 | " 抽象表达式类,声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享\n", 45 | " \"\"\"\n", 46 | " __metaclass__ = ABCMeta\n", 47 | " \n", 48 | " @abstractmethod\n", 49 | " def interpret(self, context):\n", 50 | " pass\n", 51 | " \n", 52 | "\n", 53 | "class TerminalExpression(AbstractExpression):\n", 54 | " \"\"\"\n", 55 | " 终结符表达式,实现与文法中的终结符相关联的解释操作。实现抽象表达式中所要求的接口,主要是一个interpret()方法。\n", 56 | " 文法中的每一个终结符都有一个具体终结符表达式与之相对应。\n", 57 | " \"\"\"\n", 58 | " def interpret(self, context):\n", 59 | " print(\"终结符表达式\")\n", 60 | " \n", 61 | " \n", 62 | "class NontermialExpression(AbstractExpression):\n", 63 | " \"\"\"\n", 64 | " 非终结符表达式,为文法中的非终结符实现解释操作。对文法中每一条规则R1,R2,...Rn都需要一个具体的非终结符表达式类。\n", 65 | " 通过实现抽闲表达式的interpret()方法实现解释操作。解释操作以递归的方式调用上 main所提到的代表R1,R2,...Rn中各\n", 66 | " 个符号的实例变量。\n", 67 | " \"\"\"\n", 68 | " def interpret(self, context):\n", 69 | " print(\"非终结符表达式\")\n", 70 | " \n", 71 | " \n", 72 | "class Context():\n", 73 | " \"\"\"\n", 74 | " 上下文类,包含解释器之外的一些全局信息\n", 75 | " \"\"\"\n", 76 | " def __init__(self):\n", 77 | " self.input = None\n", 78 | " self.output = None\n", 79 | " \n", 80 | " \n", 81 | "def main():\n", 82 | " \"\"\"\n", 83 | " 客户端代码,构建表示该文法定义的语言中一个特定的句子的抽象语法树。\n", 84 | " \"\"\"\n", 85 | " context = Context()\n", 86 | " exp_list = []\n", 87 | " exp_list.append(TerminalExpression())\n", 88 | " exp_list.append(NontermialExpression())\n", 89 | " exp_list.append(TerminalExpression())\n", 90 | " exp_list.append(NontermialExpression())\n", 91 | " \n", 92 | " for exp in exp_list:\n", 93 | " exp.interpret(context)\n", 94 | " \n", 95 | "main()" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "### 点评\n", 103 | "\n", 104 | "当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式[DP]。\n", 105 | "\n", 106 | "用了解释器模式,就意味着可以很容易的改变和扩展文法,因为该模式使用类来表示文法规则,你可以使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接便携。\n", 107 | "\n", 108 | "解释器模式也有不足,解释器模式为文法中每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使用其他技术,如语法分析程序或者编译器生成器来处理[DP]。" 109 | ] 110 | } 111 | ], 112 | "metadata": { 113 | "kernelspec": { 114 | "display_name": "Python 3", 115 | "language": "python", 116 | "name": "python3" 117 | }, 118 | "language_info": { 119 | "codemirror_mode": { 120 | "name": "ipython", 121 | "version": 3 122 | }, 123 | "file_extension": ".py", 124 | "mimetype": "text/x-python", 125 | "name": "python", 126 | "nbconvert_exporter": "python", 127 | "pygments_lexer": "ipython3", 128 | "version": "3.6.4" 129 | } 130 | }, 131 | "nbformat": 4, 132 | "nbformat_minor": 2 133 | } 134 | -------------------------------------------------------------------------------- /第2章 商场收银软件——策略模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 题目\n", 8 | "设计一个控制台程序, 模拟商场收银软件,根据客户购买商品的单价和数量,计算总价。\n", 9 | "\n", 10 | "## 基础版本" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 10, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "输入商品单价:40\n", 23 | "输入商品数量:9\n", 24 | "当前总价: 360.00\n" 25 | ] 26 | } 27 | ], 28 | "source": [ 29 | "price = float(input(\"输入商品单价:\"))\n", 30 | "number = int(input(\"输入商品数量:\"))\n", 31 | "total = (price * number)\n", 32 | "print(\"当前总价: %.2f\" % total)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### 点评\n", 40 | "上述程序仅仅实现了基本功能,但是当商场有打折活动,例如八折,五折等,就不满足需求了,折扣的方法还可能有满减活动,例如满300减100,满500减200等。假设只有打折和满减两种促销活动,那么这就很像上一章节的计算器,支持正常收费,打折活动和满减活动三种计算方法,可以用简单工厂方法实现。\n", 41 | "\n", 42 | "## 改进版本1.0——简单工厂模式" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 33, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from abc import ABCMeta, abstractmethod\n", 52 | "\n", 53 | "class CashBase():\n", 54 | " \"\"\"\n", 55 | " 基础类\n", 56 | " \"\"\"\n", 57 | " __metaclass__ = ABCMeta\n", 58 | " \n", 59 | " def __init__(self):\n", 60 | " self.final_price = None\n", 61 | " \n", 62 | " @abstractmethod\n", 63 | " def accept_cash(self):\n", 64 | " pass\n", 65 | "\n", 66 | "class CashNormal(CashBase):\n", 67 | " \"\"\"\n", 68 | " 正常收费\n", 69 | " \"\"\"\n", 70 | " \n", 71 | " def accept_cash(self, money):\n", 72 | " self.final_price = money\n", 73 | " return self.final_price\n", 74 | "\n", 75 | "class CashRebate(CashBase):\n", 76 | " \"\"\"\n", 77 | " 打折活动\n", 78 | " \"\"\"\n", 79 | " def __init__(self, rebate):\n", 80 | " self.rebate = rebate\n", 81 | " \n", 82 | " def accept_cash(self, money):\n", 83 | " self.final_price = money * self.rebate\n", 84 | " return self.final_price\n", 85 | "\n", 86 | "class CashReturn(CashBase):\n", 87 | " \"\"\"\n", 88 | " 满减活动\n", 89 | " \"\"\"\n", 90 | " def __init__(self, return_condition, return_money):\n", 91 | " self.return_condition = return_condition\n", 92 | " self.return_money = return_money\n", 93 | " \n", 94 | " def accept_cash(self, money):\n", 95 | " if money >= self.return_condition:\n", 96 | " self.final_price = money - self.return_money\n", 97 | " else:\n", 98 | " self.final_price = money\n", 99 | " return self.final_price\n", 100 | "\n", 101 | "class CashFactory():\n", 102 | " \"\"\"\n", 103 | " 收费方式工厂类\n", 104 | " \"\"\"\n", 105 | " # 类的变量,类似静态变量,通过`类名.变量名`访问\n", 106 | " cash_accepter_map = {\n", 107 | " \"正常收费\": CashNormal(),\n", 108 | " \"满300减100\": CashReturn(300, 100),\n", 109 | " \"打8折\": CashRebate(0.8)\n", 110 | " }\n", 111 | " \n", 112 | " @staticmethod\n", 113 | " def createCashAccepter(cash_type):\n", 114 | " if cash_type in CashFactory.cash_accepter_map:\n", 115 | " return CashFactory.cash_accepter_map[cash_type]\n", 116 | " else:\n", 117 | " return None" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "#### 客户端代码" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 34, 130 | "metadata": {}, 131 | "outputs": [ 132 | { 133 | "name": "stdout", 134 | "output_type": "stream", 135 | "text": [ 136 | "输入商品单价:10\n", 137 | "输入商品数量:50\n", 138 | "1:正常收费\n", 139 | "2:满300减100\n", 140 | "3:打8折\n", 141 | "选择收费方式(1~3)3\n", 142 | "应收: 500.00\n", 143 | "实收: 400.00\n" 144 | ] 145 | } 146 | ], 147 | "source": [ 148 | "price = float(input(\"输入商品单价:\"))\n", 149 | "number = int(input(\"输入商品数量:\"))\n", 150 | "cash_type_list = [\"正常收费\", \"满300减100\", \"打8折\"]\n", 151 | "for i in cash_type_list:\n", 152 | " print(\"{}:{}\".format(cash_type_list.index(i)+1, i))\n", 153 | "cash_type_index = int(input(\"选择收费方式(1~3)\"))\n", 154 | "\n", 155 | "total = price * number\n", 156 | "cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])\n", 157 | "print(\"应收: %.2f\" % total)\n", 158 | "total = cash_accepter.accept_cash(total)\n", 159 | "print(\"实收: %.2f\" % total)" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "### 点评\n", 167 | "1. 如果同时支持打折和满减,需要如何处理?\n", 168 | "2. 简单工厂模式主要解决对象的创建问题,无法解决对象经常改动的问题,例如折扣和满减力度是经常变化的,不能每次改动都改代码;\n", 169 | "3. 算法经常改动, 需要用到策略模式;\n", 170 | "4. 封装变化点是面向对象的一种重要的思维方式。\n", 171 | "\n", 172 | "## 策略模式\n", 173 | "\n", 174 | "该模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 29, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "from abc import ABCMeta, abstractmethod\n", 184 | "\n", 185 | "class CashBase():\n", 186 | " \"\"\"\n", 187 | " 抽象策略:基础类\n", 188 | " \"\"\"\n", 189 | " __metaclass__ = ABCMeta\n", 190 | " \n", 191 | " def __init__(self):\n", 192 | " self.final_price = None\n", 193 | " \n", 194 | " @abstractmethod\n", 195 | " def accept_cash(self):\n", 196 | " pass\n", 197 | "\n", 198 | "class CashNormal(CashBase):\n", 199 | " \"\"\"\n", 200 | " 具体策略:正常收费\n", 201 | " \"\"\"\n", 202 | " \n", 203 | " def accept_cash(self, money):\n", 204 | " self.final_price = money\n", 205 | " return self.final_price\n", 206 | "\n", 207 | "class CashRebate(CashBase):\n", 208 | " \"\"\"\n", 209 | " 具体策略:打折活动\n", 210 | " \"\"\"\n", 211 | " def __init__(self, rebate):\n", 212 | " self.rebate = rebate\n", 213 | " \n", 214 | " def accept_cash(self, money):\n", 215 | " self.final_price = money * self.rebate\n", 216 | " return self.final_price\n", 217 | "\n", 218 | "class CashReturn(CashBase):\n", 219 | " \"\"\"\n", 220 | " 具体策略:满减活动\n", 221 | " \"\"\"\n", 222 | " def __init__(self, return_condition, return_money):\n", 223 | " self.return_condition = return_condition\n", 224 | " self.return_money = return_money\n", 225 | " \n", 226 | " def accept_cash(self, money):\n", 227 | " if money >= self.return_condition:\n", 228 | " self.final_price = money - self.return_money\n", 229 | " else:\n", 230 | " self.final_price = money\n", 231 | " return self.final_price\n", 232 | "\n", 233 | "class CashContext():\n", 234 | " \"\"\"\n", 235 | " 策略上下文类(基础版本),用具体策略类来配置,维护一个具体策略对象的引用\n", 236 | " \"\"\"\n", 237 | " def __init__(self, cash_strategy):\n", 238 | " self.cash_strategy = cash_strategy\n", 239 | " \n", 240 | " def get_result(slef, money):\n", 241 | " return self.cash_strategy.accept_cash(money) " 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "metadata": {}, 247 | "source": [ 248 | "### 点评\n", 249 | "在CashContext类中,我们需要传入一个具体策略类来进行配置,在商场收银软件这个场景中,那就是不同的收费策略,那么如何生成不同的收费策略对象呢?可以将策略模式和简单工厂相结合。" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 30, 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [ 258 | "class CashContext():\n", 259 | " \"\"\"\n", 260 | " 策略上下文类(改进版本),用具体策略类来配置,维护一个具体策略对象的引用\n", 261 | " \"\"\"\n", 262 | " # 类的变量,类似静态变量,通过`类名.变量名`访问\n", 263 | " cash_accepter_map = {\n", 264 | " \"正常收费\": CashNormal(),\n", 265 | " \"满300减100\": CashReturn(300, 100),\n", 266 | " \"打8折\": CashRebate(0.8)\n", 267 | " }\n", 268 | " def __init__(self, cash_type):\n", 269 | " self.cash_strategy = CashContext.cash_accepter_map[cash_type]\n", 270 | " \n", 271 | " def get_result(self, money):\n", 272 | " return self.cash_strategy.accept_cash(money) " 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "#### 客户端代码" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 32, 285 | "metadata": {}, 286 | "outputs": [ 287 | { 288 | "name": "stdout", 289 | "output_type": "stream", 290 | "text": [ 291 | "输入商品单价:10\n", 292 | "输入商品数量:10\n", 293 | "1:正常收费\n", 294 | "2:满300减100\n", 295 | "3:打8折\n", 296 | "选择收费方式(1~3)3\n", 297 | "应收: 100.00\n", 298 | "实收: 80.00\n" 299 | ] 300 | } 301 | ], 302 | "source": [ 303 | "price = float(input(\"输入商品单价:\"))\n", 304 | "number = int(input(\"输入商品数量:\"))\n", 305 | "cash_type_list = [\"正常收费\", \"满300减100\", \"打8折\"]\n", 306 | "for i in cash_type_list:\n", 307 | " print(\"{}:{}\".format(cash_type_list.index(i)+1, i))\n", 308 | "cash_type_index = int(input(\"选择收费方式(1~3)\"))\n", 309 | "\n", 310 | "total = price * number\n", 311 | "cash_context = CashContext(cash_type_list[cash_type_index-1])\n", 312 | "print(\"应收: %.2f\" % total)\n", 313 | "total = cash_context.get_result(total)\n", 314 | "print(\"实收: %.2f\" % total)" 315 | ] 316 | }, 317 | { 318 | "cell_type": "markdown", 319 | "metadata": {}, 320 | "source": [ 321 | "### 点评\n", 322 | "策略模式+简单工厂和仅用简单工厂模式的区别在哪里呢?\n", 323 | "\n", 324 | " 简单工厂\n", 325 | " cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])\n", 326 | " ...\n", 327 | " total = cash_accepter.accept_cash(total)\n", 328 | " \n", 329 | " 策略模式+简单工厂\n", 330 | " cash_context = CashContext(cash_type_list[cash_type_index-1])\n", 331 | " ...\n", 332 | " total = cash_context.get_result(total)\n", 333 | " \n", 334 | "- 简单工厂需要让客户端认识两个类,`CashFactory`和`CashBase` \n", 335 | "- 策略模式+简单工厂,客户端只需要认识一个类,`CashContext`\n", 336 | "- 客户端实例化的是`CashContext`的对象,调用的是`CashContext`的`get_result`方法,这使得具体的收费策略彻底与客户端分离,甚至连策略的基类`CashBase`都不需要客户端认识。\n", 337 | "\n", 338 | "### 策略模式解析\n", 339 | "1. 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用素有的算法,减少了各种算法类与使用算法类之间的耦合[DPE]。\n", 340 | "2. 策略模式的Strategy层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能[DP],例如计算费用的结果get_result。\n", 341 | "3. 策略模式可以简化单元测试,因为每个算法都有自己的类,可以用过自己的接口单独测试[DPE]。\n", 342 | "4. 策略模式是用来封装算法的,但是实践中,可以用它来封装几乎任何类型的规则,只要需要不同时间应用不同业务规则,就可以考虑使用策略模式处理这种变化的可能性[DPE]。\n", 343 | "\n", 344 | "### 美中不足\n", 345 | "在CashContext中用到了一个dict()型的类的变量`cash_accepter_map`保存各种算法策略,如果新增`满200减50`的策略,那么还要更新`cash_accepter_map`,这显得并不优雅,`任何需要的变更都需要成本,但是成本的高低是有差异的`,为了更加优雅,降低变更成本,可以使用`反射技术`,这一技术将在`抽象工厂模式`中介绍。" 346 | ] 347 | } 348 | ], 349 | "metadata": { 350 | "kernelspec": { 351 | "display_name": "Python 3", 352 | "language": "python", 353 | "name": "python3" 354 | }, 355 | "language_info": { 356 | "codemirror_mode": { 357 | "name": "ipython", 358 | "version": 3 359 | }, 360 | "file_extension": ".py", 361 | "mimetype": "text/x-python", 362 | "name": "python", 363 | "nbconvert_exporter": "python", 364 | "pygments_lexer": "ipython3", 365 | "version": "3.6.4" 366 | } 367 | }, 368 | "nbformat": 4, 369 | "nbformat_minor": 2 370 | } 371 | -------------------------------------------------------------------------------- /第3章 单一职责原则.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 单一职责原则\n", 8 | "\n", 9 | "就一个类而言,应该仅有一个引起它变化的原因。\n", 10 | "\n", 11 | "如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。\n", 12 | "\n", 13 | "软件设计真正要做的许多内容,就是发现职责并把哪些职责相互分离。如果你能够想到多余一个的动机去改变一个类,那么这个类就具有多余一个的职责,就应该考虑类的职责分离。" 14 | ] 15 | } 16 | ], 17 | "metadata": { 18 | "kernelspec": { 19 | "display_name": "Python 3", 20 | "language": "python", 21 | "name": "python3" 22 | }, 23 | "language_info": { 24 | "codemirror_mode": { 25 | "name": "ipython", 26 | "version": 3 27 | }, 28 | "file_extension": ".py", 29 | "mimetype": "text/x-python", 30 | "name": "python", 31 | "nbconvert_exporter": "python", 32 | "pygments_lexer": "ipython3", 33 | "version": "3.6.4" 34 | } 35 | }, 36 | "nbformat": 4, 37 | "nbformat_minor": 2 38 | } 39 | -------------------------------------------------------------------------------- /第4章 开放-封闭原则.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 开放-封闭原则\n", 8 | "\n", 9 | "开放-封闭原则,是说软件实体(类,模块,函数等)应该可以扩展,但是不可修改。即对扩展开放(Open for extension) ,对更改封闭(Closed for modification)。\n", 10 | "\n", 11 | "软件需求是一定会变化的,在设计软件系统的时候遵循开放-封闭原则,当面对需求的变化时,可以相对容易修改,保持相对稳定。\n", 12 | "\n", 13 | "设计软件要容易维护又不容易出问题的最好办法,就是多扩展,少修改。\n", 14 | "\n", 15 | "绝对的对修改关闭是不可能的。无论模块多么的“封闭”,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应对哪种变化封闭作出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。并且,当变化发生时,应当立即采取行动。\n", 16 | "\n", 17 | "在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。\n", 18 | "\n", 19 | "面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。\n", 20 | "\n", 21 | "我们希望的是在开发工作展开不久就知道可能发生的变化。查明可能发生变化所等待的时间越长,要创建正确的抽象就越困难。\n", 22 | "\n", 23 | "开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对下岗技术所声称的巨大好处,也就是可维护,可扩展,可复用,灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分作出抽象,然而,对于应用程序中的每个部分都刻意的进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要,避免矫枉过正。" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.6.4" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 2 48 | } 49 | -------------------------------------------------------------------------------- /第5章 依赖倒转原则.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 依赖倒转原则\n", 8 | "\n", 9 | "依赖倒转原则,主要包括两点:\n", 10 | "\n", 11 | "- 抽象不应该依赖细节,细节应该依赖于抽象;\n", 12 | "- 高层模块不应该依赖底层模块,二者都应该依赖抽象;\n", 13 | "\n", 14 | "也就是说针对接口编程,不要对实现编程。例如一个数据库信息管理系统,数据库连接属于低层模块,增删改查等操作属于高层模块,系统界面属于更高层的模块。不同层级的模块都应该依赖于抽象,也就是接口或者抽象类,只要接口稳定,那么任何一个的更改都不用担心其他受影响,这就使得无论高层模块还是低层模块都可以很容易地被复用。\n", 15 | "\n", 16 | "## 里氏代换原则\n", 17 | "\n", 18 | "子类型必须能够替换掉它们的父类型。\n", 19 | "\n", 20 | "一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,把父类都替换成它的子类,程序的行为没有变化。\n", 21 | "\n", 22 | "例如设计一个鸟类,一个企鹅类,假设鸟类都可以飞,但是企鹅不会飞,那么企鹅是鸟么?企鹅可以继承鸟类吗?\n", 23 | "\n", 24 | "虽然直观上企鹅也是一种特殊的鸟类,但是在面向对象编程的世界里,鸟类会飞,企鹅类不会飞,也就是企鹅类不能以鸟类的身份出现,所以且不能继承鸟类。\n", 25 | "\n", 26 | "只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。\n", 27 | "\n", 28 | "## 总结\n", 29 | "\n", 30 | "依赖倒转可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果便携式考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之就是过程化的设计了。" 31 | ] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.6.4" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 2 55 | } 56 | -------------------------------------------------------------------------------- /第6章 衣服搭配系统——装饰模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 题目\n", 8 | "设计一个控制台程序,可以给人搭配嘻哈风格(T恤,垮裤,运动鞋)或白领风格(西装,领带,皮鞋)的衣服并展示,类似QQ秀那样的。\n", 9 | "\n", 10 | "## 基础版本" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 3, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "class Person():\n", 20 | " \n", 21 | " def __init__(self, name):\n", 22 | " self.name = name\n", 23 | " \n", 24 | " def wear_T_shirts(self):\n", 25 | " print(\"T恤\")\n", 26 | " \n", 27 | " def wear_big_trouser(self):\n", 28 | " print(\"垮裤\")\n", 29 | " \n", 30 | " def wear_sneakers(self):\n", 31 | " print(\"运动鞋\")\n", 32 | " \n", 33 | " def wear_suit(self):\n", 34 | " print(\"西装\")\n", 35 | " \n", 36 | " def wear_tie(self):\n", 37 | " print(\"领带\")\n", 38 | " \n", 39 | " def wear_leather_shoes(self):\n", 40 | " print(\"皮鞋\")\n", 41 | " \n", 42 | " def show(self):\n", 43 | " print(\"装扮的{}\".format(self.name))" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "客户端代码" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 4, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "第一种装扮\n", 63 | "T恤\n", 64 | "垮裤\n", 65 | "运动鞋\n", 66 | "装扮的张贺\n", 67 | "第二种装扮\n", 68 | "西装\n", 69 | "领带\n", 70 | "皮鞋\n", 71 | "装扮的张贺\n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "def main():\n", 77 | " hezhang = Person(\"张贺\")\n", 78 | " print(\"第一种装扮\")\n", 79 | " hezhang.wear_T_shirts()\n", 80 | " hezhang.wear_big_trouser()\n", 81 | " hezhang.wear_sneakers()\n", 82 | " hezhang.show()\n", 83 | " \n", 84 | " print(\"第二种装扮\")\n", 85 | " hezhang.wear_suit()\n", 86 | " hezhang.wear_tie()\n", 87 | " hezhang.wear_leather_shoes()\n", 88 | " hezhang.show()\n", 89 | "\n", 90 | "main()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### 点评\n", 98 | "- 仅实现了基本功能\n", 99 | "- 如果添加“超人”装扮,需要如何做?需要在Person类中改代码;\n", 100 | "- 如果Person类除了穿衣服,还要支持吃饭,睡觉等功能,需要如何做?需要在Person类中改代码;\n", 101 | "- 综上,需要把“服饰”类和“人”类分离。\n", 102 | "\n", 103 | "## 改进版本1.0——人衣分离" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 10, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "class Person():\n", 113 | " \n", 114 | " def __init__(self, name):\n", 115 | " self.name = name\n", 116 | " \n", 117 | " def show(self):\n", 118 | " print(\"装扮的{}\".format(self.name))\n", 119 | "\n", 120 | "from abc import ABCMeta, abstractmethod\n", 121 | "\n", 122 | "\n", 123 | "class Finery(metaclass=ABCMeta):\n", 124 | " \n", 125 | " @abstractmethod\n", 126 | " def show(self):\n", 127 | " pass\n", 128 | " \n", 129 | "class TShirts(Finery):\n", 130 | " \n", 131 | " def show(self):\n", 132 | " print(\"T恤\")\n", 133 | "\n", 134 | "class BigTrouser(Finery):\n", 135 | " \n", 136 | " def show(self):\n", 137 | " print(\"垮裤\")\n", 138 | " \n", 139 | "class Sneakers(Finery):\n", 140 | " \n", 141 | " def show(self):\n", 142 | " print(\"运动鞋\")\n", 143 | " \n", 144 | "class Suit(Finery):\n", 145 | " \n", 146 | " def show(self):\n", 147 | " print(\"西装\")\n", 148 | " \n", 149 | "class Tie(Finery):\n", 150 | " \n", 151 | " def show(self):\n", 152 | " print(\"领带\")\n", 153 | " \n", 154 | "class LeatherShoes(Finery):\n", 155 | " \n", 156 | " def show(self):\n", 157 | " print(\"皮鞋\")" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "客户端代码" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 11, 170 | "metadata": {}, 171 | "outputs": [ 172 | { 173 | "name": "stdout", 174 | "output_type": "stream", 175 | "text": [ 176 | "第一种装扮\n", 177 | "T恤\n", 178 | "垮裤\n", 179 | "运动鞋\n", 180 | "装扮的张贺\n", 181 | "第二种装扮\n", 182 | "西装\n", 183 | "领带\n", 184 | "皮鞋\n", 185 | "装扮的张贺\n" 186 | ] 187 | } 188 | ], 189 | "source": [ 190 | "def main():\n", 191 | " hezhang = Person(\"张贺\")\n", 192 | " print(\"第一种装扮\")\n", 193 | " t_shirts = TShirts()\n", 194 | " big_trouser = BigTrouser()\n", 195 | " sneakers = Sneakers()\n", 196 | " \n", 197 | " t_shirts.show()\n", 198 | " big_trouser.show()\n", 199 | " sneakers.show()\n", 200 | " hezhang.show()\n", 201 | " \n", 202 | " print(\"第二种装扮\")\n", 203 | " suit = Suit()\n", 204 | " tie = Tie()\n", 205 | " leather_shoes = LeatherShoes()\n", 206 | " suit.show()\n", 207 | " tie.show()\n", 208 | " leather_shoes.show()\n", 209 | " hezhang.show()\n", 210 | "\n", 211 | "main()" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": {}, 217 | "source": [ 218 | "### 点评\n", 219 | "分析以下代码:\n", 220 | "```\n", 221 | " hezhang = Person(\"张贺\")\n", 222 | " t_shirts = TShirts()\n", 223 | " big_trouser = BigTrouser()\n", 224 | " sneakers = Sneakers()\n", 225 | " \n", 226 | " t_shirts.show()\n", 227 | " big_trouser.show()\n", 228 | " sneakers.show()\n", 229 | " hezhang.show()\n", 230 | "```\n", 231 | "用自然语言描述就是:\n", 232 | "- 先实例化出`hezhang`这个人类,准确的说,是没有穿衣服的人类; \n", 233 | "- 再实例化并穿上出各种衣服,T恤,垮裤,运动鞋等;\n", 234 | "- 再展示出来`hezhang`;\n", 235 | "\n", 236 | "但是服饰和人之间好像没有任何关系,那么如何用代码表示:\n", 237 | "\n", 238 | "- 实例化没穿衣服的人类\n", 239 | "- 为没穿衣服的人类穿上T恤\n", 240 | "- 为穿着T恤的人类穿上垮裤\n", 241 | "- 为穿着T恤,垮裤的人类穿上运动鞋\n", 242 | "\n", 243 | "这需要用到装饰模式。\n", 244 | "\n", 245 | "## 装饰模式\n", 246 | "\n", 247 | "装饰模式,为了动态的给一个对象添加一些额外的职责,就增加功能而言,装饰模式比生成子类更为灵活[DP]。\n", 248 | "\n", 249 | "装饰模式有以下几个主要组成部分:\n", 250 | "\n", 251 | "- 组件类Component:定义一个对象接口, 可以给这些对象动态的添加职责;\n", 252 | "- 具体组件类ConcretComponent:定义了一个具体对象,也可以给这个对象添加一些职责;\n", 253 | "- 装饰类Decorator:装饰抽象类,继承Component,从外类来扩展Component类的功能,对于Component而言,无需知道Decorator的存在;\n", 254 | "- 具体装饰类ConcretDecorator:具体装饰类,为Component添加职责\n", 255 | "\n", 256 | "用代码表示:" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": 24, 262 | "metadata": {}, 263 | "outputs": [], 264 | "source": [ 265 | "from abc import ABCMeta, abstractmethod\n", 266 | "\n", 267 | "\n", 268 | "class Component(metaclass=ABCMeta):\n", 269 | " \"\"\"\n", 270 | " 组件类Component:定义一个对象接口, 可以给这些对象动态的添加职责\n", 271 | " \"\"\"\n", 272 | " @abstractmethod\n", 273 | " def operation(self):\n", 274 | " pass\n", 275 | " \n", 276 | " \n", 277 | "class ConcreteComponent(Component):\n", 278 | " \"\"\"\n", 279 | " 具体组件类ConcretComponent:定义了一个具体对象,也可以给这个对象添加一些职责\n", 280 | " \"\"\"\n", 281 | " def operation(self):\n", 282 | " print(\"具体组件的操作\")\n", 283 | " \n", 284 | " \n", 285 | "class Decorator(Component):\n", 286 | " \"\"\"\n", 287 | " 装饰类Decorator:装饰抽象类,继承Component,从外类来扩展Component类的功能,对于Component而言,无需知道Decorator的存在;\n", 288 | " \"\"\"\n", 289 | " def __init__(self):\n", 290 | " self.component = None\n", 291 | " \n", 292 | " def set_component(self, component):\n", 293 | " self.component = component\n", 294 | " \n", 295 | " def operation(self):\n", 296 | " if self.component != None:\n", 297 | " self.component.operation()\n", 298 | " \n", 299 | " \n", 300 | "class ConcreteDecratorA(Decorator):\n", 301 | " \"\"\"\n", 302 | " 具体装饰类ConcretDecorator:具体装饰类,为Component添加职责\n", 303 | " \"\"\"\n", 304 | " def __init__(self):\n", 305 | " self.added_operation = \"A:具体装饰类A独有操作\"\n", 306 | " \n", 307 | " def operation(self):\n", 308 | " super().operation()\n", 309 | " print(self.added_operation)\n", 310 | " print(\"A:具体装饰对象A的操作\")\n", 311 | " \n", 312 | " \n", 313 | "class ConcreteDecratorB(Decorator):\n", 314 | " \"\"\"\n", 315 | " 具体装饰类ConcretDecorator:具体装饰类,为Component添加职责\n", 316 | " \"\"\"\n", 317 | " def __init__(self):\n", 318 | " self.added_operation = \"B:具体装饰类B独有操作\"\n", 319 | " \n", 320 | " def operation(self):\n", 321 | " super().operation()\n", 322 | " print(self.added_operation)\n", 323 | " print(\"B:具体装饰对象B的操作\")" 324 | ] 325 | }, 326 | { 327 | "cell_type": "code", 328 | "execution_count": 25, 329 | "metadata": {}, 330 | "outputs": [ 331 | { 332 | "name": "stdout", 333 | "output_type": "stream", 334 | "text": [ 335 | "具体组件的操作\n", 336 | "A:具体装饰类A独有操作\n", 337 | "A:具体装饰对象A的操作\n", 338 | "B:具体装饰类B独有操作\n", 339 | "B:具体装饰对象B的操作\n" 340 | ] 341 | } 342 | ], 343 | "source": [ 344 | "def main():\n", 345 | " component = ConcreteComponent()\n", 346 | " decorator_a = ConcreteDecratorA()\n", 347 | " decorator_b = ConcreteDecratorB()\n", 348 | " \n", 349 | " decorator_a.set_component(component)\n", 350 | " decorator_b.set_component(decorator_a)\n", 351 | " decorator_b.operation()\n", 352 | " \n", 353 | "main()" 354 | ] 355 | }, 356 | { 357 | "cell_type": "markdown", 358 | "metadata": {}, 359 | "source": [ 360 | "## 改进版本2.0——装饰模式" 361 | ] 362 | }, 363 | { 364 | "cell_type": "code", 365 | "execution_count": 36, 366 | "metadata": {}, 367 | "outputs": [], 368 | "source": [ 369 | "from abc import ABCMeta,abstractmethod\n", 370 | "\n", 371 | "\n", 372 | "class Person():\n", 373 | " \"\"\"\n", 374 | " 人物类(组件类)\n", 375 | " \"\"\"\n", 376 | " def __init__(self, name):\n", 377 | " self.name = name\n", 378 | " \n", 379 | " def show(self):\n", 380 | " print(\"装扮的{}\".format(self.name))\n", 381 | " \n", 382 | " \n", 383 | "class Finery(Person):\n", 384 | " \"\"\"\n", 385 | " 服饰类(装饰类)\n", 386 | " \"\"\"\n", 387 | " def __init__(self):\n", 388 | " self.component = None\n", 389 | " \n", 390 | " def decorate(self, component):\n", 391 | " self.component = component\n", 392 | " \n", 393 | " \n", 394 | " def show(self):\n", 395 | " if self.component != None:\n", 396 | " self.component.show()\n", 397 | " \n", 398 | "\n", 399 | "class TShirts(Finery):\n", 400 | " \"\"\"\n", 401 | " 具体装饰类\n", 402 | " \"\"\"\n", 403 | " def show(self):\n", 404 | " print(\"T恤\")\n", 405 | " super().show()\n", 406 | "\n", 407 | "class BigTrouser(Finery):\n", 408 | " \"\"\"\n", 409 | " 具体装饰类\n", 410 | " \"\"\"\n", 411 | " def show(self):\n", 412 | " print(\"垮裤\")\n", 413 | " super().show()\n", 414 | " \n", 415 | " \n", 416 | "class Sneakers(Finery):\n", 417 | " \"\"\"\n", 418 | " 具体装饰类\n", 419 | " \"\"\"\n", 420 | " def show(self):\n", 421 | " print(\"运动鞋\")\n", 422 | " super().show()\n", 423 | " \n", 424 | "class Suit(Finery):\n", 425 | " \"\"\"\n", 426 | " 具体装饰类\n", 427 | " \"\"\"\n", 428 | " def show(self):\n", 429 | " print(\"西装\")\n", 430 | " super().show()\n", 431 | " \n", 432 | "class Tie(Finery):\n", 433 | " \"\"\"\n", 434 | " 具体装饰类\n", 435 | " \"\"\"\n", 436 | " def show(self):\n", 437 | " print(\"领带\")\n", 438 | " super().show()\n", 439 | " \n", 440 | "class LeatherShoes(Finery):\n", 441 | " \"\"\"\n", 442 | " 具体装饰类\n", 443 | " \"\"\"\n", 444 | " def show(self):\n", 445 | " print(\"皮鞋\")\n", 446 | " super().show()" 447 | ] 448 | }, 449 | { 450 | "cell_type": "markdown", 451 | "metadata": {}, 452 | "source": [ 453 | "客户端代码" 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "execution_count": 37, 459 | "metadata": {}, 460 | "outputs": [ 461 | { 462 | "name": "stdout", 463 | "output_type": "stream", 464 | "text": [ 465 | "运动鞋\n", 466 | "垮裤\n", 467 | "T恤\n", 468 | "装扮的张贺\n" 469 | ] 470 | } 471 | ], 472 | "source": [ 473 | "def main():\n", 474 | " hezhang = Person(\"张贺\")\n", 475 | " t_shirts = TShirts()\n", 476 | " big_trouser = BigTrouser()\n", 477 | " sneakers = Sneakers()\n", 478 | " \n", 479 | " t_shirts.decorate(hezhang)\n", 480 | " big_trouser.decorate(t_shirts)\n", 481 | " sneakers.decorate(big_trouser)\n", 482 | " sneakers.show()\n", 483 | " \n", 484 | "main()" 485 | ] 486 | }, 487 | { 488 | "cell_type": "markdown", 489 | "metadata": {}, 490 | "source": [ 491 | "### 点评\n", 492 | "\n", 493 | "上述客户端代码可以用自然语言描述为:\n", 494 | "\n", 495 | "- 实例化没穿衣服的人类\n", 496 | "- 为没穿衣服的人类穿上T恤\n", 497 | "- 为穿着T恤的人类穿上垮裤\n", 498 | "- 为穿着T恤,垮裤的人类穿上运动鞋\n", 499 | "\n", 500 | "所以,装饰模型是为已有功能动态的添加更多功能的一种方式,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户端代码可以在运行时根据需要有选择的,按顺序的使用装饰功能包装对象。\n", 501 | "\n", 502 | "装饰模式的优点在于,把类中的装饰功能从类中搬移出去,这样可以简化原有的类,有效的把类的`核心职责`和`装饰功能`区分开了。而且可以去除相关类中重复的装饰逻辑,即一个装饰功能可以给多个不同的类使用。" 503 | ] 504 | } 505 | ], 506 | "metadata": { 507 | "kernelspec": { 508 | "display_name": "Python 3", 509 | "language": "python", 510 | "name": "python3" 511 | }, 512 | "language_info": { 513 | "codemirror_mode": { 514 | "name": "ipython", 515 | "version": 3 516 | }, 517 | "file_extension": ".py", 518 | "mimetype": "text/x-python", 519 | "name": "python", 520 | "nbconvert_exporter": "python", 521 | "pygments_lexer": "ipython3", 522 | "version": "3.6.4" 523 | } 524 | }, 525 | "nbformat": 4, 526 | "nbformat_minor": 2 527 | } 528 | -------------------------------------------------------------------------------- /第7章 找人帮忙追美眉——代理模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 题目1\n", 8 | "\n", 9 | "Boy追求Girl,给Girl送鲜花,送巧克力,送洋娃娃。" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 4, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "曼曼, 送你洋娃娃\n", 22 | "曼曼, 送你巧克力\n", 23 | "曼曼, 送你鲜花\n" 24 | ] 25 | } 26 | ], 27 | "source": [ 28 | "class Boy():\n", 29 | " def __init__(self, girl):\n", 30 | " self.girl = girl\n", 31 | " \n", 32 | " def give_dolls(self):\n", 33 | " print(\"{}, 送你洋娃娃\".format(self.girl.name))\n", 34 | " \n", 35 | " def give_flowers(self):\n", 36 | " print(\"{}, 送你鲜花\".format(self.girl.name))\n", 37 | "\n", 38 | " def give_chocolate(self):\n", 39 | " print(\"{}, 送你巧克力\".format(self.girl.name))\n", 40 | " \n", 41 | "class Girl():\n", 42 | " def __init__(self, name):\n", 43 | " self.name = name\n", 44 | " \n", 45 | "def main():\n", 46 | " mm = Girl(\"曼曼\")\n", 47 | " gg = Boy(mm)\n", 48 | " \n", 49 | " gg.give_dolls()\n", 50 | " gg.give_chocolate()\n", 51 | " gg.give_flowers()\n", 52 | "\n", 53 | "main()" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "## 题目2\n", 61 | "假设Boy并不认识Girl,Boy希望让Proxy帮他把鲜花,巧克力,洋娃娃转交给Girl。" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 5, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "曼曼, 送你洋娃娃\n", 74 | "曼曼, 送你巧克力\n", 75 | "曼曼, 送你鲜花\n" 76 | ] 77 | } 78 | ], 79 | "source": [ 80 | "class Proxy():\n", 81 | " def __init__(self, girl):\n", 82 | " self.girl = girl\n", 83 | " \n", 84 | " def give_dolls(self):\n", 85 | " print(\"{}, 送你洋娃娃\".format(self.girl.name))\n", 86 | " \n", 87 | " def give_flowers(self):\n", 88 | " print(\"{}, 送你鲜花\".format(self.girl.name))\n", 89 | "\n", 90 | " def give_chocolate(self):\n", 91 | " print(\"{}, 送你巧克力\".format(self.girl.name))\n", 92 | " \n", 93 | "class Girl():\n", 94 | " def __init__(self, name):\n", 95 | " self.name = name\n", 96 | " \n", 97 | "def main():\n", 98 | " mm = Girl(\"曼曼\")\n", 99 | " proxy = Proxy(mm)\n", 100 | " \n", 101 | " proxy.give_dolls()\n", 102 | " proxy.give_chocolate()\n", 103 | " proxy.give_flowers()\n", 104 | "\n", 105 | "main()" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## 点评\n", 113 | "这样看上去的确是Proxy送给Girl的各种礼物,但是这些礼物其实是Boy的,在代码中并没有体现这一点。在代码中如何体现表面上是Proxy送的礼物,实际上是Boy送的礼物呢?这需要用到代理模式,把送礼物封装成接口,然后让Boy和Proxy都去实现这个接口,Boy和Proxy的区别就是Proxy的接口实现要调用Boy的接口实现。\n", 114 | "\n", 115 | "Python中没有明确的接口定义方法,抽象接口可以看作编程人员对自己编码行为的约束,让自己按照一定的规矩写代码。为了实现同样的目的,在Python中可以通过定义一个抽象类来达到相同的目的。" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 7, 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "name": "stdout", 125 | "output_type": "stream", 126 | "text": [ 127 | "曼曼, 送你洋娃娃\n", 128 | "曼曼, 送你巧克力\n", 129 | "曼曼, 送你鲜花\n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "from abc import ABCMeta, abstractmethod\n", 135 | "\n", 136 | "\n", 137 | "class IGiveGift():\n", 138 | "\n", 139 | " __metaclass__ = ABCMeta\n", 140 | " \n", 141 | " @abstractmethod\n", 142 | " def give_dolls(self):\n", 143 | " pass\n", 144 | " \n", 145 | " @abstractmethod\n", 146 | " def give_flowers(self):\n", 147 | " pass\n", 148 | " \n", 149 | " @abstractmethod\n", 150 | " def give_chocolate(self):\n", 151 | " pass\n", 152 | " \n", 153 | " \n", 154 | "class Boy(IGiveGift):\n", 155 | " \n", 156 | " def __init__(self, girl):\n", 157 | " self.girl = girl\n", 158 | " \n", 159 | " def give_dolls(self):\n", 160 | " print(\"{}, 送你洋娃娃\".format(self.girl.name))\n", 161 | " \n", 162 | " def give_flowers(self):\n", 163 | " print(\"{}, 送你鲜花\".format(self.girl.name))\n", 164 | "\n", 165 | " def give_chocolate(self):\n", 166 | " print(\"{}, 送你巧克力\".format(self.girl.name))\n", 167 | " \n", 168 | " \n", 169 | "class Proxy(IGiveGift):\n", 170 | " \n", 171 | " def __init__(self, girl):\n", 172 | " self.gg = Boy(girl)\n", 173 | " \n", 174 | " def give_dolls(self):\n", 175 | " self.gg.give_dolls()\n", 176 | " \n", 177 | " def give_flowers(self):\n", 178 | " self.gg.give_flowers()\n", 179 | "\n", 180 | " def give_chocolate(self):\n", 181 | " self.gg.give_chocolate()\n", 182 | " \n", 183 | "def main():\n", 184 | " mm = Girl(\"曼曼\")\n", 185 | " proxy = Proxy(mm)\n", 186 | " \n", 187 | " proxy.give_dolls()\n", 188 | " proxy.give_chocolate()\n", 189 | " proxy.give_flowers()\n", 190 | "\n", 191 | "main()" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "### 点评\n", 199 | "现在从代码中可以清晰的看出,表面上是proxy送给Girl mm的礼物,但是其实是Boy gg送的。\n", 200 | "\n", 201 | "## 代理模式\n", 202 | "代理模式,为其他对象提供一种代理以控制对这个对象的访问。在上述场景中,mm和gg并没有直接的联系,即mm和gg互相无法访问到,但是可以通过proxy来达到gg送mm礼物的效果。\n", 203 | "\n", 204 | "代理模式主要包括三种类:\n", 205 | "- Subject类:例如IGiveGift类,定义了RealSubject和Proxy的功用接口,这样就在任何使用RealSubject的地方使用Proxy;\n", 206 | "- RealSubject类:例如Boy类,定义Proxy所代表的真实实体;\n", 207 | "- Proxy类:例如Porxy类,保存一个引用是的代理可以访问实体,并提供一个于Subject的接口相同的接口,这样代理就可以用来代替实体;\n", 208 | "\n", 209 | "代理模式应用主要包括以下几种:\n", 210 | "- 远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实[DP]。\n", 211 | "- 虚拟代理,是根据需要创建开销很大的对象。通过它来存在实例化需要很长时间的真实对象[DP]。浏览器优化下载就是用的代理模式,例如打开一个很大的HTML网页时,里面可能由很多文字和图片,但是依然可以很快打开,优先看到文字,但是图片还在一张一张下载。哪些未打开的图片框,就是通过虚拟代理来替代真实图片,此时代理存储了真实图片的路径和尺寸。\n", 212 | "- 安全代理,用来控制真实对象访问时的权限[DP]。一般用于对象应该有不同的访问权限的时候。\n", 213 | "- 智能指引,是指当调用真实的对象时,代理处理另外一些事[DP]。例如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它;或者当第一引用一个持久对象时,将它装入内存;或在访问一个实际对象前,检查是否已经锁定它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理。" 214 | ] 215 | } 216 | ], 217 | "metadata": { 218 | "kernelspec": { 219 | "display_name": "Python 3", 220 | "language": "python", 221 | "name": "python3" 222 | }, 223 | "language_info": { 224 | "codemirror_mode": { 225 | "name": "ipython", 226 | "version": 3 227 | }, 228 | "file_extension": ".py", 229 | "mimetype": "text/x-python", 230 | "name": "python", 231 | "nbconvert_exporter": "python", 232 | "pygments_lexer": "ipython3", 233 | "version": "3.6.4" 234 | } 235 | }, 236 | "nbformat": 4, 237 | "nbformat_minor": 2 238 | } 239 | -------------------------------------------------------------------------------- /第8章 学习雷锋好榜样——工厂方法模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 简单工厂模式 v.s. 工厂方法模式\n", 8 | "\n", 9 | "以简单计算器为例,对比一下简单工厂模式和工厂方法模式的区别。\n", 10 | "\n", 11 | "### 简单工厂模式" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "input a number:99\n", 24 | "input a operater(+ - * /):/\n", 25 | "input a number:9\n", 26 | "11.0\n" 27 | ] 28 | } 29 | ], 30 | "source": [ 31 | "from abc import ABCMeta, abstractmethod\n", 32 | "\n", 33 | "\n", 34 | "class Operation():\n", 35 | " \"\"\"\n", 36 | " 抽象产品类(运算符类)\n", 37 | " \"\"\"\n", 38 | " __metaclass__ = ABCMeta\n", 39 | "\n", 40 | " def __init__(self):\n", 41 | " self.result = None\n", 42 | "\n", 43 | " @abstractmethod\n", 44 | " def get_result(self):\n", 45 | " pass\n", 46 | "\n", 47 | " \n", 48 | "class AddOperation(Operation):\n", 49 | " \"\"\"\n", 50 | " 具体产品类(加法运算符)\n", 51 | " \"\"\"\n", 52 | " def get_result(self, number_a, number_b):\n", 53 | " self.result = number_a + number_b\n", 54 | " return self.result\n", 55 | "\n", 56 | " \n", 57 | "class SubOperation(Operation):\n", 58 | " \"\"\"\n", 59 | " 具体产品类(减法运算符)\n", 60 | " \"\"\"\n", 61 | " def get_result(self, number_a, number_b):\n", 62 | " self.result = number_a - number_b\n", 63 | " return self.result\n", 64 | "\n", 65 | " \n", 66 | "class MulOperation(Operation):\n", 67 | " \"\"\"\n", 68 | " 具体产品类(乘法运算符)\n", 69 | " \"\"\"\n", 70 | " def get_result(self, number_a, number_b):\n", 71 | " self.result = number_a * number_b\n", 72 | " return self.result\n", 73 | " \n", 74 | " \n", 75 | "class DivOperation(Operation):\n", 76 | " \"\"\"\n", 77 | " 具体产品类(除法运算符)\n", 78 | " \"\"\"\n", 79 | " def get_result(self, number_a, number_b):\n", 80 | " if number_b == 0:\n", 81 | " print(\"With operator '/', the second number can not be zero.\")\n", 82 | " return self.result\n", 83 | " self.result = number_a / number_b\n", 84 | " return self.result\n", 85 | " \n", 86 | " \n", 87 | "class OperationFactory():\n", 88 | " \"\"\"\n", 89 | " 产品工厂类\n", 90 | " \"\"\"\n", 91 | " @classmethod\n", 92 | " def create_operate(self, operator):\n", 93 | " oper = None\n", 94 | " if operator == \"+\":\n", 95 | " oper = AddOperation()\n", 96 | " elif operator == \"-\":\n", 97 | " oper = SubOperation()\n", 98 | " elif operator == \"*\":\n", 99 | " oper = MulOperation()\n", 100 | " elif operator == \"/\":\n", 101 | " oper = DivOperation()\n", 102 | " else:\n", 103 | " print(\"Wrong operator.\")\n", 104 | " return oper\n", 105 | "\n", 106 | "number_a = int(input(\"input a number:\"))\n", 107 | "operator = str(input(\"input a operater(+ - * /):\"))\n", 108 | "number_b = int(input(\"input a number:\"))\n", 109 | "\n", 110 | "oper = OperationFactory.create_operate(operator)\n", 111 | "print(oper.get_result(number_a, number_b))" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "### 工厂方法模式" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 5, 124 | "metadata": {}, 125 | "outputs": [ 126 | { 127 | "name": "stdout", 128 | "output_type": "stream", 129 | "text": [ 130 | "input a number:99\n", 131 | "input a operater(+ - * /):/\n", 132 | "input a number:11\n", 133 | "9.0\n" 134 | ] 135 | } 136 | ], 137 | "source": [ 138 | "from abc import ABCMeta, abstractmethod\n", 139 | "\n", 140 | "\n", 141 | "class IFactory():\n", 142 | " \"\"\"\n", 143 | " 通用工厂接口\n", 144 | " \"\"\"\n", 145 | " __metaclass__ = ABCMeta\n", 146 | " \n", 147 | " @abstractmethod\n", 148 | " def create_operation(self):\n", 149 | " pass\n", 150 | "\n", 151 | " \n", 152 | "class AddFactory(IFactory):\n", 153 | " \"\"\"\n", 154 | " 实现工厂接口的加法工厂类\n", 155 | " \"\"\"\n", 156 | " def create_operation(self):\n", 157 | " return AddOperation()\n", 158 | "\n", 159 | " \n", 160 | "class SubFactory(IFactory):\n", 161 | " \"\"\"\n", 162 | " 实现工厂接口的剑法工厂类\n", 163 | " \"\"\"\n", 164 | " def create_operation(self):\n", 165 | " return SubOperation()\n", 166 | "\n", 167 | " \n", 168 | "class MulFactory(IFactory):\n", 169 | " \"\"\"\n", 170 | " 实现工厂接口的乘法工厂类\n", 171 | " \"\"\"\n", 172 | " def create_operation(self):\n", 173 | " return MulOperation()\n", 174 | " \n", 175 | "\n", 176 | "class DivFactory(IFactory):\n", 177 | " \"\"\"\n", 178 | " 实现工厂接口的除法工厂类\n", 179 | " \"\"\"\n", 180 | " def create_operation(self):\n", 181 | " return DivOperation()\n", 182 | " \n", 183 | "\n", 184 | "def main():\n", 185 | " number_a = int(input(\"input a number:\"))\n", 186 | " operator = str(input(\"input a operater(+ - * /):\"))\n", 187 | " number_b = int(input(\"input a number:\"))\n", 188 | "\n", 189 | " if operator == \"+\":\n", 190 | " oper_factory = AddFactory()\n", 191 | " elif operator == \"-\":\n", 192 | " oper_factory = SubFactory()\n", 193 | " elif operator == \"*\":\n", 194 | " oper_factory = MulFactory()\n", 195 | " elif operator == \"/\":\n", 196 | " oper_factory = DivFactory()\n", 197 | " else:\n", 198 | " print(\"Wrong operator.\")\n", 199 | "\n", 200 | " oper = oper_factory.create_operation()\n", 201 | " print(oper.get_result(number_a, number_b))\n", 202 | " \n", 203 | "main()" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "### 点评\n", 211 | "\n", 212 | "#### 工厂方法更复杂了?\n", 213 | "\n", 214 | "如果需要增加其他运算,比如求M的N次方。\n", 215 | "\n", 216 | "在简单工厂模式里,先增加一个求M的N次方的产品类,然后更改工厂类的if判断增加分支即可。\n", 217 | "\n", 218 | "在工厂方法模式里,先增加一个求M的N次方的产品类,还要新增一个相关工厂类,最后还有修改客户端代码。\n", 219 | "\n", 220 | "这就是简单工厂和工厂方法的区别所在。简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关类,对于客户端来说,去除了与具体产品的依赖。但是,如果要增加一个新的功能,比如求M的N次方,需要更改工厂类的if判断分支条件,修改原有的类?违背了开放-封闭原则,这可不是好方法。所以就需要工厂方法模式来处理。\n", 221 | "\n", 222 | "## 工厂方法模式\n", 223 | "\n", 224 | "工厂方法模式,定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类[DP]。\n", 225 | "\n", 226 | "相当于将简单工厂中的工厂类,变成了一个工厂抽象接口和多个具体生成对象的工厂,于是我们要增加求M的N次方的功能,就不需要更改工厂类,只需要增加此功能的运算类和相应的工厂类即可。这样整个工厂和产品体系其实都没有修改,而只是扩展,这就完全符合了开放-封闭原则。\n", 227 | "\n", 228 | "但是,工厂方法模式是现实,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。要增加新功能,本来修改工厂类,现在修改客户端了。\n", 229 | "\n", 230 | "## 题目\n", 231 | "\n", 232 | "木叶学校组织学雷锋活动,让鸣人,小樱,佐助帮敬老院的老人扫地,洗衣,买米,如何实现?" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 8, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "扫地\n", 245 | "洗衣\n", 246 | "买米\n" 247 | ] 248 | } 249 | ], 250 | "source": [ 251 | "class LeiFeng():\n", 252 | " \n", 253 | " def sweep(self):\n", 254 | " print(\"扫地\")\n", 255 | " \n", 256 | " def wash(self):\n", 257 | " print(\"洗衣\")\n", 258 | " \n", 259 | " def buy_rice(self):\n", 260 | " print(\"买米\")\n", 261 | " \n", 262 | "class Student(LeiFeng):\n", 263 | " pass\n", 264 | "\n", 265 | "def main():\n", 266 | " mingren = Student()\n", 267 | " xiaoying = Student()\n", 268 | " zuozhu = Student()\n", 269 | " \n", 270 | " mingren.sweep()\n", 271 | " xiaoying.wash()\n", 272 | " zuozhu.buy_rice()\n", 273 | "main()" 274 | ] 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "metadata": {}, 279 | "source": [ 280 | "### 点评\n", 281 | "- 学生都会毕业,但是帮助老人是长期工作,所以每次不同的人帮助老人,都需要改客户端代码,而且老人不可能知道所有来帮忙的学生的名字;\n", 282 | "- 除了学生,社区志愿者也可以帮助老人\n", 283 | "\n", 284 | "如何用简单工厂方法解决上述问题?" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 11, 290 | "metadata": {}, 291 | "outputs": [ 292 | { 293 | "name": "stdout", 294 | "output_type": "stream", 295 | "text": [ 296 | "买米\n", 297 | "洗衣\n", 298 | "扫地\n" 299 | ] 300 | } 301 | ], 302 | "source": [ 303 | "class Volunteer(LeiFeng):\n", 304 | " pass\n", 305 | "\n", 306 | "\n", 307 | "class SimpleFactory():\n", 308 | " \n", 309 | " @classmethod\n", 310 | " def create_leifeng(self, leifeng_type):\n", 311 | " self.leifeng = None\n", 312 | " if leifeng_type == \"学生\":\n", 313 | " self.leifeng = Student()\n", 314 | " elif leifeng_type == \"志愿者\":\n", 315 | " self.leifeng = Volunteer()\n", 316 | " else:\n", 317 | " print(\"ERROR LeiFeng Type\")\n", 318 | " return self.leifeng\n", 319 | " \n", 320 | "def main():\n", 321 | " studentA = SimpleFactory.create_leifeng(\"学生\")\n", 322 | " studentA.buy_rice()\n", 323 | " studentB = SimpleFactory.create_leifeng(\"学生\")\n", 324 | " studentB.wash()\n", 325 | " studentC = SimpleFactory.create_leifeng(\"学生\")\n", 326 | " studentB.sweep()\n", 327 | " \n", 328 | "main()" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "metadata": {}, 334 | "source": [ 335 | "### 点评\n", 336 | "- 好的地方,客户端的代码,如果要换志愿者,只需要换参数即可;\n", 337 | "- 坏的地方,在任何实例化的时候都需要写一句`SimpleFactory.create_leifeng(\"学生\")`,这会导致大量重复,在修改为`志愿者`的时候非常麻烦,可以用工厂方法解决这个问题;" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": 13, 343 | "metadata": {}, 344 | "outputs": [ 345 | { 346 | "name": "stdout", 347 | "output_type": "stream", 348 | "text": [ 349 | "扫地\n", 350 | "洗衣\n", 351 | "买米\n" 352 | ] 353 | } 354 | ], 355 | "source": [ 356 | "from abc import ABCMeta, abstractmethod\n", 357 | "\n", 358 | "\n", 359 | "class ILeiFengFactory():\n", 360 | " __metaclass__ = ABCMeta\n", 361 | " \n", 362 | " @abstractmethod\n", 363 | " def create_leifeng(self):\n", 364 | " pass\n", 365 | "\n", 366 | " \n", 367 | "class StudentFactory(ILeiFengFactory):\n", 368 | " \n", 369 | " def create_leifeng(self):\n", 370 | " return Student()\n", 371 | "\n", 372 | " \n", 373 | "class VolunteerFactory(ILeiFengFactory):\n", 374 | " \n", 375 | " def create_leifeng(self):\n", 376 | " return Volunteer()\n", 377 | "\n", 378 | "def main():\n", 379 | " leifeng_factory = StudentFactory()\n", 380 | " stu1 = leifeng_factory.create_leifeng()\n", 381 | " stu2 = leifeng_factory.create_leifeng()\n", 382 | " stu3 = leifeng_factory.create_leifeng()\n", 383 | " \n", 384 | " stu1.sweep()\n", 385 | " stu2.wash()\n", 386 | " stu3.buy_rice()\n", 387 | " \n", 388 | "main()" 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "metadata": {}, 394 | "source": [ 395 | "### 点评\n", 396 | "- 此时如果要将学生改成志愿者,只需要修改一行代码即可;\n", 397 | "- 工厂方法克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点;\n", 398 | "\n", 399 | "## 总结\n", 400 | "简单工厂和工厂方法都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可以实现,降低了客户程序和产品对象的耦合。\n", 401 | "\n", 402 | "工厂方法是简单工厂模式的进一步抽象和推广,由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但缺点是由于每加一个产品,就需要加一个产品工厂类,增加了额外开发量。\n", 403 | "\n", 404 | "另外,工厂方法还是没有避免修改客户端的代码,可以利用`反射`解决避免分支判断的问题。" 405 | ] 406 | } 407 | ], 408 | "metadata": { 409 | "kernelspec": { 410 | "display_name": "Python 3", 411 | "language": "python", 412 | "name": "python3" 413 | }, 414 | "language_info": { 415 | "codemirror_mode": { 416 | "name": "ipython", 417 | "version": 3 418 | }, 419 | "file_extension": ".py", 420 | "mimetype": "text/x-python", 421 | "name": "python", 422 | "nbconvert_exporter": "python", 423 | "pygments_lexer": "ipython3", 424 | "version": "3.6.4" 425 | } 426 | }, 427 | "nbformat": 4, 428 | "nbformat_minor": 2 429 | } 430 | -------------------------------------------------------------------------------- /第9章 如何准备多份简历——原型模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 题目\n", 8 | "设计一个简历类,必须有姓名,可以设置性别和年龄,即个人信息,可以设置曾就职公司和工作时间,即工作经历。\n", 9 | "\n", 10 | "## 基础版本" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 3, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "鸣人\t男\t29\n", 23 | "2016-2018\t木叶公司\n", 24 | "鸣人\t男\t29\n", 25 | "2016-2018\t木叶公司\n", 26 | "鸣人\t男\t29\n", 27 | "2016-2018\t木叶公司\n" 28 | ] 29 | } 30 | ], 31 | "source": [ 32 | "class Resume():\n", 33 | " \n", 34 | " def __init__(self, name):\n", 35 | " self.name = name # python默认成员变量公开\n", 36 | " self.__sex = None # python默认成员变量公开,加__表示私有\n", 37 | " self.__age = None # python默认成员变量公开,加__表示私有\n", 38 | " self.__time_area = None # python默认成员变量公开,加__表示私有\n", 39 | " self.__company = None # python默认成员变量公开,加__表示私有\n", 40 | " \n", 41 | " def set_personal_info(self, sex, age):\n", 42 | " self.__sex = sex\n", 43 | " self.__age = age\n", 44 | " \n", 45 | " def set_work_experience(self, time_area, company):\n", 46 | " self.__time_area = time_area\n", 47 | " self.__company = company\n", 48 | " \n", 49 | " def display(self):\n", 50 | " print(\"{}\\t{}\\t{}\".format(self.name, self.__sex, self.__age))\n", 51 | " print(\"{}\\t{}\".format(self.__time_area, self.__company))\n", 52 | " \n", 53 | "def main():\n", 54 | " resume_a = Resume(\"鸣人\")\n", 55 | " resume_a.set_personal_info(\"男\", \"29\")\n", 56 | " resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n", 57 | " \n", 58 | " resume_b = Resume(\"鸣人\")\n", 59 | " resume_b.set_personal_info(\"男\", \"29\")\n", 60 | " resume_b.set_work_experience(\"2016-2018\", \"木叶公司\")\n", 61 | " \n", 62 | " resume_c = Resume(\"鸣人\")\n", 63 | " resume_c.set_personal_info(\"男\", \"29\")\n", 64 | " resume_c.set_work_experience(\"2016-2018\", \"木叶公司\")\n", 65 | " \n", 66 | " resume_a.display()\n", 67 | " resume_b.display()\n", 68 | " resume_c.display()\n", 69 | " \n", 70 | "main()" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "### 点评\n", 78 | "- 上述main函数中生成简历的方法,相当于手写简历,三份简历要三次实例化\n", 79 | "- 而且如果要更改某个字段,比如把时间从`2016-2018`改成`2017-2018`,那么同样修改三次\n", 80 | "\n", 81 | "那如果这样写呢?" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 4, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "name": "stdout", 91 | "output_type": "stream", 92 | "text": [ 93 | "鸣人\t男\t29\n", 94 | "2016-2018\t木叶公司\n", 95 | "鸣人\t男\t29\n", 96 | "2016-2018\t木叶公司\n", 97 | "鸣人\t男\t29\n", 98 | "2016-2018\t木叶公司\n" 99 | ] 100 | } 101 | ], 102 | "source": [ 103 | "def main():\n", 104 | " resume_a = Resume(\"鸣人\")\n", 105 | " resume_a.set_personal_info(\"男\", \"29\")\n", 106 | " resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n", 107 | " \n", 108 | " resume_b = resume_a\n", 109 | " \n", 110 | " resume_c = resume_a\n", 111 | " \n", 112 | " resume_a.display()\n", 113 | " resume_b.display()\n", 114 | " resume_c.display()\n", 115 | " \n", 116 | "main()" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "### 点评\n", 124 | "- 这里传递的是引用,而不是具体的值,相当于在简历b和简历c上没有实际内容,而是写着“详见简历a”\n", 125 | "- 可以使用clone的方法解决这个问题,即原型模式\n", 126 | "\n", 127 | "## 原型模式\n", 128 | "\n", 129 | "原型模式,即用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象[DP]。也就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 8, 135 | "metadata": {}, 136 | "outputs": [ 137 | { 138 | "name": "stdout", 139 | "output_type": "stream", 140 | "text": [ 141 | "1\n" 142 | ] 143 | } 144 | ], 145 | "source": [ 146 | "from abc import ABCMeta, abstractmethod\n", 147 | "from copy import copy\n", 148 | "\n", 149 | "class Prototype():\n", 150 | " \"\"\"\n", 151 | " 抽象原型类\n", 152 | " \"\"\"\n", 153 | " __metaclass__ = ABCMeta\n", 154 | " def __init__(self, id):\n", 155 | " self.id = id\n", 156 | " \n", 157 | " @abstractmethod\n", 158 | " def clone(self):\n", 159 | " pass\n", 160 | "\n", 161 | "class ConcretePrototypeOne(Prototype):\n", 162 | " \"\"\"\n", 163 | " 具体原型类\n", 164 | " \"\"\"\n", 165 | " def __init__(self, id):\n", 166 | " super().__init__(id)\n", 167 | " \n", 168 | " def clone(self):\n", 169 | " return copy(self) # 1. 浅拷贝copy.copy() 或 深拷贝copy.deepcopy() 2. Python无需强制类型转换\n", 170 | "\n", 171 | "def main():\n", 172 | " prototype1 = ConcretePrototypeOne(\"1\")\n", 173 | " prototype1_cloned = prototype1.clone() \n", 174 | " print(prototype1_cloned.id)\n", 175 | " \n", 176 | "main()" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "### Python中的浅拷贝与深拷贝\n", 184 | "\n", 185 | "Python中的对象之间赋值时是按引用传递的,如果需要拷贝对象,需要使用标准库中的copy模块。\n", 186 | "\n", 187 | "- copy.copy(浅拷贝):只拷贝顶层对象,不会拷贝顶层对象的内部的对象成员变量;\n", 188 | "- copy.deepcopy(深拷贝):拷贝对象及其子对象\n", 189 | "\n", 190 | "按照基础版本的简历类定义,成员变量的类型都是基本数据类型(string),所以使用浅拷贝即可。那么什么时候用深拷贝呢?假如我们将工作经历定义为一个单独的类WorkExperience,那么简历类中就会有一个成员变量的类型是WorkExperience,如果这时候需要拷贝操作,就需要用深拷贝了。\n", 191 | "\n", 192 | "### 深拷贝原型模式" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 19, 198 | "metadata": {}, 199 | "outputs": [ 200 | { 201 | "name": "stdout", 202 | "output_type": "stream", 203 | "text": [ 204 | "---浅拷贝, 工作经历都被修改成最后一次的值---\n", 205 | "鸣人\t男\t29\n", 206 | "2019-2020\t问问公司\n", 207 | "鸣人\t男\t29\n", 208 | "2019-2020\t问问公司\n", 209 | "鸣人\t男\t24\n", 210 | "2019-2020\t问问公司\n", 211 | "--------深拷贝, 工作经历为不同的值--------\n", 212 | "鸣人\t男\t29\n", 213 | "2016-2018\t木叶公司\n", 214 | "鸣人\t男\t29\n", 215 | "2018-2019\t王者学校\n", 216 | "鸣人\t男\t24\n", 217 | "2019-2020\t问问公司\n" 218 | ] 219 | } 220 | ], 221 | "source": [ 222 | "from copy import deepcopy\n", 223 | "\n", 224 | "\n", 225 | "class WorkExperience():\n", 226 | "\n", 227 | " def __init__(self, time_area=\"\", company=\"\"):\n", 228 | " self.time_area = time_area\n", 229 | " self.company = company\n", 230 | " \n", 231 | "class Resume():\n", 232 | " \n", 233 | " def __init__(self, name):\n", 234 | " self.name = name # python默认成员变量公开\n", 235 | " self.__sex = None # python默认成员变量公开,加__表示私有\n", 236 | " self.__age = None # python默认成员变量公开,加__表示私有\n", 237 | " self.__work_expereince = WorkExperience() # python默认成员变量公开,加__表示私有\n", 238 | " \n", 239 | " def set_personal_info(self, sex, age):\n", 240 | " self.__sex = sex\n", 241 | " self.__age = age\n", 242 | " \n", 243 | " def set_work_experience(self, time_area, company):\n", 244 | " self.__work_expereince.time_area = time_area\n", 245 | " self.__work_expereince.company = company\n", 246 | " \n", 247 | " def display(self):\n", 248 | " print(\"{}\\t{}\\t{}\".format(self.name, self.__sex, self.__age))\n", 249 | " print(\"{}\\t{}\".format(self.__work_expereince.time_area, self.__work_expereince.company))\n", 250 | " \n", 251 | " def deep_clone(self):\n", 252 | " \"\"\"\n", 253 | " 深拷贝方法\n", 254 | " \"\"\"\n", 255 | " return deepcopy(self)\n", 256 | " \n", 257 | " def clone(self):\n", 258 | " \"\"\"\n", 259 | " 浅拷贝方法\n", 260 | " \"\"\"\n", 261 | " return copy(self)\n", 262 | " \n", 263 | "\n", 264 | "def main():\n", 265 | " resume_a = Resume(\"鸣人\")\n", 266 | " resume_a.set_personal_info(\"男\", \"29\")\n", 267 | " resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n", 268 | " \n", 269 | " resume_b = resume_a.clone()\n", 270 | " resume_b.set_work_experience(\"2018-2019\", \"王者学校\")\n", 271 | " \n", 272 | " resume_c = resume_a.clone()\n", 273 | " resume_c.set_personal_info(\"男\", \"24\")\n", 274 | " resume_c.set_work_experience(\"2019-2020\", \"问问公司\")\n", 275 | " \n", 276 | " resume_a.display()\n", 277 | " resume_b.display()\n", 278 | " resume_c.display()\n", 279 | " \n", 280 | "def deep_main():\n", 281 | " resume_a = Resume(\"鸣人\")\n", 282 | " resume_a.set_personal_info(\"男\", \"29\")\n", 283 | " resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n", 284 | " \n", 285 | " resume_b = resume_a.deep_clone()\n", 286 | " resume_b.set_work_experience(\"2018-2019\", \"王者学校\")\n", 287 | " \n", 288 | " resume_c = resume_a.deep_clone()\n", 289 | " resume_c.set_personal_info(\"男\", \"24\")\n", 290 | " resume_c.set_work_experience(\"2019-2020\", \"问问公司\")\n", 291 | " \n", 292 | " resume_a.display()\n", 293 | " resume_b.display()\n", 294 | " resume_c.display()\n", 295 | "\n", 296 | "print(\"---浅拷贝, 工作经历都被修改成最后一次的值---\")\n", 297 | "main()\n", 298 | "print(\"--------深拷贝, 工作经历为不同的值--------\")\n", 299 | "deep_main()" 300 | ] 301 | } 302 | ], 303 | "metadata": { 304 | "kernelspec": { 305 | "display_name": "Python 3", 306 | "language": "python", 307 | "name": "python3" 308 | }, 309 | "language_info": { 310 | "codemirror_mode": { 311 | "name": "ipython", 312 | "version": 3 313 | }, 314 | "file_extension": ".py", 315 | "mimetype": "text/x-python", 316 | "name": "python", 317 | "nbconvert_exporter": "python", 318 | "pygments_lexer": "ipython3", 319 | "version": "3.6.4" 320 | } 321 | }, 322 | "nbformat": 4, 323 | "nbformat_minor": 2 324 | } 325 | --------------------------------------------------------------------------------