diff --git a/BehavioralPatterns/ChainOfReponsibility/README.md b/BehavioralPatterns/ChainOfReponsibility/README.md new file mode 100644 index 000000000..10863d928 --- /dev/null +++ b/BehavioralPatterns/ChainOfReponsibility/README.md @@ -0,0 +1,61 @@ +# 职责链模式 Chain of Responsibility +## 意图 +通过设立有机会处理请求的中间对象,将请求的发起者和接受者解耦。链接这些接收对象、传递请求,直到该请求被处理。 + +经典的消息机制、异常处理都是这样一种职责链模式。发起的消息逐层向外传递,直到有处理该消息的对象出现。 + +## 适用性 +* 多个对象会处理请求,不同处理者间的优先级不明确,需要能够自动获知该由谁处理; +* 需要处理一类请求,可能来自不同对象,且没有显示地指明接受者; +* 能够处理请求的一组对象,能够被动态指定。 + +## 结构 + +![structure](./res/ChainOfResponsibilityStructure.png) + +典型的对象结构: + +![structure](./res/ChainOfResponsibilityObject.png) + +各部分角色的职责: + +* Handler + * 定义处理请求的接口 + * 实现继任者(successor)链接 +* ConcreteHandler + * 处理负责的请求 + * 如果不是其责任范围内的请求,负责将其传递给继任者 +* Client + * 面向的是ConcreteHandler + * 调用接口 + +## 模式效果 +1. 减少耦合 + + 对象只需要知道请求会被“妥善”处理,而不用关心是由谁处理;接受者无需知道请求的发起者;链式结构中的对象也无需了解整个链的结构。 + + 结果是,职责链模式简化了对象间的关联,对象无需维护请求的接受者的引用,只要持有后继者的引用即可。 +2. 分派责任更加灵活 + + 能够在运行时添加或修改对象所负担的责任。 +3. 请求不保证会被执行 + + 由于没有明确的责任对象,无法保证请求会最终被执行。也有可能由于职责链配置问题导致请求不被执行。 + +## 实现 +1. 实现继任者链有两种可行的方式: + * 定义新的链接(通常在Handler中,但ConcreteHandler需要重新定义) + * 使用已有的链接 + + 使用已有的继任者链接,意味着ConcreteHandler 创建时其继任者已经决定?这部分看得还不是太明白,需要之后结合代码再来看。 +2. 连接继任者 + + 如果没有已定义的链,就必须自己创建。这种情况下,Handler除了定义接口,还要定义继任者。 +3. 表达请求 + + 有多种表达请求的方式,最简单的,是硬编码,讲请求直接写在Handler中,缺点是扩展性不好。 + + 另一个选择是使用请求编号的方式(例如用一个整型值),根据编码决定如何请求。该做法要求发送者和接受者明确编码的含义。这一做法更为灵活,但需要额外的请求分发工作,而且不是类型安全。 + + 参数可用objects的方式,单独传输。 + diff --git a/BehavioralPatterns/ChainOfReponsibility/chain.py b/BehavioralPatterns/ChainOfReponsibility/chain.py new file mode 100644 index 000000000..d9b9e2806 --- /dev/null +++ b/BehavioralPatterns/ChainOfReponsibility/chain.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://www.testingperspective.com/wiki/doku.php/collaboration/chetan/designpatternsinpython/chain-of-responsibilitypattern""" + +class Handler: + def __init__(self,successor): + self._successor = successor; + + def handle(self,request): + i = self._handle(request) + if not i: + self._successor.handle(request) + + def _handle(self, request): + raise NotImplementedError('Must provide implementation in subclass.') + + +class ConcreteHandler1(Handler): + + def _handle(self, request): + if 0 < request <= 10: + print('request {} handled in handler 1'.format(request)) + return True + +class ConcreteHandler2(Handler): + + def _handle(self, request): + if 10 < request <= 20: + print('request {} handled in handler 2'.format(request)) + return True + +class ConcreteHandler3(Handler): + + def _handle(self, request): + if 20 < request <= 30: + print('request {} handled in handler 3'.format(request)) + return True + +class DefaultHandler(Handler): + + def _handle(self, request): + print('end of chain, no handler for {}'.format(request)) + return True + + +class Client: + + def __init__(self): + self.handler = ConcreteHandler1(ConcreteHandler3(ConcreteHandler2(DefaultHandler(None)))) + + def delegate(self, requests): + for request in requests: + self.handler.handle(request) + + +if __name__ == "__main__": + client = Client() + requests = [2, 5, 14, 22, 18, 3, 35, 27, 20] + client.delegate(requests) + +### OUTPUT ### +# request 2 handled in handler 1 +# request 5 handled in handler 1 +# request 14 handled in handler 2 +# request 22 handled in handler 3 +# request 18 handled in handler 2 +# request 3 handled in handler 1 +# end of chain, no handler for 35 +# request 27 handled in handler 3 +# request 20 handled in handler 2 diff --git a/BehavioralPatterns/ChainOfReponsibility/res/ChainOfResponsibilityObject.png b/BehavioralPatterns/ChainOfReponsibility/res/ChainOfResponsibilityObject.png new file mode 100644 index 000000000..c3e1e87b7 Binary files /dev/null and b/BehavioralPatterns/ChainOfReponsibility/res/ChainOfResponsibilityObject.png differ diff --git a/BehavioralPatterns/ChainOfReponsibility/res/ChainOfResponsibilityStructure.png b/BehavioralPatterns/ChainOfReponsibility/res/ChainOfResponsibilityStructure.png new file mode 100644 index 000000000..95f71c014 Binary files /dev/null and b/BehavioralPatterns/ChainOfReponsibility/res/ChainOfResponsibilityStructure.png differ diff --git a/BehavioralPatterns/Command/README.md b/BehavioralPatterns/Command/README.md new file mode 100644 index 000000000..c7215fada --- /dev/null +++ b/BehavioralPatterns/Command/README.md @@ -0,0 +1,50 @@ +# 命令模式 Command + +## 意图 +将请求封装成对象,由此可用不同的请求作为参数,将请求队列化,日志输出请求,还支持操作的回退。 + +也被称为动作模式(Action)、事务模式(Transaction)。 + +## 适用性 +当你有以下需求时使用命令模式 + +* 将动作执行参数化。命令模式就是回调函数的面向对象的替代。 +* 描述请求、队列缓存请求、不在请求创建时执行请求。 +* 支持回退操作,这要求命令对象除了执行操作外,还提供回退操作的接口。 +* 支持输出请求变化日志,当系统奔溃后能够从断点恢复执行。数据库一般都有类似的操作日志。 +* 在底层操作的基础上使用上层操作构建系统。典型的应用就是数据库操作中的事务(Transaction)。每个事务都包括多个基本的操作,当所有操作都成功后事务才提交,否则回退所有的操作。 + +## 结构 + +![structure](./res/Command.png) + +其中,Client的责任是创建ConcreteCommand,并为其指定Receiver。 + +## 模式效果 + +1. 命令模式将对象调用执行和如何执行解耦。 +2. 命令是一级对象,这意味着能够像其他对象一样操作、扩展。 +3. 可以将多个命令集成为一个复合命令。 +4. 很容易添加新的命令,因为这不会影响已有的类。 + +## 实现 +实现命令模式时需要考虑以下一些问题: + +1. 命令该有多智能? + + 一个极端是仅仅提供了receiver,用来调用请求,另一个极端是完全不管receiver,在命令中将请求实现。 + +2. 支持回退和重新执行 + + 除了提供相应的接口,ConcreteCommand类还需要存储额外的状态,可能包括: + + * 接收请求的对象 + * 操作执行的参数 + * 接收对象的原始状态,确保能够让接受者回退到上一状态 + + 一级回退,只要存储上个命令;多级回退就需要存储历史命令列表。 + + 一个可回退的命令必须支持拷贝,因为需要拷贝到历史命令列表中,而不能仅仅是存储命令的引用,因为命令可能会被修改。 + +3. 在回退操作时避免出现错误累积情况 +4. 考虑使用C++的模板实现 \ No newline at end of file diff --git a/BehavioralPatterns/Command/command.py b/BehavioralPatterns/Command/command.py new file mode 100644 index 000000000..727f42cbf --- /dev/null +++ b/BehavioralPatterns/Command/command.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os + + +class MoveFileCommand(object): + + def __init__(self, src, dest): + self.src = src + self.dest = dest + + def execute(self): + print('renaming {} to {}'.format(self.src, self.dest)) + os.rename(self.src, self.dest) + + def undo(self): + print('renaming {} to {}'.format(self.dest, self.src)) + os.rename(self.dest, self.src) + + +def main(): + command_stack = [] + + # commands are just pushed into the command stack + command_stack.append(MoveFileCommand('foo.txt', 'bar.txt')) + command_stack.append(MoveFileCommand('bar.txt', 'baz.txt')) + + # they can be executed later on + for cmd in command_stack: + cmd.execute() + + # and can also be undone at will + for cmd in reversed(command_stack): + cmd.undo() + +if __name__ == "__main__": + main() + +### OUTPUT ### +# renaming foo.txt to bar.txt +# renaming bar.txt to baz.txt +# renaming baz.txt to bar.txt +# renaming bar.txt to foo.txt diff --git a/BehavioralPatterns/Command/res/Command.png b/BehavioralPatterns/Command/res/Command.png new file mode 100644 index 000000000..003b44fca Binary files /dev/null and b/BehavioralPatterns/Command/res/Command.png differ diff --git a/BehavioralPatterns/Interpreter/README.md b/BehavioralPatterns/Interpreter/README.md new file mode 100644 index 000000000..e8c41bb3b --- /dev/null +++ b/BehavioralPatterns/Interpreter/README.md @@ -0,0 +1,54 @@ +# 解释器模式 Interpreter + +## 意图 +定义语言的语法,并能够使用该语法解释该语言中的句子。 + +## 适用性 +当有一种语言需要翻译,而且能将该语言中的语句用抽象的语法树表示时,可以使用解释器模式。解释器模式在以下情况下运行得最好: + +* 语法简单 + + 复杂的语法就需要复杂的、庞大的、难以管理的类结构。对于这种情况使用像解析生成器(parser generator)这样的工具是一种替代方案,它能够在不创建抽象语法树的前提实现解释语言,能够节省空间和时间。 + +* 效率不是主要的考量 + + 最高效的解释器通常不是通过解释器实现,而是将它们转化成另一种形式。 + +## 结构 + +![structure](./res/Interpreter.png) + +解释器的核心,就是建立这样一个语法树,用户直接面向抽象基类,调用解释方法翻译内容。 + +## 模式效果 + +1. 方便修改和扩展语法。 + + 修改现有类实现修改语法,通过新增类实现扩展语法。 + +2. 实现语法也容易 + + 语法树中的大部分结点代码都比较类似,实现了一个之后其他差不多也都有了。 + +3. 复杂的语法难以维护 + + 在解释器模式中,至少有一个类需要了解语言中所有的语法,所以如果有许多规则,就很难管理和维护。当语法复杂时,其他的技术,例如解析器(parser)、编译器(compiler)就更为合适。 + +4. 添加新的方式来解释表达式 + + 通过解释器模式,能够方便地为表达式提供新的评估方式。例如,对一个语句以更为漂亮的方式输出。如果你持续创建新的解释方式,建议使用访问者模式(Visitor)。 + +## 实现 +解释器模式和组合模式实现时有很多相似之处,下面是一些解释器模式特有的话题: + +1. 创建抽象语法树。 + + 对于抽象语法树的创建方式,解释器模式没有硬性规定。具体问题具体分析。 +2. 定义解释器的操作 + + 不需要在表达式类(Expression class)中定义解释器的操作,通常将新创建的解释器,放在访问者对象中。 +3. 使用蝇量模式共享最小符号 + + 语法中会有许多基本的最小符号(例如英语中的字母,算数运算中的加减乘除符号),能通过蝇量模式共享资源从而获益。 + + 最小结点通常不会存储他们在抽象语法树中的位置,由父节点负责提供上下文信息。这就涉及到蝇量模式中的一个概念:内部状态和外部状态。 \ No newline at end of file diff --git a/BehavioralPatterns/Interpreter/res/Interpreter.png b/BehavioralPatterns/Interpreter/res/Interpreter.png new file mode 100644 index 000000000..f762a5f45 Binary files /dev/null and b/BehavioralPatterns/Interpreter/res/Interpreter.png differ diff --git a/BehavioralPatterns/Iterator/README.md b/BehavioralPatterns/Iterator/README.md new file mode 100644 index 000000000..d92daf790 --- /dev/null +++ b/BehavioralPatterns/Iterator/README.md @@ -0,0 +1,74 @@ +# 迭代器模式 +## 意图 +提供一种顺序访问对象集合的方法,而不用关心其底层实现。 + +也称为游标(Cursor)。 + +## 适用性 + +* 不暴露内部表现的情况下,访问一个集合对象; +* 支持对集合对象的多重遍历; +* 为遍历不同的集合结构,提供统一的接口; + +## 结构 + +![structure](./res/Iterator.png) + +创建迭代器,需要调用集合中的接口。 + +## 模式效果 + +1. 支持集合遍历方式的变化 + + 复杂的集合可能会以多种方式遍历,迭代器模式使得变更迭代算法变得简单,只需要用一个新的迭代器实例替换当前的即可。 +2. 迭代器简化了集合的接口 + + 集合本身有遍历的需求,通过迭代器来实现了这些功能,集合的接口就得以简化。 +3. 集合可以有多种遍历方法 + + 集合的遍历状态,由迭代器维护,因此集合同时可以有多个遍历。 + +## 实现 +迭代器有许多的实现变化和选择,其中的取舍往往是依赖于实现的语言。下面是一些重要的方面: + +1. 谁控制迭代? + + 是由迭代器还是客户控制迭代?当用户控制迭代,称为外部迭代器,如果由迭代器自身控制,称为内部迭代器。 + + 外部迭代器比内部迭代器更为灵活,能够简单地比较对象相等。内部迭代器对于像C++这样没有匿名函数、闭包的语言,不是很有意义,但使用起来很方便,因为迭代逻辑集成在迭代器中。 +2. 谁定义遍历算法? + + 并非只有迭代器可以定义遍历算法,集合本身也可以定义遍历算法,迭代器只是用来存储迭代的状态。将这种迭代器称为**游标**,因为它仅仅用来指向集合中的当前位置。 + + 如果由迭代器定义遍历算法,就很方便对同一个集合使用不同的遍历算法,也可以将同样的遍历算法用到不同的集合上。反之,由于遍历算法会访问集合的私有成员,会破坏集合的封装。 +3. 迭代器的鲁棒性如何? + + 迭代时修改集合非常危险,可能会访问同一对象两次,也可能错过某个对象。简单的解决方案是遍历集合的拷贝,但这样做代价太高。 + + 一个鲁棒性好的迭代器需要确保插入和删除时不会干扰迭代,而且不用拷贝集合 +4. 额外的迭代器操作 + + 除了上述的迭代器基本操作,还有一些额外的操作,也很有用。例如,有序集合有Previous操作访问前一个元素;对于已排序的或已索引的集合SkipTo操作也很有用。 +5. C++中使用多态迭代器 + + 多态迭代器的对象是通过工厂方法动态创建的,只有在支持多态时才使用,否则就用具体迭代器,在栈上分配。 + + 多态迭代器还有一个缺点:用户需要负责删除对象,很多时候用户都会忘记释放堆上的对象,尤其是当操作有多个出口点时。 + + 代理模式提供了弥补方案。可以使用在栈上分配的代理作为实际迭代器的替身,在代理的析构中释放迭代器。当代理离开作用域时,迭代器也被一并释放。即使在发生异常时,代理也能清理干净堆上的资源。这是C++为人熟知的技术“资源分配即初始化(resource allocation is initialization)”。 +6. 迭代器的访问特权 + + 迭代器可以被看成是集合的扩展,两者是紧耦合的,在C++中,可以将迭代器设置为集合的友元,这样迭代器就能直接访问集合属性,是遍历更为高效。 + + 然而,这种访问特权会使定义新的遍历变得困难,因为要求集合定义新的友元。为了避免这一情况,迭代器可以包含保护方法(protected operation)访问集合的非公开但核心的成员,迭代器的子类(且仅是子类)可以通过这些方法访问集合。 +7. 组合的迭代器 + + 外部迭代器想要实现对迭代形式的组合集合进行遍历,会比较困难,因为集合结构中的位置可能是许多层级的嵌套,因此外部迭代器需要记录下组合位置的完整路径。使用内部迭代器就比较方便,因为组合中的对象能够方便地掌握自己的当前位置。 + + 如果组合中的结点支持移动,那游标型的迭代器是更好的选择,只需要保存当前结点的追踪。 + + 组合通常需要多种迭代方式,前序、后序等,可通过不同迭代器类实现各种遍历方式。 +8. 空迭代器 + + 空迭代器是一个退化的迭代器,用来判断边界条件时很有用。通过定义,空迭代器总是遍历的完成,调用IsDone的结果总是true。 + diff --git a/BehavioralPatterns/Iterator/iterator.py b/BehavioralPatterns/Iterator/iterator.py new file mode 100644 index 000000000..3aa36b8d4 --- /dev/null +++ b/BehavioralPatterns/Iterator/iterator.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/ + +Implementation of the iterator pattern with a generator""" + + +def count_to(count): + """Counts by word numbers, up to a maximum of five""" + numbers = ["one", "two", "three", "four", "five"] + # enumerate() returns a tuple containing a count (from start which + # defaults to 0) and the values obtained from iterating over sequence + for pos, number in zip(range(count), numbers): + yield number + +# Test the generator +count_to_two = lambda: count_to(2) +count_to_five = lambda: count_to(5) + +print('Counting to two...') +for number in count_to_two(): + print(number, end=' ') + +print() + +print('Counting to five...') +for number in count_to_five(): + print(number, end=' ') + +print() + +### OUTPUT ### +# Counting to two... +# one two +# Counting to five... +# one two three four five diff --git a/BehavioralPatterns/Iterator/res/Iterator.png b/BehavioralPatterns/Iterator/res/Iterator.png new file mode 100644 index 000000000..f058e77a8 Binary files /dev/null and b/BehavioralPatterns/Iterator/res/Iterator.png differ diff --git a/BehavioralPatterns/Mediator/README.md b/BehavioralPatterns/Mediator/README.md new file mode 100644 index 000000000..6adf5701c --- /dev/null +++ b/BehavioralPatterns/Mediator/README.md @@ -0,0 +1,57 @@ +# 中介者模式 Mediator + +## 意图 +定义一个对象,封装一系列对象的交互。中介者模式推崇松耦合,防止对象显示地互相引用,通过中介者能独立地切换交互方式。 + +## 适用性 + +在以下情况时考虑使用中介者模式: + +* 一系列对象间的交互定义得非常清晰,但又非常复杂 +* 复用对象困难因为它引用了许多其他对象 +* 在多个类之间的分发行为,不需要引入很多子类,就能够实现。 + +## 结构 + +![structure](./res/MediatorStructure.png) + +这个结构比较抽象,添加一个具体的实例: +![structure](./res/MediatorSample.png) + +其中DialogDirector相当于Mediator,FontDialogDirector相当于ConcreteMediator。当任何一个Widget有变化时,会通过WidgetChanged方法通知Mediator,Mediator再对相关的Widget进行更新。这样做将一系列的Widget从交互中解脱,统一和Mediator进行。 + +## 模式效果 + +中介者模式有以下一些好处和不足: + +1. 限制了继承的泛滥 + + 中介者实现了原本要在多个对象互相交互的行为,变更行为只需要实现子类继承中介者,同僚类(Colleague)可以复用。 +2. 解耦了各同僚 + + 中介者模式使同僚之间松耦合,很方便变化和复用各同僚和中介者。 +3. 简化了对象协议 + + 中介者模式将多对多的协议简化为了一对多,这更有利于理解、维护和扩展。 +4. 抽象了对象的协作方式 + + 将中间件独立开来,开发时能够专注在业务对象本身。 +5. 集中控制 + + 将同僚之间的复杂度转化为中介者内部的复杂度。因为中介者封装了协议,变得比其他对象更为复杂,这使得中介者本身更加难以维护。 + +## 实现 + +1. 忽略抽象的中介者类 + + 当系统中只存在一个中介者时,可忽略抽象的中介者类,只有在多个中介者的情况下抽象类才有意义。 +2. 同僚与中介者通信 + + 当有中介者关心的事件发生时,同僚需要发起通信,可以使用观察者模式来实现这一功能。将中介者视为观察者,将同僚类设为主题。 + + 另一种方式是在中介者中定义一个通知接口,需要发起通信时调用该接口。 + +## 模式思考 +与外观模式相比,有一个显著的特点是中介者封装的是一个系列的对象(a set of objects),这些对象彼此之间有着潜在的联系,这一概念和工厂模式的核心“簇”(Family)有点相似。 + +另一方面,Facade封装了子系统对外的接口,而Mediator对于系列中对象之间的访问也进行了封装,使得这些对象也只和Mediator交互。 \ No newline at end of file diff --git a/BehavioralPatterns/Mediator/mediator.py b/BehavioralPatterns/Mediator/mediator.py new file mode 100644 index 000000000..1c6e55e65 --- /dev/null +++ b/BehavioralPatterns/Mediator/mediator.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://dpip.testingperspective.com/?p=28""" + +import random +import time + + +class TC: + '''Access db and report the result bt TM''' + + def __init__(self): + self._tm = None + self._bProblem = 0 + + def setup(self): + print("Setting up the Test") + time.sleep(0.1) + self._tm.prepareReporting() + + def execute(self): + if not self._bProblem: + print("Executing the test") + time.sleep(0.1) + else: + print("Problem in setup. Test not executed.") + + def tearDown(self): + if not self._bProblem: + print("Tearing down") + time.sleep(0.1) + self._tm.publishReport() + else: + print("Test not executed. No tear down required.") + + def setTM(self, tm): + self._tm = tm + + def setProblem(self, value): + self._bProblem = value + + +class Reporter: + + def __init__(self): + self._tm = None + + def prepare(self): + print("Reporter Class is preparing to report the results") + time.sleep(0.1) + + def report(self): + print("Reporting the results of Test") + time.sleep(0.1) + + def setTM(self, tm): + self._tm = tm + + +class DB: + + def __init__(self): + self._tm = None + + def insert(self): + print("Inserting the execution begin status in the Database") + time.sleep(0.1) + # Following code is to simulate a communication from DB to TC + if random.randrange(1, 4) == 3: + return -1 + + def update(self): + print("Updating the test results in Database") + time.sleep(0.1) + + def setTM(self, tm): + self._tm = tm + + +class TestManager: + '''The Mediator object''' + + def __init__(self): + self._reporter = None + self._db = None + self._tc = None + + def prepareReporting(self): + rvalue = self._db.insert() + if rvalue == -1: + self._tc.setProblem(1) + self._reporter.prepare() + + def setReporter(self, reporter): + self._reporter = reporter + + def setDB(self, db): + self._db = db + + def publishReport(self): + self._db.update() + self._reporter.report() + + def setTC(self, tc): + self._tc = tc + + +if __name__ == '__main__': + reporter = Reporter() + db = DB() + tm = TestManager() + tm.setReporter(reporter) + tm.setDB(db) + reporter.setTM(tm) + db.setTM(tm) + # For simplification we are looping on the same test. + # Practically, it could be about various unique test classes and their + # objects + for i in range(3): + tc = TC() + tc.setTM(tm) + tm.setTC(tc) + tc.setup() + tc.execute() + tc.tearDown() + +### OUTPUT ### +# Setting up the Test +# Inserting the execution begin status in the Database +# Executing the test +# Tearing down +# Updating the test results in Database +# Reporting the results of Test +# Setting up the Test +# Inserting the execution begin status in the Database +# Reporter Class is preparing to report the results +# Problem in setup. Test not executed. +# Test not executed. No tear down required. +# Setting up the Test +# Inserting the execution begin status in the Database +# Executing the test +# Tearing down +# Updating the test results in Database +# Reporting the results of Test diff --git a/BehavioralPatterns/Mediator/res/MediatorSample.png b/BehavioralPatterns/Mediator/res/MediatorSample.png new file mode 100644 index 000000000..800778471 Binary files /dev/null and b/BehavioralPatterns/Mediator/res/MediatorSample.png differ diff --git a/BehavioralPatterns/Mediator/res/MediatorStructure.png b/BehavioralPatterns/Mediator/res/MediatorStructure.png new file mode 100644 index 000000000..4789bf04a Binary files /dev/null and b/BehavioralPatterns/Mediator/res/MediatorStructure.png differ diff --git a/BehavioralPatterns/Memento/README.md b/BehavioralPatterns/Memento/README.md new file mode 100644 index 000000000..8c4672318 --- /dev/null +++ b/BehavioralPatterns/Memento/README.md @@ -0,0 +1,45 @@ +# 备忘录模式 Memento + +## 意图 +不破化原有的封装,获取对象的内部状态并记录,使得对象在将来能够回退到这一记录的状态。 + +也被称为Token。 + +## 适用性 + +* 需要存储对象状态(至少是部分状态)的快照,用来在未来某些情况下回退对象; +* 通过直接接口访问的方式获取状态会暴露对象的实现细节,破坏对象的封装; + +## 结构 + +![structure](./res/Memento.png) + +Memento对于Caretaker只开放**窄接口**——Caretaker只存储和传递Memento,但不能访问其中的state,对于Originator开放**宽接口**——能够访问state。 + +## 模式效果 + +1. 保护已有封装 + + 备忘录模式避免了暴露仅有发起人管理、但又必须存储在外部的信息。 +2. 简化了发起人 + + 其他设计方案中,发起人必须自己保存快照信息。使用备忘录模式后,快照信息存储在发起人的外部,简化了本身对象。 +3. 使用备忘录的代价可能会较高 + + 如果发起者每次快照需要记录大量信息、或是用户使用快照的过于频繁,备忘录模式可能会导致相当可观的维护开销。尽量确保发起者需要回退的状态轻量级情况。 +4. 定义窄接口和宽接口 + + 宽窄接口的说明在结构章节中提到,这一需求在部分语言中较难实现。 +5. 维护备忘录时的隐藏开销 + + Caretaker负责维护备忘录(包括删除),但它并不了解备忘录中包含多少状态信息,因此一个轻量级的Caretaker可能会因为维护大量备忘录而占用大量存储。 + +## 实现 +有以下两个话题需要考虑 + +1. 语言的支持 + + 备忘录模式需要宽窄两种接口,理想情况下实现语言要支持两层的静态保护。C++中,你可以将*宽接口设为私有,然后将发起人设为友元,将窄接口设为公开*。 +2. 存储变化增量 + + 当备忘录按照一个可预测的顺序创建和传回时,可以只存储发起人内部状态的增量信息 \ No newline at end of file diff --git a/BehavioralPatterns/Memento/memento.py b/BehavioralPatterns/Memento/memento.py new file mode 100644 index 000000000..42034658a --- /dev/null +++ b/BehavioralPatterns/Memento/memento.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://code.activestate.com/recipes/413838-memento-closure/""" + +import copy + + +def Memento(obj, deep=False): + state = (copy.copy, copy.deepcopy)[bool(deep)](obj.__dict__) + + def Restore(): + obj.__dict__.clear() + obj.__dict__.update(state) + return Restore + + +class Transaction: + + """A transaction guard. This is really just + syntactic suggar arount a memento closure. + """ + deep = False + + def __init__(self, *targets): + self.targets = targets + self.Commit() + + def Commit(self): + self.states = [Memento(target, self.deep) for target in self.targets] + + def Rollback(self): + for st in self.states: + st() + + +class transactional(object): + + """Adds transactional semantics to methods. Methods decorated with + @transactional will rollback to entry state upon exceptions. + """ + + def __init__(self, method): + self.method = method + + def __get__(self, obj, T): + def transaction(*args, **kwargs): + state = Memento(obj) + try: + return self.method(obj, *args, **kwargs) + except: + state() + raise + return transaction + + +class NumObj(object): + + def __init__(self, value): + self.value = value + + def __repr__(self): + return '<%s: %r>' % (self.__class__.__name__, self.value) + + def Increment(self): + self.value += 1 + + @transactional + def DoStuff(self): + self.value = '1111' # <- invalid value + self.Increment() # <- will fail and rollback + + +if __name__ == '__main__': + n = NumObj(-1) + print(n) + t = Transaction(n) + try: + for i in range(3): + n.Increment() + print(n) + t.Commit() + print('-- commited') + for i in range(3): + n.Increment() + print(n) + n.value += 'x' # will fail + print(n) + except: + t.Rollback() + print('-- rolled back') + print(n) + print('-- now doing stuff ...') + try: + n.DoStuff() + except: + print('-> doing stuff failed!') + import sys + import traceback + traceback.print_exc(file=sys.stdout) + pass + print(n) + +### OUTPUT ### +# +# +# +# +# -- commited +# +# +# +# -- rolled back +# +# -- now doing stuff ... +# -> doing stuff failed! +# Traceback (most recent call last): +# File "memento.py", line 91, in +# n.DoStuff() +# File "memento.py", line 47, in transaction +# return self.method(obj, *args, **kwargs) +# File "memento.py", line 67, in DoStuff +# self.Increment() # <- will fail and rollback +# File "memento.py", line 62, in Increment +# self.value += 1 +# TypeError: Can't convert 'int' object to str implicitly +# diff --git a/BehavioralPatterns/Memento/res/Memento.png b/BehavioralPatterns/Memento/res/Memento.png new file mode 100644 index 000000000..57e205b89 Binary files /dev/null and b/BehavioralPatterns/Memento/res/Memento.png differ diff --git a/BehavioralPatterns/Observer/README.md b/BehavioralPatterns/Observer/README.md new file mode 100644 index 000000000..d9f936382 --- /dev/null +++ b/BehavioralPatterns/Observer/README.md @@ -0,0 +1,66 @@ +# 观察者模式 Observer + +## 意图 +在对象间建立了一对多的依赖,当一个对象状态变化,会通知所有依赖对象并自动更新。 + +观察者模式也被成为依赖(Dependents)或发布订阅模式。 + +## 经典场景 +订报纸 + +## 适用范围 + +1. 系统抽象包括两部分,其中之一依赖另外一部分; +2. 一个对象变化会使得其他对象变化,而且你事先不知道会有多少对象会需要变化; +3. 一个对象能够在事先不用知道其他依赖对象的情况下通知他们,这些对象是松耦合的。 + + +## 结构 + +![structure](./res/ObserverBasicStructure.png) + +## 运作流程 + +以下流程图描述了1个Subject和2个Observer之间的协作 + +![collaboration](./res/ObserverCollaboration.png) + +## 模式效果 + +1. Subject和Observer之间的松耦合; +2. 能够支持广播通信; +3. 会出现“预期之外的更新”情况; + +## 实现细节 +1. 建立Subjects和Observers的映射 + + 关联Subject和Observers最直接的方法是在Subject中存储Observers的索引。但是,当有大量Subject和少量Observers时这种作法的存储消耗过大。一个解决的方法是建立统一Subject-Observer的映射,这样没有Observer的Subjectjiiu不会占用额外空间。 +2. 观察多个Subject + + 一个Observer可能观察多个Subject,单纯适用Update无法知道是那个Subject发生了变化,需要在Update带上Subject信息。 +3. 由谁负责调用Notify? + + * 在State的Set方法中调用。好处是用户可以不用关心这一过程;缺点是每次Set都会Notify,效率低。 + * 由用户负责选择调用时机。好处是效率更高;缺点是用户可能忘记调用。 + +4. 已删除的Subject的游弋引用 + + Subject删除时会在Observer里残留游弋引用,解决方法是当Subject删除时进行Notify,让Observer能够删除引用 +5. 确保State的一致性 +6. 避免适用 push 和 pull 模型 + + * push模型:把所有的变更信息全部发送给Observer,不管是不是需要;这一模型假设了Subject知道Observer的需求,复用性变差; + * pull模型:只发送一个变更通知,具体的变更内容需要Observer自行获取;这一模型强调了Subject对于Observer一无所知,效率偏低 + * 那到底该如何取舍? + +7. 按需发布 + + 在attach时添加interest信息,notify时根据interest进行,有的放矢。 +8. 当结构较为复杂时,可添加中间件`ChangeManager` + + ![changemanager](./res/ChangeManager.png) + +9. 合并Subject和Observer + + 一个对象很可能既是Subject也是Observer,在缺少多重继承的语言中就需要将这两个类合并,作为一个Observer基类进行使用。 + diff --git a/BehavioralPatterns/Observer/observer.py b/BehavioralPatterns/Observer/observer.py new file mode 100644 index 000000000..4ce1aed65 --- /dev/null +++ b/BehavioralPatterns/Observer/observer.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://code.activestate.com/recipes/131499-observer-pattern/""" + + +class Subject(object): + + def __init__(self): + self._observers = [] + + def attach(self, observer): + if observer not in self._observers: + self._observers.append(observer) + + def detach(self, observer): + try: + self._observers.remove(observer) + except ValueError: + pass + + def notify(self, modifier=None): + for observer in self._observers: + if modifier != observer: + observer.update(self) + + +# Example usage +class Data(Subject): + + def __init__(self, name=''): + Subject.__init__(self) + self.name = name + self._data = 0 + + @property + def data(self): + return self._data + + @data.setter + def data(self, value): + self._data = value + self.notify() + + +class HexViewer: + + def update(self, subject): + print('HexViewer: Subject %s has data 0x%x' % + (subject.name, subject.data)) + + +class DecimalViewer: + + def update(self, subject): + print('DecimalViewer: Subject %s has data %d' % + (subject.name, subject.data)) + + +# Example usage... +def main(): + data1 = Data('Data 1') + data2 = Data('Data 2') + view1 = DecimalViewer() + view2 = HexViewer() + data1.attach(view1) + data1.attach(view2) + data2.attach(view2) + data2.attach(view1) + + print("Setting Data 1 = 10") + data1.data = 10 + print("Setting Data 2 = 15") + data2.data = 15 + print("Setting Data 1 = 3") + data1.data = 3 + print("Setting Data 2 = 5") + data2.data = 5 + print("Detach HexViewer from data1 and data2.") + data1.detach(view2) + data2.detach(view2) + print("Setting Data 1 = 10") + data1.data = 10 + print("Setting Data 2 = 15") + data2.data = 15 + + +if __name__ == '__main__': + main() + +### OUTPUT ### +# Setting Data 1 = 10 +# DecimalViewer: Subject Data 1 has data 10 +# HexViewer: Subject Data 1 has data 0xa +# Setting Data 2 = 15 +# HexViewer: Subject Data 2 has data 0xf +# DecimalViewer: Subject Data 2 has data 15 +# Setting Data 1 = 3 +# DecimalViewer: Subject Data 1 has data 3 +# HexViewer: Subject Data 1 has data 0x3 +# Setting Data 2 = 5 +# HexViewer: Subject Data 2 has data 0x5 +# DecimalViewer: Subject Data 2 has data 5 +# Detach HexViewer from data1 and data2. +# Setting Data 1 = 10 +# DecimalViewer: Subject Data 1 has data 10 +# Setting Data 2 = 15 +# DecimalViewer: Subject Data 2 has data 15 diff --git a/BehavioralPatterns/Observer/res/ChangeManager.png b/BehavioralPatterns/Observer/res/ChangeManager.png new file mode 100644 index 000000000..6b1ff0c94 Binary files /dev/null and b/BehavioralPatterns/Observer/res/ChangeManager.png differ diff --git a/BehavioralPatterns/Observer/res/ObserverBasicStructure.png b/BehavioralPatterns/Observer/res/ObserverBasicStructure.png new file mode 100644 index 000000000..07ee60749 Binary files /dev/null and b/BehavioralPatterns/Observer/res/ObserverBasicStructure.png differ diff --git a/BehavioralPatterns/Observer/res/ObserverCollaboration.png b/BehavioralPatterns/Observer/res/ObserverCollaboration.png new file mode 100644 index 000000000..de49c0325 Binary files /dev/null and b/BehavioralPatterns/Observer/res/ObserverCollaboration.png differ diff --git a/BehavioralPatterns/State/README.md b/BehavioralPatterns/State/README.md new file mode 100644 index 000000000..ad3795238 --- /dev/null +++ b/BehavioralPatterns/State/README.md @@ -0,0 +1,61 @@ +# 状态模式 State + +## 意图 +允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 + +一个对象的行为,会因为其所处的状态而发生变化,需要有大量的`if...else...`分支,此时就可以考虑使用状态模式。 + +## 适用性 +状态模式适合以下场景: + +* 对象的行为受其状态约束,在运行时依据状态会发生变化; +* 对象的行为有一大块依赖状态的条件分支语句。状态通常是一个常量枚举。状态模式将每个条件分支放在一个独立类中,使得处理每个分支转变为调用类方法。 + +## 结构 + +![structure](./res/StateStructure.png) + +* Context将请求委托给具体的State执行; +* Context可能会将自身作为参数传给State,使其能够访问Context内容; +* Client只和Context交互,能够通过接口设定State,并不直接访问State; +* Context或ConcreteState决定状态迁移的场景和方式; + +## 模式效果 +1. 将状态指向的行为本地化,并根据不同状态区分行为。将特定状态的相关行为放进了一个对象中;因为状态和行为绑定,方便添加新的状态; +2. 将状态迁移的过程隐藏; +3. 状态对象可共享; + * 状态对象没有实例变量,仅仅是根据类型进行编码,那就是可共享的状态对象; + * 天然的蝇量模式(没有内部状态,只有行为) + +## 实现 +1. 由谁定义状态迁移? + + 状态模式没有限定状态迁移的标准。如果这是固定的,则能够由代码的上下文决定状态迁移。更为灵活的方式是由子类决定迁移方式,这就需要一个额外的接口用于设定状态。 + + 不固定的方式对于添加新的状态更为灵活,但是使得子状态之间不再那么透明,每个子状态至少要知道一个其他子状态的,从而实现状态跃迁。 + +2. 一种基于表的(状态模式)替代方案 + + 创建一张表,将输入映射到状态迁移。对于每种状态,表将可能的输入映射到后续的状态。 + + 这一方案的主要优点是其规则性:能够通过变换数据,而非代码来实现规则的变化。 + + 然而,也有以下一些缺点: + + * 查表比(虚)函数调用更为低效; + * 将迁移逻辑归一化、表格化使其更加难以理解; + * 想要将额外的行为添加进状态迁移通常会比较困难; + + 表驱动的状态机和状态模式的差异总结:状态模式强调与状态绑定的行为,表驱动的状态机着眼于定义状态迁移。 + +3. 创建和删除状态对象 + + 一个实现时通常需要权衡的是状态对象的生命周期:是使用时创建,用完就删除,还是提前创建好,用完也不删呢? + + 前者通常在状态只有在运行时才能确定的场景下,而且环境较少变更状态,当状态中包含很多信息时,避免了创建这类状态的花费。 + + 后者对于状态迁移频繁的场景适应性更好,避免了频繁创建状态。但这种作法也有不方便之处,代码空间中需要维护针对所有状态的引用。 + +4. 使用动态继承 + + 部分语言支持。可将方法委托给不同类型的对象,实现某种程度的动态继承。 \ No newline at end of file diff --git a/BehavioralPatterns/State/res/StateStructure.png b/BehavioralPatterns/State/res/StateStructure.png new file mode 100644 index 000000000..65e673e6a Binary files /dev/null and b/BehavioralPatterns/State/res/StateStructure.png differ diff --git a/BehavioralPatterns/State/state.py b/BehavioralPatterns/State/state.py new file mode 100644 index 000000000..5f4ef41eb --- /dev/null +++ b/BehavioralPatterns/State/state.py @@ -0,0 +1,80 @@ +"""Implementation of the state pattern""" + +# http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/ + + +class State(object): + + """Base state. This is to share functionality""" + + def scan(self): + """Scan the dial to the next station""" + self.pos += 1 + if self.pos == len(self.stations): + self.pos = 0 + print("Scanning... Station is", self.stations[self.pos], self.name) + + +class AmState(State): + + def __init__(self, radio): + self.radio = radio + self.stations = ["1250", "1380", "1510"] + self.pos = 0 + self.name = "AM" + + def toggle_amfm(self): + print("Switching to FM") + self.radio.state = self.radio.fmstate + + +class FmState(State): + + def __init__(self, radio): + self.radio = radio + self.stations = ["81.3", "89.1", "103.9"] + self.pos = 0 + self.name = "FM" + + def toggle_amfm(self): + print("Switching to AM") + self.radio.state = self.radio.amstate + + +class Radio(object): + + """A radio. It has a scan button, and an AM/FM toggle switch.""" + + def __init__(self): + """We have an AM state and an FM state""" + self.amstate = AmState(self) + self.fmstate = FmState(self) + self.state = self.amstate + + def toggle_amfm(self): + self.state.toggle_amfm() + + def scan(self): + self.state.scan() + + +# Test our radio out +if __name__ == '__main__': + radio = Radio() + actions = [radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2 + actions *= 2 + + for action in actions: + action() + +### OUTPUT ### +# Scanning... Station is 1380 AM +# Scanning... Station is 1510 AM +# Switching to FM +# Scanning... Station is 89.1 FM +# Scanning... Station is 103.9 FM +# Scanning... Station is 81.3 FM +# Scanning... Station is 89.1 FM +# Switching to AM +# Scanning... Station is 1250 AM +# Scanning... Station is 1380 AM diff --git a/BehavioralPatterns/Strategy/README.md b/BehavioralPatterns/Strategy/README.md new file mode 100644 index 000000000..47989b872 --- /dev/null +++ b/BehavioralPatterns/Strategy/README.md @@ -0,0 +1,88 @@ +# 策略模式 Strategy + +## 意图 +定义了一系列的算法,对其进行封装,使他们互为可交换。策略模式使得各算法在使用时能够切换。 + +为了解决某个问题、实现某个功能,存在多种方法(或算法),将这些方法全部在调用对象中实现显然不合理。将这些方法抽离出来单独实现,称为“策略”,使用时根据需要选择合适的策略装载,大概就是策略模式的含义。 + +## 适用性 +* 策略模式提供了将一个类与多种相似相关行为之一关联的方法; +* 你需要同一算法的不同变种; +* 算法数据中包含了用户不必知道的细节,使用策略模式以免暴露复杂的、算法相关的数据结构; +* 一个类定义了许多行为,会在一个多条件判断中选择执行哪种行为。使用策略模式替代这种条件判断; + +## 结构 + +![structure](./res/Strategy.png) + +* ContextInterface调用Algorithm的时候,需要将所有的必要参数传入,或者将自身引用传入; +* 由用户选择具体策略,传递给Context,再调用ContextInterface执行策略; + +## 模式效果 + +1. 相关算法家族 + + 通过这样的抽象架构能够提取出算法的公有功能。 + +2. 继承之外的另一种选择 + + 策略模式符合“使用组合,而不是继承”的原则,更为灵活。 + +3. 通过使用策略模式,去除了大量条件分支语句 +4. 具体实现的选择 + + 为相似的行为提供了不同的实现,由用户根据时间、空间的取舍决定具体的实现。 + +5. 用户必须理解不同的策略 + + 用户必须了解自己的选择的后果,了解所有备选算法的实际功效。 + +6. Strategy和Context之间的通信 + + 由于所有的策略都有相同的接口、相同的参数,就存在复杂算法需要更多参数,而简单算法可能完全不需要参数。为了复杂算法准备的参数,在选用简单算法时就显得累赘且浪费时间。 + + 如果想要解决这一问题,就要求Context理解Strategy接口的参数含义,而这又造成两者不再是松耦合状态。 + +7. 对象数量的增加 + + 策略模式增加了应用中对象的数量。一个解决办法是将策略设计为无状态对象(没有类成员变量,只有成员方法),然后让Context共享这些策略。 + + 蝇量方法对于这类实现更有心得,会在相关章节中详述。 + +## 实现 + +1. 定义Strategy和Context接口 + + Strategy和Context的接口必须提供ConcreteStrategy必要的参数,有两种方式: + + * 将参数以参数列表方式写在接口中,做到了Strategy和Context的解耦,但可能会给Strategy传递不必要的参数; + * 将Context本身的引用传递给Strategy,Strategy记录该引用,需要什么参数就获取什么参数,这样两者的耦合程度就更高。 + + 根据实际需要决定使用哪种方法。 + +2. 将Strategy作为模板成员变量 + + C++ 模板允许将Strategy作为模板变量使用: + + // Context + template + class Context { + public: + void Operation() { theStrategy.DoAlgorithm; } + + private: + AStrategy theStrategy; + } + + // Strategy + class MyStrategy { + public: + void DoAlgorithm(); + } + + // sample + Context aContext; + +3. 将策略设为可选 + + 在使用前先判断策略是否有效,如果无效则使用Context的默认方法,如果有效,则调用策略方法。 \ No newline at end of file diff --git a/BehavioralPatterns/Strategy/res/Strategy.png b/BehavioralPatterns/Strategy/res/Strategy.png new file mode 100644 index 000000000..b3c0386ce Binary files /dev/null and b/BehavioralPatterns/Strategy/res/Strategy.png differ diff --git a/BehavioralPatterns/Strategy/strategy.py b/BehavioralPatterns/Strategy/strategy.py new file mode 100644 index 000000000..8c7a9b5a4 --- /dev/null +++ b/BehavioralPatterns/Strategy/strategy.py @@ -0,0 +1,49 @@ +# http://stackoverflow.com/questions/963965/how-is-this-strategy-pattern +# -written-in-python-the-sample-in-wikipedia +""" +In most of other languages Strategy pattern is implemented via creating some +base strategy interface/abstract class and subclassing it with a number of +concrete strategies (as we can see at +http://en.wikipedia.org/wiki/Strategy_pattern), however Python supports +higher-order functions and allows us to have only one class and inject +functions into it's instances, as shown in this example. +""" +import types + + +class StrategyExample: + + def __init__(self, func=None): + self.name = 'Strategy Example 0' + if func is not None: + self.execute = types.MethodType(func, self) + + def execute(self): + print(self.name) + + +def execute_replacement1(self): + print(self.name + ' from execute 1') + + +def execute_replacement2(self): + print(self.name + ' from execute 2') + + +if __name__ == '__main__': + strat0 = StrategyExample() + + strat1 = StrategyExample(execute_replacement1) + strat1.name = 'Strategy Example 1' + + strat2 = StrategyExample(execute_replacement2) + strat2.name = 'Strategy Example 2' + + strat0.execute() + strat1.execute() + strat2.execute() + +### OUTPUT ### +# Strategy Example 0 +# Strategy Example 1 from execute 1 +# Strategy Example 2 from execute 2 diff --git a/BehavioralPatterns/TemplateMethod/README.md b/BehavioralPatterns/TemplateMethod/README.md new file mode 100644 index 000000000..c0ec409c9 --- /dev/null +++ b/BehavioralPatterns/TemplateMethod/README.md @@ -0,0 +1,41 @@ +# 模板方法模式 Template Method + +## 意图 +定义算法的主体框架,而将其中的某些步骤移交给子类决定。模板方法在不改变算法框架的前提下让子类能够重新定义部分步骤。 + +模板方法针对的是一个具体的方法,将其步骤模板化,而实现的过程交给了子类。 + +## 案例 +以项目开发的一般周期来说,不同项目实施方案肯定不同,但都可以归纳到同一个模板:需求收集、立项、设计、开发、调试、发布。这样一个开发的流程就是一个模板方法。 + +## 适用性 + +1. 将算法中不变的部分一并实现,将可变部分留给子类实现; +2. 各个子类中有相同的行为或代码,将这些整合在模板方法中; +3. 限制子类的扩展。符合“开放封闭原则”,开放的是部分步骤,封闭的是整体框架,每个开放的部分对于子类就是一个“钩子”,子类只能在这些部分进行扩展; + +## 结构 + +![structure](./res/TemplateMethod.png) + +## 模式效果 + +模板模式引出了一种控制结构,一般称为“**好莱坞原则**”——“不要来找我们,我们会找你”。这里AbstractClass就是好莱坞大导演,而ConcreteClass就是试镜的小演员,ConcreteClass只负责自己有限的职责——实现Operation1和Operation2,调用工作有AbstractClass负责。 + +AbstractClass必须明确哪些方法是可以被重写,哪些是必须被重写,而哪些又是不能被重写。 + +## 实现 + +1. 合理使用C++的访问控制 + + * primitive方法是必须被重写的,必须为纯虚函数,确保子类会重写; + * 为了确保primitive方法不被外部访问,可以设置为protected权限; + * 模板方法是不希望子类重写的,不能设为虚函数; + +2. 尽可能减少Primitive方法的数量 + + 能够简化子类的实现。 + +3. 命名规则 + + 通过使用命名规则来提示子类的继承实现。 \ No newline at end of file diff --git a/BehavioralPatterns/TemplateMethod/res/TemplateMethod.png b/BehavioralPatterns/TemplateMethod/res/TemplateMethod.png new file mode 100644 index 000000000..9577edd40 Binary files /dev/null and b/BehavioralPatterns/TemplateMethod/res/TemplateMethod.png differ diff --git a/BehavioralPatterns/TemplateMethod/template.py b/BehavioralPatterns/TemplateMethod/template.py new file mode 100644 index 000000000..42e7377ee --- /dev/null +++ b/BehavioralPatterns/TemplateMethod/template.py @@ -0,0 +1,107 @@ +"""http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/ + +An example of the Template pattern in Python""" + +ingredients = "spam eggs apple" +line = '-' * 10 + + +# Skeletons +def iter_elements(getter, action): + """Template skeleton that iterates items""" + for element in getter(): + action(element) + print(line) + + +def rev_elements(getter, action): + """Template skeleton that iterates items in reverse order""" + for element in getter()[::-1]: + action(element) + print(line) + + +# Getters +def get_list(): + return ingredients.split() + + +def get_lists(): + return [list(x) for x in ingredients.split()] + + +# Actions +def print_item(item): + print(item) + + +def reverse_item(item): + print(item[::-1]) + + +# Makes templates +def make_template(skeleton, getter, action): + """Instantiate a template method with getter and action""" + def template(): + skeleton(getter, action) + return template + +# Create our template functions +templates = [make_template(s, g, a) + for g in (get_list, get_lists) + for a in (print_item, reverse_item) + for s in (iter_elements, rev_elements)] + +# Execute them +for template in templates: + template() + +### OUTPUT ### +# spam +# ---------- +# eggs +# ---------- +# apple +# ---------- +# apple +# ---------- +# eggs +# ---------- +# spam +# ---------- +# maps +# ---------- +# sgge +# ---------- +# elppa +# ---------- +# elppa +# ---------- +# sgge +# ---------- +# maps +# ---------- +# ['s', 'p', 'a', 'm'] +# ---------- +# ['e', 'g', 'g', 's'] +# ---------- +# ['a', 'p', 'p', 'l', 'e'] +# ---------- +# ['a', 'p', 'p', 'l', 'e'] +# ---------- +# ['e', 'g', 'g', 's'] +# ---------- +# ['s', 'p', 'a', 'm'] +# ---------- +# ['m', 'a', 'p', 's'] +# ---------- +# ['s', 'g', 'g', 'e'] +# ---------- +# ['e', 'l', 'p', 'p', 'a'] +# ---------- +# ['e', 'l', 'p', 'p', 'a'] +# ---------- +# ['s', 'g', 'g', 'e'] +# ---------- +# ['m', 'a', 'p', 's'] +# ---------- diff --git a/BehavioralPatterns/Visitor/README.md b/BehavioralPatterns/Visitor/README.md new file mode 100644 index 000000000..731838192 --- /dev/null +++ b/BehavioralPatterns/Visitor/README.md @@ -0,0 +1,69 @@ +# 访问者模式 Visitor + +## 意图 +封装一些施加于某种数据结构元素之上的操作。访问者使你能够在不修改操作元素的类的情况下定义新的操作。 + +## 适用性 + +* 一个数据结构中包含许多类,有着不同的接口,希望根据不同的类执行不同的操作。 +* 对象会执行许多不同且没有关联的操作,你不希望对象的类被这么多操作锁“污染”。访问者让你将相关的操作定义在一个类中。 +* 数据结构中的类很少发生变化,但会对整体机构定义新的操作,修改数据结构中类的接口会影响所有的访问者,代价高昂,如果数据结构中类变化频繁,最好将这些类定义在访问者中 + +## 结构 + +![structure](./res/VisitorStructure.png) + +从结构定义中能够看出,抽象访问者中定义了所有ConcreteElement的访问方法,所以整个结构中的Element要尽可能固定。 + +## 协作流程 + +![structure](./res/VisitorObject.png) + +* 使用访问者模式必须创建具体访问者对象,遍历整个数据结构,使用访问者访问每个元素。 +* 当元素被访问到时,调用访问者的对应方法。 + +## 模式效果 + +1. 访问者模式很容易添加新操作 + + 通过添加一个新的访问者,能够对结构中所有对象定义新的操作。 +2. 访问者将相关的操作聚集,分隔不相关的 + + 相关的操作被定义在同一个访问者中,不相关的操作由不同访问者持有。 +3. 添加新的Element实例困难 + + 正如之前章节提到的,访问者要有对所有Element都有相应方法,所以添加新的Element会比较困难。 +4. 访问整个类层级结构 + + 可以使用迭代器遍历对象,但迭代器不能遍历不同类型的元素。这就要求这些元素都有相同的基类。 + + 但访问者并没有这样的限制,调用访问者的对象完全可以是有着不同的基类。 +5. 累积状态 + + 当访问者逐个访问元素时,可以累积状态,否则就要将这一状态在遍历过程传递,或者记录为全局对象。 +6. 打破了封装 + + 访问者直接访问了ConcreteElement,打破了元素的封装。 + +## 实现 + +实现时要考虑以下两个问题: + +1. 双重分发 + + 访问者模式能够给类添加方法而不修改类,这是使用了双重分发技术实现的。 + + 支持单分发的语言,决定调用什么方法的,是请求名和接受者的类型(想象多态)。 + + 双重分发意味着方法的执行由请求类型和两个接受者,例如Accept方法就有Visitor和Element共通决定。 + + +2. 由谁负责遍历对象结构? + + 可以有三个地方来实现遍历,分别是对象结构内部、访问者内部、或是另外实现迭代器。 + + 对象结构内部实现遍历是最常见的做法,因为迭代内部的元素实现起来最便捷。 + + 使用内部迭代器还是外部迭代器都可以,很大程度上取决于语言的特性。 + + 在访问者中实现遍历也是可行的,一般用来实现比较复杂的遍历逻辑,但这样的话每个具体访问者都要实现遍历的代码,会比较麻烦。 \ No newline at end of file diff --git a/BehavioralPatterns/Visitor/res/VisitorObject.png b/BehavioralPatterns/Visitor/res/VisitorObject.png new file mode 100644 index 000000000..8890b6b3c Binary files /dev/null and b/BehavioralPatterns/Visitor/res/VisitorObject.png differ diff --git a/BehavioralPatterns/Visitor/res/VisitorStructure.png b/BehavioralPatterns/Visitor/res/VisitorStructure.png new file mode 100644 index 000000000..4d99b773b Binary files /dev/null and b/BehavioralPatterns/Visitor/res/VisitorStructure.png differ diff --git a/CreationalPatterns/AbstractFactory/README.md b/CreationalPatterns/AbstractFactory/README.md new file mode 100644 index 000000000..925fcb589 --- /dev/null +++ b/CreationalPatterns/AbstractFactory/README.md @@ -0,0 +1,41 @@ +# 抽象工厂模式 Abstract Factory + +## 意图 +提供接口,用于创建一族对象,而无需指定具体的类。 + +抽象工厂也被称为工具箱(Kit)。 + +## 概念说明:产品家族 +要理解抽象工厂,首先要理解产品等级(或者称为产品线)及产品家族(Famiy)的概念。一系列类似的产品属于同一产品线(或产品等级),而一套产品组成了一个产品家族。 + +以MonsterHunter中的装备举例,全套装备的产品家族就应该包括以下产品线:头盔、胸甲、臂甲、裙甲、腿甲,每个产品线会有多个产品,比如头盔有野猪王头盔、轰龙头盔,胸甲也有野猪王胸甲、轰龙胸甲。每个套装就是一个产品家族。 + +## 适用性 +1. 系统不必关心产品是如何创建、组合和表现的; +2. 系统每次使用多个产品家族中的一个; +3. 产品家族中的产品总是打包使用,这个限制需要强调; +4. 你想要提供一类产品,但只提供接口而不是具体实现; + +## 结构 + +![structure](./res/AbstractFactoryBasicStructure.png) + +## 运作流程 +* 通常,具体工厂在运行时创建,每个具体工厂中有产品创建的具体实现,为了创建不同的产品对象,需要使用不同的具体工厂; +* 抽象工厂将产品的创建交给其具体工厂处理; + +## 模式效果 +* 使用抽象类隔离具体类; +* 能够比较容易地按家族替换产品;也能够方便地添加一族产品; +* 确保产品的一致性,一个家族的产品一并使用; +* 对于添加产品线较为困难; + +## 实现细节 +1. 将工厂创建为单例(Singleton),因为通常每种工厂只需要一个实例; +2. 抽象工厂只提供接口声明,具体的创建由具体工厂实现。通常,为每个产品线定义一个工厂方法。(工厂方法的详细描述见相关章节); +3. 定义扩展工厂。添加一个产品线对于抽象工厂会比较麻烦,需要修改所有依赖的类,一个可行的方法是工厂只提供一个`Make`方法,该方法接收一个参数,从而决定创建的具体对象。 + +## 其他 +抽象工厂的核心就是产品家族、产品线的创建,根据“开放-封闭”原则,抽象工厂是对产品家族的扩展开放、对产品线的扩展封闭的一种设计模式。 + +正如抽象工厂的适用场景的描述,当我们有多个产品家族、存在每次使用其中一个完整的家族的约束,这时候抽象工厂模式可能就是我们想要的。 diff --git a/CreationalPatterns/AbstractFactory/abstract_factory.py b/CreationalPatterns/AbstractFactory/abstract_factory.py new file mode 100644 index 000000000..a32c067e6 --- /dev/null +++ b/CreationalPatterns/AbstractFactory/abstract_factory.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/ + +"""Implementation of the abstract factory pattern""" + +import random + +class PetShop: + + """A pet shop""" + + def __init__(self, animal_factory=None): + """pet_factory is our abstract factory. We can set it at will.""" + + self.pet_factory = animal_factory + + def show_pet(self): + """Creates and shows a pet using the abstract factory""" + + pet = self.pet_factory.get_pet() + print("We have a lovely {}".format(pet)) + print("It says {}".format(pet.speak())) + print("We also have {}".format(self.pet_factory.get_food())) + + +# Stuff that our factory makes + +class Dog: + + def speak(self): + return "woof" + + def __str__(self): + return "Dog" + + +class Cat: + + def speak(self): + return "meow" + + def __str__(self): + return "Cat" + + +# Factory classes + +class DogFactory: + + def get_pet(self): + return Dog() + + def get_food(self): + return "dog food" + + +class CatFactory: + + def get_pet(self): + return Cat() + + def get_food(self): + return "cat food" + +# Create the proper family +def get_factory(): + """Let's be dynamic!""" + return random.choice([DogFactory, CatFactory])() + + +# Show pets with various factories +if __name__ == "__main__": + for i in range(3): + shop = PetShop(get_factory()) + shop.show_pet() + print("=" * 20) + +### OUTPUT ### +# We have a lovely Dog +# It says woof +# We also have dog food +# ==================== +# We have a lovely Dog +# It says woof +# We also have dog food +# ==================== +# We have a lovely Cat +# It says meow +# We also have cat food +# ==================== diff --git a/CreationalPatterns/AbstractFactory/res/AbstractFactoryBasicStructure.png b/CreationalPatterns/AbstractFactory/res/AbstractFactoryBasicStructure.png new file mode 100644 index 000000000..934b6b6bd Binary files /dev/null and b/CreationalPatterns/AbstractFactory/res/AbstractFactoryBasicStructure.png differ diff --git a/CreationalPatterns/Builder/README.md b/CreationalPatterns/Builder/README.md new file mode 100644 index 000000000..f010e6702 --- /dev/null +++ b/CreationalPatterns/Builder/README.md @@ -0,0 +1,71 @@ +# 生成器模式 Builder + +## 意图 +它可以将复杂对象的构造过程与其表现分离、抽象出来,使这个抽象过程可以构造出不同表现(属性)的对象。 + +## 模式比喻 +我要一座房子住,可是我不知道怎么盖(简单的砌墙,层次较低),也不知道怎么样设计(建几个房间,几个门好看,层次较高),于是我需要找**施工队,他们会砌墙**,还得找个**设计师,他知道怎么设计**,我还要**确保施工队听设计师的领导**,而设计师本身也不干活,光是下命令,这里砌一堵墙,这里砌一扇门,这样施工队就可以开始建设。最后,我向施工队要房子。在这个过程中,**设计师是什么也没有,除了他在脑子里的设计和命令,所以要房子最终要跟施工队要** + +## 适用性 +在以下场合使用生成器模式: + +* 创建复杂对象的算法要与对象组织的部分分离; +* 对象的创建过程允许不同的表现方式 + +Java/C++中的protobuf自动生成、用于构建protobuf对象的代码,应该算是生成器模式的一个实际应用。 + +## 结构 + +![structure](./res/Builder.png) + +角色分析: + +* Builder + * 定义用于创建产品各部分的抽象接口 +* ConcreteBuilder + * 通过实现Builder的接口创建和组装产品的各部分 + * 定义和追踪其创建的表现 + * 提供取回完整产品的接口 +* Director + * 通过调用Builder接口创建对象 +* Product + * 代表了创建出的复杂对象。ConcreteBuilder构造了Product的内部表现,定义了其组装过程 + * 包含了组成Product的部件类,包括将这些部件组装成最终结果的接口 + +模式时序图: + +![structure](./res/BuilderSequence.png) + +## 模式效果 +1. 能够改变产品的内部表现。 + + Product通过抽象接口进行构建,通过定义一种新的Builder就能改变产品的内部表现。 + +2. 将产品的构造和表现分离 + + 生成器模式封装了复杂对象的构建和表现。客户不用知道定义产品内部结构的类。 + + ConcreteBuilder包含了所有的创建和组装某类产品的代码。这些代码只用写一次,不同的Director可以复用这些代码,来构造产品。 + +3. 对于构建过程提供了更好的控制 + + 与其他创建类型的模式不同,生成器模式在Director的控制下一步步构造产品,仅在产品完成后才从Builder中取回。 + +## 实现 +通常,Builder是个虚类,只负责定义接口,ConcreteBuilder实现这些接口创建产品。 + +以下是一些实现时需要考虑的问题: + +1. 组装和构建接口: + + Builder的抽象接口要能够支持所有的ConcreteBuilder的构建。 + +2. 为什么没有抽象的Product? + + ConcreteBuilder创建的Product可能千差万别,难以设定一个通用的抽象基类。 + + 通常用户给Director配置合适的ConcreteBuilder,所以用户能够知道具体的Builder子类正被使用,能够合适地处置产品。 + +3. Builder中接口默认为空 + + 在C++中,构建方法通常不能为虚函数,而是被定义为什么都不做的空函数,有用户来重写他们感兴趣的方法。 \ No newline at end of file diff --git a/CreationalPatterns/Builder/builder.py b/CreationalPatterns/Builder/builder.py new file mode 100644 index 000000000..3b04412d1 --- /dev/null +++ b/CreationalPatterns/Builder/builder.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding : utf-8 -*- + +""" +@author: Diogenes Augusto Fernandes Herminio +https://gist.github.com/420905#file_builder_python.py +""" + + +# Director +class Director(object): + + def __init__(self): + self.builder = None + + def construct_building(self): + self.builder.new_building() + self.builder.build_floor() + self.builder.build_size() + + def get_building(self): + return self.builder.building + + +# Abstract Builder +class Builder(object): + + def __init__(self): + self.building = None + + def new_building(self): + self.building = Building() + + +# Concrete Builder +class BuilderHouse(Builder): + + def build_floor(self): + self.building.floor = 'One' + + def build_size(self): + self.building.size = 'Big' + + +class BuilderFlat(Builder): + + def build_floor(self): + self.building.floor = 'More than One' + + def build_size(self): + self.building.size = 'Small' + + +# Product +class Building(object): + + def __init__(self): + self.floor = None + self.size = None + + def __repr__(self): + return 'Floor: {0.floor} | Size: {0.size}'.format(self) + + +# Client +if __name__ == "__main__": + director = Director() + director.builder = BuilderHouse() + director.construct_building() + building = director.get_building() + print(building) + director.builder = BuilderFlat() + director.construct_building() + building = director.get_building() + print(building) + +### OUTPUT ### +# Floor: One | Size: Big +# Floor: More than One | Size: Small diff --git a/CreationalPatterns/Builder/res/Builder.png b/CreationalPatterns/Builder/res/Builder.png new file mode 100644 index 000000000..24071a662 Binary files /dev/null and b/CreationalPatterns/Builder/res/Builder.png differ diff --git a/CreationalPatterns/Builder/res/BuilderSequence.png b/CreationalPatterns/Builder/res/BuilderSequence.png new file mode 100644 index 000000000..17109398a Binary files /dev/null and b/CreationalPatterns/Builder/res/BuilderSequence.png differ diff --git a/CreationalPatterns/FactoryMethod/README.md b/CreationalPatterns/FactoryMethod/README.md new file mode 100644 index 000000000..7669ce1d5 --- /dev/null +++ b/CreationalPatterns/FactoryMethod/README.md @@ -0,0 +1,119 @@ +# 工厂方法模式 Factory method + +## 意图 +定义一个创建对象的接口,由子类决定具体实例化的类型。工厂方法让类的实例化推迟到子类中进行。 + +## 适用性 +* 一个类无法参与该类所必须创建对象的创建过程(只能下放给子类参与); +* 一个类希望由子类决定创建对象的类型; +* 一个类将创建对象的责任委托给了几个子类,而不必去关心这些委托具体是如何实现的。 + +## 题外话 +提到工厂方法,很容易联想到抽象工厂,但从两种方法的适用范围来看,着重点是有差异的。 + +抽象工厂着眼于产品家族、产品线,强调每个具体工厂有一个完整的产品家族;工厂方法更注重对象的创建责任委托,即由子类去考虑究竟该创建什么对象,怎么创建对象。 + +## 结构 + +![structure](./res/FactoryMethodStructure.png) + +Creator依赖其子类定义工厂方法,返回合适的具体对象。 + +## 模式效果 + +工厂方法去除了代码对于特定应用类的依赖,代码只和Product的接口打交道,因此能和用户定义的所有具体Product交互。 + +一个潜在的缺点是用户必须实现Creator的子类、仅仅是为了实现工厂方法。 + +还有两个额外的效果: + +1. 为子类提供钩子 + + 将对象创建的过程封装在工厂方法总比直接创建对象更为灵活。当对象版本有扩展时,工厂方法为子类提供了现成的钩子。 + +2. 连接平行的类层次 + + 这个概念比较抽象,需要一个实例辅助理解。 + + 假设有个图形对象,用户可对其进行拉伸、移动等操作。考虑到图形类型多样,实现这类交互并不容易,有些属性是与图形描绘相关,表示的是图形的性质,有些属性与操作相关,不同的图形可能会有不同的属性,相当的操作会有不同的实现方法。 + + 考虑到以上一些限制,一个合理的设计是将图形以及其操作分离,但每种特定的图形又有其对应的特定操作。其结构图如下: + + ![structure](./res/ParallelHierarchies.png) + +## 实现 + +当实现工厂方法模式时考虑以下问题: + +1. 两种主要的形式 + + * Creator是抽象类,无法实例化对象,使用时必须定义子类并定义工厂方法; + * Creator有默认的工厂方法,只有在要重新实现工厂方法、创建非默认的对象时定义子类并定义工厂方法; + +2. 参数化的工厂方法 + + 又称为**简单工厂**([维基百科词条](http://zh.wikipedia.org/wiki/%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95#.E7.AE.80.E5.8D.95.E5.B7.A5.E5.8E.82)中是这么命名的)。 + + 工厂方法传入ProductId,根据ProductId决定创建的具体对象。这样做就不必要复杂的继承逻辑了。当然,也可以使用子类重新实现该参数化的工厂方法。 + + 一般的参数化工厂代码形式如下: + + class Creator { + public: + virtual Product *Create(ProductId); + } + + Product *Creator::Create (ProductId id) { + if (id == MINE) return new MyProduct; + if (id == YOURS) return new YourProduct; + // repeat for remaining products + + return 0; + } + + // subclass of Creator + class DerivedCreator: public Creator { + public: + virtual Product *Create(ProductId); + } + + Product *DerivedCreator::Create(ProductId id) { + id (id == THEIRS) return new TheirProduct; + + return Creator::Create(id); + } + +3. 语言相关的一些特性 + + 具体语言具体分析 + +4. 使用模板来避免继承 + + 想要使用工厂方法,就必须创建相应子类定义具体的工厂方法。在C++中另一种方案是使用模板来避免这种情况: + + class Creator { + public: + virtual Product *CreateProduct() = 0; + } + + // template creator + template + class StandardCreator : public Creator { + public: + virtual Product *CreateProduct(); + } + + template + Product *StandardCreator::CreateProduct () { + return new TheProduct; + } + + // sample + StandardCreator myCreator; + Product *product = myCreator.CreateProduct(); + +5. 命名规则 + + 给工厂方法以特定的命名规则,使得用户能够一目了然,例如: + + Class *doMakeClass(); \ No newline at end of file diff --git a/CreationalPatterns/FactoryMethod/factory_method.py b/CreationalPatterns/FactoryMethod/factory_method.py new file mode 100644 index 000000000..c21e3960b --- /dev/null +++ b/CreationalPatterns/FactoryMethod/factory_method.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/""" + + +class GreekGetter: + + """A simple localizer a la gettext""" + + def __init__(self): + self.trans = dict(dog="σκύλος", cat="γάτα") + + def get(self, msgid): + """We'll punt if we don't have a translation""" + try: + return self.trans[msgid] + except KeyError: + return str(msgid) + + +class EnglishGetter: + + """Simply echoes the msg ids""" + + def get(self, msgid): + return str(msgid) + + +def get_localizer(language="English"): + """The factory method""" + languages = dict(English=EnglishGetter, Greek=GreekGetter) + return languages[language]() + +# Create our localizers +e, g = get_localizer(language="English"), get_localizer(language="Greek") +# Localize some text +for msgid in "dog parrot cat bear".split(): + print(e.get(msgid), g.get(msgid)) + +### OUTPUT ### +# dog σκύλος +# parrot parrot +# cat γάτα +# bear bear diff --git a/CreationalPatterns/FactoryMethod/res/FactoryMethodStructure.png b/CreationalPatterns/FactoryMethod/res/FactoryMethodStructure.png new file mode 100644 index 000000000..48eb75ad5 Binary files /dev/null and b/CreationalPatterns/FactoryMethod/res/FactoryMethodStructure.png differ diff --git a/CreationalPatterns/FactoryMethod/res/ParallelHierarchies.png b/CreationalPatterns/FactoryMethod/res/ParallelHierarchies.png new file mode 100644 index 000000000..63a2c2bce Binary files /dev/null and b/CreationalPatterns/FactoryMethod/res/ParallelHierarchies.png differ diff --git a/CreationalPatterns/Prototype/README.md b/CreationalPatterns/Prototype/README.md new file mode 100644 index 000000000..ce27bfca2 --- /dev/null +++ b/CreationalPatterns/Prototype/README.md @@ -0,0 +1,72 @@ +# 原型模式 Prototype +## 意图 +使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。 + +原型模式的核心在于原型提供的`clone`方法。 + +## 适用性 +当系统和产品的创建、组合、表现分离时,可以使用原型模式,另外: + +* 具体实例化哪个类是运行时才决定,或者 +* 避免构建与产品层级平行的工厂层级,或者 +* 类的实例只会有不同状态组合的其中之一。相比于每次实例化,从原型克隆(clone)会更加的方便。 + +## 结构 + +![structure](./res/Prototype.png) + +## 模式效果 + +原型模式有着抽象工厂模式和生成器模式相同的效果:向用户隐藏了具体的产品,减少用户需要了解的信息。另外,这些模式让用户只需要关心应用相关的内容,产品发生变化时无需做出改变。 + +原型模式特有的一些优势如下: + +1. 运行时添加和删除产品 + + 原型模式使你能通过注册原型实例,将一个新的具体产品合并到系统中,这比其他的创建型模式更为灵活。 +2. 通过改变值来指定新的对象 + + 高度动态的系统让你能够通过指定对象属性,定义新的行为,而非定义新的类。 + + 这一设计使得用户不用编码,就定义了新的“类”。事实上,原型克隆和类实例化很相似,原型模式能够大幅减少系统中所需的类的数量。 + +3. 通过改变结构来指定新的对象 + + 许多应用通过创建部件和子部件的方式创建对象,通过原型模式,我们可将各种部件作为原型,不同结构的部件作为不同的原型,简化对象的创建。 + +4. 减少子类 + + 工厂方法会创建与产品层级平级的工厂方法层级,而原型模式通过克隆创建对象实例,也就不存在创建方法层级。这一特性在C++这种不将类型作为一级成员(first-class objects)的语言中比较实用,在objective-c和python中效果不那么显著。 + +5. 配置应用动态加载类 + + 想要动态加载类,所依赖的构造方法就不能是静态的。通过原型注册和克隆方法,应用能够动态加载类。 + +所有原型的子类必须实现Clone方法,这对于已经存在的类是比较困难的(开发封闭原则,对类的扩展开放,修改封闭)。另外,对于不支持拷贝的对象Clone方法的实现也有难度。 + +## 实现 + +原型模式对于C++这样的静态语言很有帮助,这类语言中类型不是对象,运行时缺少类型信息。对于Smalltalk和Objective C这类语言的效果就不是那么明显。 + +实现原型模式时,需要考虑以下问题: + +1. 使用原型管理 + + 当原型种类不固定时,需要维护可用原型的注册表,用户可能不会关心这个注册表中的具体内容,但是他们会尝试存入和取出原型。 + + 原型管理负责根据给定的Key返回所需的原型,需要提供给用户的操作包括注册、注销、获取,有时还有浏览。 + +2. 实现克隆操作 + + 原型模式最困难的就是实现Clone方法,尤其是存在环形引用时。很多语言都支持拷贝操作,但该如何解决“浅拷贝还是深拷贝”的问题? + + 浅拷贝更加简单和高效,Smalltalk默认为浅拷贝,C++的拷贝由成员类型决定,指针是共享一块内存的。而Clone方法大部分情况下需要深拷贝。实现时要根据具体的情况,决定哪些对象是可以共享,哪些是必须独立的。 + + 如果系统中的对象提供存储和加载操作,你能够使用Clone方法非常容易地实现:存储时就是Clone到内存,加载就是从内存中Clone得到对象。 + +3. 初始化克隆 + + 一些用户乐于直接调用Clone接口,而另一些希望能够向Clone提供一些状态或参数。向Clone操作中传递参数有碍Clone接口的一致性。 + + 如果原型类型提供了设置状态的方法,用户可以在Clone接口之后立刻调用,如果没有这样的方法,就需要引入初始化方法了。初始化方法传入参数,并设置Clone的内部状态。 + diff --git a/CreationalPatterns/Prototype/prototype.py b/CreationalPatterns/Prototype/prototype.py new file mode 100644 index 000000000..2f2a14a82 --- /dev/null +++ b/CreationalPatterns/Prototype/prototype.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import copy + + +class Prototype: + + def __init__(self): + self._objects = {} + + def register_object(self, name, obj): + """Register an object""" + self._objects[name] = obj + + def unregister_object(self, name): + """Unregister an object""" + del self._objects[name] + + def clone(self, name, **attr): + """Clone a registered object and update inner attributes dictionary""" + obj = copy.deepcopy(self._objects.get(name)) + obj.__dict__.update(attr) + return obj + + +class A: + def __init__(self): + self.x = 3 + self.y = 8 + self.z = 15 + self.garbage = [38, 11, 19] + + def __str__(self): + return '{} {} {} {}'.format(self.x, self.y, self.z, self.garbage) + + +def main(): + a = A() + prototype = Prototype() + prototype.register_object('objecta', a) + b = prototype.clone('objecta') + c = prototype.clone('objecta', x=1, y=2, garbage=[88, 1]) + print([str(i) for i in (a, b, c)]) + +if __name__ == '__main__': + main() + +### OUTPUT ### +# ['3 8 15 [38, 11, 19]', '3 8 15 [38, 11, 19]', '1 2 15 [88, 1]'] diff --git a/CreationalPatterns/Prototype/res/Prototype.png b/CreationalPatterns/Prototype/res/Prototype.png new file mode 100644 index 000000000..b55e9a994 Binary files /dev/null and b/CreationalPatterns/Prototype/res/Prototype.png differ diff --git a/CreationalPatterns/Singleton/README.md b/CreationalPatterns/Singleton/README.md new file mode 100644 index 000000000..4b241e723 --- /dev/null +++ b/CreationalPatterns/Singleton/README.md @@ -0,0 +1,92 @@ +# 单例模式 Singleton + +## 意图 +确保类只有唯一的实例,并提供访问该实例全局入口点。 + +## 案例 +单例模式太常见了,以至于想不到什么特别的经典案例。 + +开发中非常常用这一模式,比如配置管理(全局只有一份配置,各处有访问的需要) + +单例模式提供全局唯一的入口点,除此之外无法创建对象。 + +## 适用性 + +* 对象必须有且只有一个实例,且有一个能够访问该对象的入口点; +* 当子类需要扩展这一单例,用户能够使用这一扩展的单例而无需修改他们的代码; + +## 结构 + +![structure](./res/Singleton.png) + +`Instance()`方法是外部访问Singleton实例的唯一入口点。 + +## 模式效果 + +1. 管控对单个实例的访问。 +2. 简化的命名空间 + + 使用单例模式是对使用全局变量的一种改进,能够使得命名空间保持整洁清晰。 + +3. 允许精炼操作和表现 + + 单例也是可以被继承的,能够使用某个单例来配置应用。这样你就可以在运行时选择单例完成应用的配置工作。 + +4. 控制实例的数量 + + 单例模式同样允许让Singleton类有多个实例对象,只需要修改那唯一的入口点即可。 + +5. 相比与类方法更为灵活 + + 相比于使用类方法,使用单例访问成员方法更为灵活。 + + 另外,C++中静态方法(static)永远不能是虚函数,子类也就不能多态复用。 + +## 实现 +1. **确保**只有唯一的对象 + + 代码实现中往往只提供唯一访问对象的入口,而将对象创建方法隐藏。该入口确保返回的唯一的对象在返回之前完成初始化。 + + 以下是C++中的最基本的实现方式: + + // Singleton.h + class Singleton { + public: + static Singleton* Instance(); + protected: + Singleton(); + private: + static Singleton* _instance; + }; + + // Singleton.cpp + Singleton* Singleton::_instance = 0; + + Singleton* Singleton::Instance () { + if (_instance == 0) { + _instance = new Singleton; + } + return _instance; + } + + 对于C++,不将_instance定义为全局或静态对象,主要出于以下几个理由: + + * 无法确保只有一个静态对象被声明;这是一个责任的问题,本类型的单例,就该由本类型确保其唯一性,而不是将其托付给其他角色; + * 单例对象的创建可能需要一些值,这些值必须是程序运行过程中才能取得的; + * C++没有对全局对象的创建顺序有明确定义,这使得使用全局对象作为单例实体,就不允许对其他对象存在依赖,没有必要添加这一限制; +2. 单例类的继承 + + * 通过唯一入口实例化时指定具体的子类类型。**C++中static方法是不能为虚的**,如果存在多个子类需要有某个特定的Tag方能实例化; + * 将实例化方法从基类转移到子类中; + * 使用**单例注册表(registry of singleton)**。提供注册和查找的静态方法,子类负责向基类注册单例,使用时在注册表中查找已注册的单例进行使用 + + class Singleton { + public: + static void Register(const char* name, Singleton*); + static Singleton* Instance(); + protected: + static Singleton* Lookup(const char* name); + private: + static Singleton* _instance; + static List* _registry; + }; \ No newline at end of file diff --git a/CreationalPatterns/Singleton/res/Singleton.png b/CreationalPatterns/Singleton/res/Singleton.png new file mode 100644 index 000000000..e1e0592ee Binary files /dev/null and b/CreationalPatterns/Singleton/res/Singleton.png differ diff --git a/StructuralPatterns/Adapter/README.md b/StructuralPatterns/Adapter/README.md new file mode 100644 index 000000000..83412d63e --- /dev/null +++ b/StructuralPatterns/Adapter/README.md @@ -0,0 +1,94 @@ +# 适配器模式 Adapter + +## 意图 +将一个类的接口转换成用户期望的接口形式。适配器使得原本因为接口不兼容而无法协同工作的类能够被正常调用。 + +适配器模式也被成为封装器、包装器(Wrapper) + +## 经典场景 +1. 欧版插座和亚版插座的适配; +2. Objective-C中的Delegate的使用; + + +## 适用性 + +适配器模式非常容易理解,它的适用场合也很容易想到: + +1. 想要适用一个已经存在的类,但接口却不是我们想要的; +2. 创建了一个希望那个被复用的类,该类需要和未知的、目前不相关的类兼容; +3. 需要适用已经存在的多个子类,但去一一适配这些子类的接口不现实,此时就需要一个`对象适配器`来适配基类的接口 + +## 结构 +适配器有两种实现方式:继承或组合: + +1. 继承 + + 适配器继承操作对象和适配目标。这类适配器成为**类适配器**(**Class Adapter**)。 + + ![structure](./res/AdapterInherit.png) + +2. 组合 + + 适配器继承操作对象,并持有适配目标的引用。这类适配器成为**对象适配器**(**object adapter**). + + ![structure](./res/AdapterComposite.png) + +## 模式效果 +类适配器和对象适配器各有优劣。 + +类适配器: + +* 由于类适配器继承了适配者,这使得该适配器对其子类不起作用; +* 由于是继承关系,适配器能够重载适配者的方法; +* 只增加了一个对象,而无需额外的指针或引用; + +对象适配器: + +* 不但是适配者,连其子类也能被适配器调用; +* 难以重载适配者的行为,有时候需要添加一个额外的子类给适配器调用,从而达成预期; + +使用适配器时,需要考虑以下一些因素: + +1. 适配器需要做多少适配工作?这主要取决于目标接口和适配者接口之间的相似程度。 +2. 插件适配器:类中内置的接口适配器,称为插件适配器,使用时能够使用几个适配器中的一个。 +3. 使用双向适配器来提供透明度。被适配的对象不再兼容Adaptee的接口,因此并不是所有Adaptee可以被使用的地方适配器都可以被使用。双向适配器提供了这样的透明性。在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。 + +## 双向适配器 +网上看来的一个例子,解释了双向适配器的概念。 + +假设有一群狼和一群羊,其中有一只狼W和一张羊S由于某些狗血的原因成为了好朋友,狼想请羊去家里玩,羊也想请狼去家里玩,但两个族群是天敌,不可能友好相处。 + +他们想了一个办法,做一张神奇的披风,一侧是羊皮,一侧是狼皮,羊S披上就可以伪装成狼,狼W披上另一面就可以伪装成羊。有了这张披风,两个小伙伴又能快乐地玩耍了。 + +在这里,这张神奇的披风就是一个双向适配器,经过它的适配,羊可以适配成狼,狼可以适配成羊。 + +![structure](./res/TwoWayAdapter.png) + +## 实现 +1. 适配器的C++实现:适配器公有继承Target,私有继承适配对象,这样适配器就是Target的一个子类,而不会是被适配对象的子类。 +2. 插件适配器的实现: + * 使用抽象方法的插件适配器 + + ![structure](./res/PluggableAdapter1.png) + + TreeDisplay的使用方法: + + GetChildren(n); + for each chile { + AddGraphicNode(CreateGraphicNode(child)); + BuildTree(child); + } + DirectoryTreeDisplay适配了文件系统的方法,使之能够显示目录结构。 + + * 使用代理的插件适配器 + + ![structure](./res/PluggableAdapter2.png) + + TreeDisplay的使用方法: + + delegate->GetChildren(this, n); + for each chile { + AddGraphicNode(delegate->CreateGraphicNode(this, child)); + BuildTree(child); + } + 委托这一形式在Objective-C中使用的非常频繁,也非常实用。TreeDisplay中包含一个delegate对象,该对象可以根据不同的需求进行切换。 \ No newline at end of file diff --git a/StructuralPatterns/Adapter/adapter.py b/StructuralPatterns/Adapter/adapter.py new file mode 100644 index 000000000..1620ce4c4 --- /dev/null +++ b/StructuralPatterns/Adapter/adapter.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://ginstrom.com/scribbles/2008/11/06/generic-adapter-class-in-python/""" + +import os + +class Dog(object): + def __init__(self): + self.name = "Dog" + def bark(self): + return "woof!" + +class Cat(object): + def __init__(self): + self.name = "Cat" + def meow(self): + return "meow!" + +class Human(object): + def __init__(self): + self.name = "Human" + def speak(self): + return "'hello'" + + +class Car(object): + def __init__(self): + self.name = "Car" + def make_noise(self, octane_level): + return "vroom{0}".format("!" * octane_level) + + +class Adapter(object): + + """ + Adapts an object by replacing methods. + Usage: + dog = Dog + dog = Adapter(dog, dict(make_noise=dog.bark)) + + >>> objects = [] + >>> dog = Dog() + >>> objects.append(Adapter(dog, dict(make_noise=dog.bark))) + >>> cat = Cat() + >>> objects.append(Adapter(cat, dict(make_noise=cat.meow))) + >>> human = Human() + >>> objects.append(Adapter(human, dict(make_noise=human.speak))) + >>> car = Car() + >>> car_noise = lambda: car.make_noise(3) + >>> objects.append(Adapter(car, dict(make_noise=car_noise))) + + >>> for obj in objects: + ... print('A {} goes {}'.format(obj.name, obj.make_noise())) + A Dog goes woof! + A Cat goes meow! + A Human goes 'hello' + A Car goes vroom!!! + """ + + def __init__(self, obj, adapted_methods): + """We set the adapted methods in the object's dict""" + self.obj = obj + self.__dict__.update(adapted_methods) + + def __getattr__(self, attr): + """All non-adapted calls are passed to the object""" + return getattr(self.obj, attr) + + +def main(): + objects = [] + dog = Dog() + objects.append(Adapter(dog, dict(make_noise=dog.bark))) + cat = Cat() + objects.append(Adapter(cat, dict(make_noise=cat.meow))) + human = Human() + objects.append(Adapter(human, dict(make_noise=human.speak))) + car = Car() + objects.append(Adapter(car, dict(make_noise=lambda: car.make_noise(3)))) + + for obj in objects: + print("A {0} goes {1}".format(obj.name, obj.make_noise())) + + +if __name__ == "__main__": + main() + +### OUTPUT ### +# A Dog goes woof! +# A Cat goes meow! +# A Human goes 'hello' +# A Car goes vroom!!! diff --git a/StructuralPatterns/Adapter/res/AdapterComposite.png b/StructuralPatterns/Adapter/res/AdapterComposite.png new file mode 100644 index 000000000..623ee51d1 Binary files /dev/null and b/StructuralPatterns/Adapter/res/AdapterComposite.png differ diff --git a/StructuralPatterns/Adapter/res/AdapterInherit.png b/StructuralPatterns/Adapter/res/AdapterInherit.png new file mode 100644 index 000000000..92de0e342 Binary files /dev/null and b/StructuralPatterns/Adapter/res/AdapterInherit.png differ diff --git a/StructuralPatterns/Adapter/res/PluggableAdapter1.png b/StructuralPatterns/Adapter/res/PluggableAdapter1.png new file mode 100644 index 000000000..8beaba766 Binary files /dev/null and b/StructuralPatterns/Adapter/res/PluggableAdapter1.png differ diff --git a/StructuralPatterns/Adapter/res/PluggableAdapter2.png b/StructuralPatterns/Adapter/res/PluggableAdapter2.png new file mode 100644 index 000000000..fd120932b Binary files /dev/null and b/StructuralPatterns/Adapter/res/PluggableAdapter2.png differ diff --git a/StructuralPatterns/Adapter/res/TwoWayAdapter.png b/StructuralPatterns/Adapter/res/TwoWayAdapter.png new file mode 100644 index 000000000..45e474776 Binary files /dev/null and b/StructuralPatterns/Adapter/res/TwoWayAdapter.png differ diff --git a/StructuralPatterns/Bridge/README.md b/StructuralPatterns/Bridge/README.md new file mode 100644 index 000000000..de5b9392a --- /dev/null +++ b/StructuralPatterns/Bridge/README.md @@ -0,0 +1,54 @@ +# 桥连模式 Bridge +解耦抽象和实现,使两者能够互不干扰地变化。 + +举例来说,画图,我可以画矩形,圆,三角形等等,在哪里画呢?我可以在pdf上画,也可以在doc上面画。画什么图和在哪里画都是可以独立变化的,此种情况就比较适合用桥模式。就是说设计中有超过一维的变化我们就可以用桥模式。如果只有一维在变化,那么我们用继承就可以圆满的解决问题。 + +## 适用性 +* 避免抽象与实现之间固定的绑定(例如子类实现接口方式),这样做能够实现动态切换实现。 +* 抽象和实现都会被继承。桥连模式使你能够组合不同的抽象和实现。 +* 修改抽象的实现时,不需要用户重新编译才能起作用。 +* C++中希望对客户完全隐藏抽象的实现,可以考虑使用桥连模式,因为在C++中类的表现在类接口中是可见的。 +* 系统中的类可能存在如下结构图所示的增殖情况,希望能摆脱这种糟糕的设计 + + ![structure](./res/Proliferation.png) + + >最顶层的抽象为Window,在不同的桌面平台有不同的子类XWindow和PMWindow。除了从平台角度划分,还可以从功能角度划分,存在基类IconWindow,而IconWindow也存在不同平台的实现XIconWindow和PMIconWindow。没种功能的Window都会存在多平台的实现,如果支持的平台增加了,没种功能Window也需要扩展对应实现。这就是所谓的类的增殖。 + +* 希望能在多个对象间共享实现,而且这个希望是对客户隐藏的。 + +## 结构 + +![structure](./res/Bridge.png) + +## 模式效果 + +1. 解锁接口和实现 +2. 提高可扩展性 + + 能够对抽象层级和实现层级分别扩展,不变的部分能够正常运行。 +3. 向客户隐藏实现细节 + +## 实现 + +1. 唯一的实现 + + 有些场合只有一种实现,就没必要创建一个抽象的实现基类了。这是一种桥连模式的退化情况,但依然尤其价值。 + +2. 创建正确的实现对象 + + 如何决定该使用哪种实现? + + 如果抽象知道所有的具体实现类,就能在实例化时构造相应的实现。这所有的实现可以被存放在列表、或者映射中。 + + 另一种方式是初始化时选择一种默认的实现,然后根据具体使用变更实现。 + + 还有一种方案是将实现的选择委托给另一个对象,例如可以是一个抽象工厂,工厂负责返回对应的一整套实现,好处是抽象并不直接和任何实现类耦合。 + +3. 共享实现 +4. 使用多重继承 + + 部分语言支持多重继承(比如C++),一个子类能够public继承抽象,private继承具体实现,从而达成预期效果。 + + 但这一做法使得抽象和实现永久绑定,本质上已经不再是桥连模式了。 + + diff --git a/StructuralPatterns/Bridge/bridge.py b/StructuralPatterns/Bridge/bridge.py new file mode 100644 index 000000000..1c3c07472 --- /dev/null +++ b/StructuralPatterns/Bridge/bridge.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Bridge_Pattern#Python""" + + +# ConcreteImplementor 1/2 +class DrawingAPI1(object): + + def draw_circle(self, x, y, radius): + print('API1.circle at {}:{} radius {}'.format(x, y, radius)) + + +# ConcreteImplementor 2/2 +class DrawingAPI2(object): + + def draw_circle(self, x, y, radius): + print('API2.circle at {}:{} radius {}'.format(x, y, radius)) + + +# Refined Abstraction +class CircleShape(object): + + def __init__(self, x, y, radius, drawing_api): + self._x = x + self._y = y + self._radius = radius + self._drawing_api = drawing_api + + # low-level i.e. Implementation specific + def draw(self): + self._drawing_api.draw_circle(self._x, self._y, self._radius) + + # high-level i.e. Abstraction specific + def scale(self, pct): + self._radius *= pct + + +def main(): + shapes = ( + CircleShape(1, 2, 3, DrawingAPI1()), + CircleShape(5, 7, 11, DrawingAPI2()) + ) + + for shape in shapes: + shape.scale(2.5) + shape.draw() + + +if __name__ == '__main__': + main() + +### OUTPUT ### +# API1.circle at 1:2 radius 7.5 +# API2.circle at 5:7 radius 27.5 diff --git a/StructuralPatterns/Bridge/res/Bridge.png b/StructuralPatterns/Bridge/res/Bridge.png new file mode 100644 index 000000000..e6e4aec04 Binary files /dev/null and b/StructuralPatterns/Bridge/res/Bridge.png differ diff --git a/StructuralPatterns/Bridge/res/Proliferation.png b/StructuralPatterns/Bridge/res/Proliferation.png new file mode 100644 index 000000000..80acadf26 Binary files /dev/null and b/StructuralPatterns/Bridge/res/Proliferation.png differ diff --git a/StructuralPatterns/Composite/README.md b/StructuralPatterns/Composite/README.md new file mode 100644 index 000000000..fe30a3345 --- /dev/null +++ b/StructuralPatterns/Composite/README.md @@ -0,0 +1,95 @@ +# 组合模式 Composite +## 意图 +将对象以树形结构组合成一个整体,表现出部分-整体这一层级结构。组合模式使得用户能够像使用对象一样使用这个组合。 + +## 经典场景 +组合模式在日常绘图软件的使用中经常会用到。例如先绘制了几个图形,然后将这些图形选中,将它们“组合”起来,之后就能像使用单个图形那样,拖拽、缩放、连线这些操作都能使用。 + +## 适用性 +1. 希望表达对象之间“部分-整体”的层级结构; +2. 希望用户能够忽略单个对象和对象组合之间的差异,一视同仁地进行操作; + +## 结构 + +![structure](./res/CompositeStructure.png) + +一个典型的组合模式结构一般如下: + +![structure](./res/TypicalComposite.png) + +用户使用抽象基类Component的接口,如果实际对象是Leaf,则直接操作,如果对象是Composite,则依次执行其中的Component操作。 + +## 使用效果 +1. 定义了由简单对象和组合对象组成了层级结构,简单对象能够组成更为复杂的对象,复杂对象也能用于继续组合。能够处理简单对象的用户代码,也能处理复杂对象; +2. 让客户代码更简洁。客户代码能够统一处理组合对象和单个对象,无需对其分类处理; +3. 能够很容易地添加新的组件,客户代码可保持不变; +4. 缺点是会限制你的设计,因为要让每个组件都极度统一。有时候你希望某些组合中只包括组件集的一个子集,此时就需要一些运行时检验了。 + +## 实现 +1. 在Component基类中添加显示的父节点的引用 + + 有利于层级关系的处理。父节点引用的设置和消除尽量写在`Add`和`Remove`方法中,而不要开放给用户使用; + +2. 共享组件。 + + 两个组合中有同一个对象,这两个组合可以共享这一对象,这有时候非常有用,比如减少存储空间需求,但在子节点中记录多个父节点会比较困难。这一话题在**蝇量模式**再深入讨论。 +3. 最大化组件接口。 + + 为了能让客户代码方便地调用接口,当然希望组件基类有尽可能完备的接口提供。但这样又会让Leaf对象中出现大量的方法根本没有实现。 +4. 子类管理方法的声明 + + Composite实现了Add和Remove方法,但这两个方法该在那里声明呢?是Component基类,还是Composite基类? + + 这是一个安全性和透明度的权衡: + + * 在所有类结构的基类Component中定义管理接口提供了透明度,使用户能够对所有对象一视同仁;但这么做缺乏安全性,因为用户可能会在Leaf中调用Add、Remove方法; + * 在Composite基类中定义管理接口能够提供安全性,但这样的话Leaf和Composite就有了不同的接口,缺少透明度,用户就必须知道当前对象的基本类型; + + 从OO开发来看,透明度应该更为重要一些,为了弥补安全性问题,可以有以下方法可以参考: + + class Composite + + class Component { + public: + // ... + virtual Composite *GetComposite() { return 0; } + } + + class Composite : public Component { + public: + void Add(Component *); + // ... + virtual Composite *GetComposite() { return this; } + } + + class Leaf : public Component { + public: + // ... + } + + // ... + if (test = aComponent->GetComposite()) { + test->Add(new Leaf); + } + +5. 该不该在Component中添加子类列表? + + 同样是处于透明度考虑,那是否该这么做?这样做最大的缺点是浪费空间,只有在仅有较少Leaf的系统中这么做。 + +6. 子对象的排序 + + 可能会有Composite中的Component排序的需求,例如绘图程序中,每个Component就是以图层的前后作为排序依据。如果确实有这样的需求,就要在实现时考虑这个问题。 + +7. 通过缓存提高性能 + + 如果Composite经常会被查询或遍历,就有为其缓存的必要。当修改了其中一个Leaf时,这一缓存就需要被更新,所以显示指向父节点的引用此时就显得各位重要。 + +8. 生命周期管理 + + 子对象的生命周期由其父对象管理,即由Composite负责删除其中的Leaf或子Composite。 + + 但是,当Leaf对象是不可变且共享的,就不符合这一原则。 + +9. 该使用何种数据结构来存储对象? + + 链表、树、数组、哈希表这些都可以,效率优先。事实上有时甚至不需要使用数据结构,Composite中的Component是一个“萝卜一个坑”预先定义好了的。 \ No newline at end of file diff --git a/StructuralPatterns/Composite/composite.py b/StructuralPatterns/Composite/composite.py new file mode 100644 index 000000000..9b8cc56fa --- /dev/null +++ b/StructuralPatterns/Composite/composite.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +A class which defines a composite object which can store +hieararchical dictionaries with names. + +This class is same as a hiearchical dictionary, but it +provides methods to add/access/modify children by name, +like a Composite. + +Created Anand B Pillai + +""" +__author__ = "Anand B Pillai" +__maintainer__ = "Anand B Pillai" +__version__ = "0.2" + + +def normalize(val): + """ Normalize a string so that it can be used as an attribute + to a Python object """ + + if val.find('-') != -1: + val = val.replace('-', '_') + + return val + + +def denormalize(val): + """ De-normalize a string """ + + if val.find('_') != -1: + val = val.replace('_', '-') + + return val + + +class SpecialDict(dict): + + """ A dictionary type which allows direct attribute + access to its keys """ + + def __getattr__(self, name): + + if name in self.__dict__: + return self.__dict__[name] + elif name in self: + return self.get(name) + else: + # Check for denormalized name + name = denormalize(name) + if name in self: + return self.get(name) + else: + raise AttributeError('no attribute named %s' % name) + + def __setattr__(self, name, value): + + if name in self.__dict__: + self.__dict__[name] = value + elif name in self: + self[name] = value + else: + # Check for denormalized name + name2 = denormalize(name) + if name2 in self: + self[name2] = value + else: + # New attribute + self[name] = value + + +class CompositeDict(SpecialDict): + + """ A class which works like a hierarchical dictionary. + This class is based on the Composite design-pattern """ + + ID = 0 + + def __init__(self, name=''): + + if name: + self._name = name + else: + self._name = ''.join(('id#', str(self.__class__.ID))) + self.__class__.ID += 1 + + self._children = [] + # Link back to father + self._father = None + self[self._name] = SpecialDict() + + def __getattr__(self, name): + + if name in self.__dict__: + return self.__dict__[name] + elif name in self: + return self.get(name) + else: + # Check for denormalized name + name = denormalize(name) + if name in self: + return self.get(name) + else: + # Look in children list + child = self.findChild(name) + if child: + return child + else: + attr = getattr(self[self._name], name) + if attr: + return attr + + raise AttributeError('no attribute named %s' % name) + + def isRoot(self): + """ Return whether I am a root component or not """ + + # If I don't have a parent, I am root + return not self._father + + def isLeaf(self): + """ Return whether I am a leaf component or not """ + + # I am a leaf if I have no children + return not self._children + + def getName(self): + """ Return the name of this ConfigInfo object """ + + return self._name + + def getIndex(self, child): + """ Return the index of the child ConfigInfo object 'child' """ + + if child in self._children: + return self._children.index(child) + else: + return -1 + + def getDict(self): + """ Return the contained dictionary """ + + return self[self._name] + + def getProperty(self, child, key): + """ Return the value for the property for child + 'child' with key 'key' """ + + # First get the child's dictionary + childDict = self.getInfoDict(child) + if childDict: + return childDict.get(key, None) + + def setProperty(self, child, key, value): + """ Set the value for the property 'key' for + the child 'child' to 'value' """ + + # First get the child's dictionary + childDict = self.getInfoDict(child) + if childDict: + childDict[key] = value + + def getChildren(self): + """ Return the list of immediate children of this object """ + + return self._children + + def getAllChildren(self): + """ Return the list of all children of this object """ + + l = [] + for child in self._children: + l.append(child) + l.extend(child.getAllChildren()) + + return l + + def getChild(self, name): + """ Return the immediate child object with the given name """ + + for child in self._children: + if child.getName() == name: + return child + + def findChild(self, name): + """ Return the child with the given name from the tree """ + + # Note - this returns the first child of the given name + # any other children with similar names down the tree + # is not considered. + + for child in self.getAllChildren(): + if child.getName() == name: + return child + + def findChildren(self, name): + """ Return a list of children with the given name from the tree """ + + # Note: this returns a list of all the children of a given + # name, irrespective of the depth of look-up. + + children = [] + + for child in self.getAllChildren(): + if child.getName() == name: + children.append(child) + + return children + + def getPropertyDict(self): + """ Return the property dictionary """ + + d = self.getChild('__properties') + if d: + return d.getDict() + else: + return {} + + def getParent(self): + """ Return the person who created me """ + + return self._father + + def __setChildDict(self, child): + """ Private method to set the dictionary of the child + object 'child' in the internal dictionary """ + + d = self[self._name] + d[child.getName()] = child.getDict() + + def setParent(self, father): + """ Set the parent object of myself """ + + # This should be ideally called only once + # by the father when creating the child :-) + # though it is possible to change parenthood + # when a new child is adopted in the place + # of an existing one - in that case the existing + # child is orphaned - see addChild and addChild2 + # methods ! + self._father = father + + def setName(self, name): + """ Set the name of this ConfigInfo object to 'name' """ + + self._name = name + + def setDict(self, d): + """ Set the contained dictionary """ + + self[self._name] = d.copy() + + def setAttribute(self, name, value): + """ Set a name value pair in the contained dictionary """ + + self[self._name][name] = value + + def getAttribute(self, name): + """ Return value of an attribute from the contained dictionary """ + + return self[self._name][name] + + def addChild(self, name, force=False): + """ Add a new child 'child' with the name 'name'. + If the optional flag 'force' is set to True, the + child object is overwritten if it is already there. + + This function returns the child object, whether + new or existing """ + + if type(name) != str: + raise ValueError('Argument should be a string!') + + child = self.getChild(name) + if child: + # print('Child %s present!' % name) + # Replace it if force==True + if force: + index = self.getIndex(child) + if index != -1: + child = self.__class__(name) + self._children[index] = child + child.setParent(self) + + self.__setChildDict(child) + return child + else: + child = self.__class__(name) + child.setParent(self) + + self._children.append(child) + self.__setChildDict(child) + + return child + + def addChild2(self, child): + """ Add the child object 'child'. If it is already present, + it is overwritten by default """ + + currChild = self.getChild(child.getName()) + if currChild: + index = self.getIndex(currChild) + if index != -1: + self._children[index] = child + child.setParent(self) + # Unset the existing child's parent + currChild.setParent(None) + del currChild + + self.__setChildDict(child) + else: + child.setParent(self) + self._children.append(child) + self.__setChildDict(child) + + +if __name__ == "__main__": + window = CompositeDict('Window') + frame = window.addChild('Frame') + tfield = frame.addChild('Text Field') + tfield.setAttribute('size', '20') + + btn = frame.addChild('Button1') + btn.setAttribute('label', 'Submit') + + btn = frame.addChild('Button2') + btn.setAttribute('label', 'Browse') + + # print(window) + # print(window.Frame) + # print(window.Frame.Button1) + # print(window.Frame.Button2) + print(window.Frame.Button1.label) + print(window.Frame.Button2.label) + +### OUTPUT ### +# Submit +# Browse diff --git a/StructuralPatterns/Composite/res/CompositeStructure.png b/StructuralPatterns/Composite/res/CompositeStructure.png new file mode 100644 index 000000000..d588b9845 Binary files /dev/null and b/StructuralPatterns/Composite/res/CompositeStructure.png differ diff --git a/StructuralPatterns/Composite/res/TypicalComposite.png b/StructuralPatterns/Composite/res/TypicalComposite.png new file mode 100644 index 000000000..8cf28aad1 Binary files /dev/null and b/StructuralPatterns/Composite/res/TypicalComposite.png differ diff --git a/StructuralPatterns/Decorator/README.md b/StructuralPatterns/Decorator/README.md new file mode 100644 index 000000000..5c6bd2530 --- /dev/null +++ b/StructuralPatterns/Decorator/README.md @@ -0,0 +1,66 @@ +# 装饰器模式 Decorator +## 意图 +动态地向对象添加额外的功能。装饰器提供了扩展对象功能的选择。 + +静态添加功能,第一时间想到的就是继承,但动态地、只针对对象个体地添加功能的需求,就是装饰器所擅长的。 + +## 经典场景 +装饰器有一个非常经典的咖啡的范例,有机会将这一场景引入,能够更好地说明。 + +## 适用性 +* 动态地、透明地向单个对象添加功能(或者说职责),而不会影响同类型的对象; +* 添加的职责能够被收回; +* 想要添加功能,但是使用子类继承的方法是不切实际的,例如添加大量独立的扩展会影响大量代码,或者某个类不可见,无法进行继承; + * 例如结构中描述的,ConcreteComponent类文件不可见,只能访问到其基类Component的场景。 + +## 结构 + +![structure](./res/DecoratorStructure.png) + +## 模式效果 +装饰器模式至少有两个优点、两点不足: + +1. 相比与静态的继承更为灵活 + + 装饰器能够在运行时为对象添加职责。 + 另一方面,装饰器允许为对象多次添加职责。以奶茶为例,可提供的奶茶类型有原味奶茶(奶茶基类)、半糖、全糖、多糖这么四种甜度的,价格也不一。如果使用继承,就需要有三个子类,但是如果使用装饰器,每被封装一次就是加半糖,不同类型的奶茶只是装饰次数的差异。 + +2. 避免在架构层级中创建囊括过多特性的类 + + 装饰器实现了需要时附加功能的特性,而不是在设计时就要充满远见地设计完备而复杂的类,对象不用在囊括许多本身根本用不到的特性。 + + 如果需要扩展特性,实现也非常方便。 + +3. 装饰器和对象本身并不一致 + + 装饰器和对象的外特性一致,但本质上并非同一事物,当需要用到对象的ID时会发生错误。 + 具体到python的装饰器中,这个问题可以被语法糖`@wraps(fn)`解决。 + +4. 大量的小对象 + + 使用装饰器注定了会产生大量相似的小对象,虽然系统知道谁是谁,但是代码Review、调试时会比较困难。 + +## 实现 +1. 接口一致性 + + 装饰器的接口和对象的接口必须一致!因此两者都是继承自同一基类。 + +2. 省略装饰器的抽象类 + + 如果只需要添加一个职责,那就不要定义什么抽象类了,直接使用装饰器基类进行装饰得了。 + +3. 保持Component基类轻量化 + + Component基类中只定义接口,不定义具体成员,这样创建的装饰器也是轻量化的,否则大量的复杂的装饰器使用起来就不那么简洁轻量了。 + +4. “治标” vs “治本” + + 装饰器是在原有行为上添加一层,可以认为是一种“治标”,另一种选择是改变对象的内部机制,称为“治本”,例如使用策略模式。此处的“治标”与“治本”并无绝对的优劣之分,不同场景下有不同的擅场。 + + 当Component是一个重量级对象,策略模式是一个更好的选择。Component将其某个行为交给独立的策略对象,策略模式能够在不同的策略对象间切换行为。关于策略模式,在相关章节中再做展开。 + + 由于装饰器模式在外部修改Component行为,Component本身对装饰器一无所知,所以装饰器对于行为是透明的。 + + 而对于策略模式,Component知道可能的扩展,会持有相应策略的引用。 + + \ No newline at end of file diff --git a/StructuralPatterns/Decorator/decorator.py b/StructuralPatterns/Decorator/decorator.py new file mode 100644 index 000000000..ce96f3153 --- /dev/null +++ b/StructuralPatterns/Decorator/decorator.py @@ -0,0 +1,31 @@ +"""https://docs.python.org/2/library/functools.html#functools.wraps""" +"""https://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python/739665#739665""" + +from functools import wraps + + +def makebold(fn): + @wraps(fn) + def wrapped(): + return "" + fn() + "" + return wrapped + + +def makeitalic(fn): + @wraps(fn) + def wrapped(): + return "" + fn() + "" + return wrapped + + +@makebold +@makeitalic +def hello(): + """a decorated hello world""" + return "hello world" + +if __name__ == '__main__': + print('result:{} name:{} doc:{}'.format(hello(), hello.__name__, hello.__doc__)) + +### OUTPUT ### +# result:hello world name:hello doc:a decorated hello world diff --git a/StructuralPatterns/Decorator/res/DecoratorStructure.png b/StructuralPatterns/Decorator/res/DecoratorStructure.png new file mode 100644 index 000000000..1e8852462 Binary files /dev/null and b/StructuralPatterns/Decorator/res/DecoratorStructure.png differ diff --git a/StructuralPatterns/Facade/README.md b/StructuralPatterns/Facade/README.md new file mode 100644 index 000000000..444ae8edb --- /dev/null +++ b/StructuralPatterns/Facade/README.md @@ -0,0 +1,38 @@ +# 外观模式 Facade + +## 意图 +为子系统的一系列接口,提供一个统一的接口。外观模式为子系统提供了更高层级的接口调用,使系统更方便使用。 + +举一个例子,用户想要入手一台新电脑,他当然可以自己去挑选主板、内存、硬盘等,然后将这些组装起来(底层接口调用);也可以找一个懂行的人,帮自己去完成这些技术含量更高的工作(上层接口调用,此时这个被委托人就是一个Facade)。 + +## 适用范围 +* 希望对一个复杂的子系统提供简单的接口,用户主需要面向这一外观即可访问子系统; +* 用户调用一个抽象的实现时会涉及到许多的依赖,通过外观实现解耦; +* 希望将子系统层级化,使用外观模式来定义每个层级的入口。如果各个子系统是独立的,可以使用外观作为他们之间的交互接口 + +## 结构 + +![structure](./res/Facade.png) + +外观模式的结构非常灵活,只要是通过Facade访问子系统就可以,其内部具体是如何调用没有限制。 + +## 模式效果 + +1. 将用户和子系统隔离,减少了用户需要处理的对象,简化了子系统的使用; +2. 使用户和子系统之间松耦合。松耦合使得子系统内部成员变化时不会影响用户。 +3. 在必要的情况下,外观模式并不阻止直接使用子系统的类,由用户决定是要易用性,还是通用性。 + +## 实现 +实现外观模式时,需要考虑以下一些因素 + +1. 减少用户-系统之间的耦合。 + + 除了外观模式本身的松耦合特性,更进一步的做法是创建Facade抽象类,对于不同的子系统实现具体的Facade子类。用户通过Facade基类的接口与子系统交互。这一做法使得用户无需了解当前使用的是哪个子类的实现。 + + 另一种方法是为Facade配置不同的子类对象(类似策略模式),通过替换子系统对象来定制Facade。 + +2. 公有还是私有的子系统类? + + 子系统可以和类进行类比,他们同样包含接口,同样封装内容——类封装状态和方法,子系统封装类。就像设置类的访问控制一样,我们可以考虑子系统接口究竟是公开的,还是私有的。 + + 公有的接口是所有用户都能访问的,私有接口是仅对内部类开放的。Facade肯定是一个公有的接口,但不是唯一的一个,子系统中的其他接口也可以是公有的,有通用性需求的用户可以访问。 \ No newline at end of file diff --git a/StructuralPatterns/Facade/facade.py b/StructuralPatterns/Facade/facade.py new file mode 100644 index 000000000..77197dc30 --- /dev/null +++ b/StructuralPatterns/Facade/facade.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import time + +SLEEP = 0.5 + + +# Complex Parts +class TC1: + + def run(self): + print("###### In Test 1 ######") + time.sleep(SLEEP) + print("Setting up") + time.sleep(SLEEP) + print("Running test") + time.sleep(SLEEP) + print("Tearing down") + time.sleep(SLEEP) + print("Test Finished\n") + + +class TC2: + + def run(self): + print("###### In Test 2 ######") + time.sleep(SLEEP) + print("Setting up") + time.sleep(SLEEP) + print("Running test") + time.sleep(SLEEP) + print("Tearing down") + time.sleep(SLEEP) + print("Test Finished\n") + + +class TC3: + + def run(self): + print("###### In Test 3 ######") + time.sleep(SLEEP) + print("Setting up") + time.sleep(SLEEP) + print("Running test") + time.sleep(SLEEP) + print("Tearing down") + time.sleep(SLEEP) + print("Test Finished\n") + + +# Facade +class TestRunner: + + def __init__(self): + self.tc1 = TC1() + self.tc2 = TC2() + self.tc3 = TC3() + self.tests = [i for i in (self.tc1, self.tc2, self.tc3)] + + def runAll(self): + [i.run() for i in self.tests] + + +# Client +if __name__ == '__main__': + testrunner = TestRunner() + testrunner.runAll() + +### OUTPUT ### +# ###### In Test 1 ###### +# Setting up +# Running test +# Tearing down +# Test Finished +# +# ###### In Test 2 ###### +# Setting up +# Running test +# Tearing down +# Test Finished +# +# ###### In Test 3 ###### +# Setting up +# Running test +# Tearing down +# Test Finished +# diff --git a/StructuralPatterns/Facade/res/Facade.png b/StructuralPatterns/Facade/res/Facade.png new file mode 100644 index 000000000..42cc641d7 Binary files /dev/null and b/StructuralPatterns/Facade/res/Facade.png differ diff --git a/StructuralPatterns/Flyweight/README.md b/StructuralPatterns/Flyweight/README.md new file mode 100644 index 000000000..971945bad --- /dev/null +++ b/StructuralPatterns/Flyweight/README.md @@ -0,0 +1,67 @@ +# 蝇量模式 Flyweight + +## 意图 +使用共享方式高效地支持大量的粒度细的对象。 + +又称为享元模式。 + +## 进一步说明 +蝇量模式的核心可以认为就是存在一个蝇量对象池,对象池中的对象记下对象的内在属性,当需要获取对象时首先尝试从池中取出对象,取不到时才考虑创建。 + +例如,内存池。 + +## 适用性 +蝇量模式的效果非常依赖于使用场景,确保只有在以下条件**都满足**时,使用蝇量模式: + +* 应用中会有大量的对象; +* 存储这些大量的对象会非常耗费资源; +* 对象的大部分状态是外在的; +* 一旦将这些外在状态移除,许多对象能够用相对较少的共享对象替代; +* 应用不依赖于对象标识。因为蝇量对象是共享的,只要逻辑上不同的对象,即使共享了同一个对象,也能被认为带有不同标识。 + +## 结构 + +![structure](./res/FlyweightStructure.png) + +下图展示了通常蝇量模式是如何使用的 + +![structure](./res/FlyweightObj.png) + +### 核心角色责任 +* Flyweight + * 申明一个接口,接收外部状态并执行相应操作 +* ConcreteFlyweight + * 存储内部状态,实现接口; + * 必须是可共享的,只能有内部状态,必须是上下文无关的; +* UnsharedConcreteFlyweight + * 并非所有的Flyweight都要是可共享的; + * 通常UnsharedConcreteFlyweight包含ConcreteFlyweight +* FlyweightFactory + * 创建、管理flyweight对象 + * 如果flyweight已存在,返回共享对象;否则,创建flyweight对象并返回 +* Client + * 持有flyweight的引用 + * 计算或存储flyweight对象的外部状态 + +## 模式效果 +蝇量模式可能会增加运行时处理外部状态的资源消耗,但是能够通过对象共享节省存储。存储容量与以下几个因素有关: + +* 通过贡献减少的对象数量; +* 每个对象的内部状态的数量; + * 空间和时间的取舍 +* 外部状态是否需要计算或存储; + * 通过计算而不是存储得到外部状态更能节省空间 + +蝇量模式经常和组合模式共同使用。 + +## 实现 +1. 删除外部状态 + * 模式是否适用很大程度上由能否很方便地识别出外部状态、并将其从对象中删除有关; + * 理想状态是外部状态能够非常容易地计算得到; +2. 管理共享对象 + * client不允许直接实例化共享对象,而应委托给FlyweightFactory; + * FlyweightFactory返回共享对象,或实例化对象; + * 可以使用引用计数维护共享对象,但在flyweight数量固定且较少时,没有这么做的必要 + + + diff --git a/StructuralPatterns/Flyweight/flyweight.py b/StructuralPatterns/Flyweight/flyweight.py new file mode 100644 index 000000000..efa489816 --- /dev/null +++ b/StructuralPatterns/Flyweight/flyweight.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""http://codesnipers.com/?q=python-flyweights""" + +import weakref + + +class Card(object): + + """The object pool. Has builtin reference counting""" + _CardPool = weakref.WeakValueDictionary() + + """Flyweight implementation. If the object exists in the + pool just return it (instead of creating a new one)""" + def __new__(cls, value, suit): + obj = Card._CardPool.get(value + suit, None) + if not obj: + obj = object.__new__(cls) + Card._CardPool[value + suit] = obj + obj.value, obj.suit = value, suit + return obj + + # def __init__(self, value, suit): + # self.value, self.suit = value, suit + + def __repr__(self): + return "" % (self.value, self.suit) + + +if __name__ == '__main__': + # comment __new__ and uncomment __init__ to see the difference + c1 = Card('9', 'h') + c2 = Card('9', 'h') + print(c1, c2) + print(c1 == c2) + print(id(c1), id(c2)) + +### OUTPUT ### +# +# True +# 140368617673296 140368617673296 diff --git a/StructuralPatterns/Flyweight/res/FlyweightObj.png b/StructuralPatterns/Flyweight/res/FlyweightObj.png new file mode 100644 index 000000000..c8210bfaf Binary files /dev/null and b/StructuralPatterns/Flyweight/res/FlyweightObj.png differ diff --git a/StructuralPatterns/Flyweight/res/FlyweightStructure.png b/StructuralPatterns/Flyweight/res/FlyweightStructure.png new file mode 100644 index 000000000..d09d104d1 Binary files /dev/null and b/StructuralPatterns/Flyweight/res/FlyweightStructure.png differ diff --git a/StructuralPatterns/Proxy/README.md b/StructuralPatterns/Proxy/README.md new file mode 100644 index 000000000..8831d84fe --- /dev/null +++ b/StructuralPatterns/Proxy/README.md @@ -0,0 +1,38 @@ +# 代理模式 Proxy + +## 意图 +为对象提供代理或占位符,实现访问控制。除了Proxy,也被称为Surrogate。 + +需要访问控制的其中一个理由是,延迟对象的创建和初始化,直到我们真的需要使用它。例如有一个包含了很多图片的文档,如果在打开时就将所有的图片都加载到内存中,那文档打开就会非常迟缓,这时候就可以用一个图片代理对象来替换真实的图片。图片代理有着图片的一些基本信息,例如长和宽,也有着如何找到这张图片的引用,例如URL。只有到需要显示这些图片时,才会去尝试通过引用加载图片。 + +## 使用范围 + +当需要一个相比于指针更为通用、复杂的引用,来指代对象时,通常会考虑使用代理模式,以下是一些使用的场景: + +1. 远程代理(**remote proxy**)提供了不同地址空间中对象的本地代表。 +2. 虚拟代理(**virtual proxy**)为初始化开销大的对象设置代理,延迟创建对象。 +3. 保护代理(**protection proxy**)管控原始对象的访问权限。当对象对于不同用户提供不同访问权限时,这类代理很有用。 +4. 智能引用(**smart reference**)是裸指针的一个替代,当对象被访问时会有一些额外行为,典型的使用包括: + * 引用计数 + * 首次引用时负责将加载对象 + * 当对象被访问时检查是否被锁 + +## 结构 + +![structure](./res/Proxy.png) + +## 模式效果 + +1. 远程代理屏蔽了对象其实是不同地址空间的事实; +2. 虚拟代理优化了对象加载; +3. 保护代理和智能引用都在对象访问时添加了额外的管理功能; + +另一种使用代理模式优化的做法,称为**copy-on-write**。拷贝一个复杂的大对象开销很大,如果这一拷贝从未被修改,那就没有必要去真正地拷贝。通过使用代理模式延迟拷贝的过程,确保只有在被修改后才执行拷贝操作。 + +上述操作要求对象进行引用计数,拷贝时引用计数+1,修改时引用计数-1并进行拷贝和修改,引用计数减为0时删除对象。 + +## 实现 + +1. 使用C++实现时,重载成员访问操作符(->) + + 每当对象被接触引用,重载该操作符使你能执行额外的工作,通过重载操作符,代理就能像指针一样地使用。 \ No newline at end of file diff --git a/StructuralPatterns/Proxy/proxy.py b/StructuralPatterns/Proxy/proxy.py new file mode 100644 index 000000000..4c5b4cc04 --- /dev/null +++ b/StructuralPatterns/Proxy/proxy.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import time + + +class SalesManager: + + def work(self): + print("Sales Manager working...") + + def talk(self): + print("Sales Manager ready to talk") + + +class Proxy: + + def __init__(self): + self.busy = 'No' + self.sales = None + + def work(self): + print("Proxy checking for Sales Manager availability") + if self.busy == 'No': + self.sales = SalesManager() + time.sleep(2) + self.sales.talk() + else: + time.sleep(2) + print("Sales Manager is busy") + + +if __name__ == '__main__': + p = Proxy() + p.work() + p.busy = 'Yes' + p.work() + +### OUTPUT ### +# Proxy checking for Sales Manager availability +# Sales Manager ready to talk +# Proxy checking for Sales Manager availability +# Sales Manager is busy diff --git a/StructuralPatterns/Proxy/res/Proxy.png b/StructuralPatterns/Proxy/res/Proxy.png new file mode 100644 index 000000000..e39419d22 Binary files /dev/null and b/StructuralPatterns/Proxy/res/Proxy.png differ