Design Patterns Lecture 3
Creational Structural Behavioural Types of Design Patterns Creational Structural Behavioural Abstract Factory Builder Factory Prototype Singleton Adapter Bridge Composite Decorator Façade Flyweight Proxy Chain of Responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor 2
组合模式 意图 将对象组合成树形结构以表示“部分-整体”的层次结构。 C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。 适用性 你想表示对象的部分-整体层次结构。 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。 结构型模式设计如何组合类和对象已获得更大的结构。这里面主要包括:组合模式,装饰者模式,代理模式,享元模式,外观模式,桥接器模式,适配器模式等。 有些应用中,组件分为两种,一种是单个的原子组件,另一种是由原子组件复合而成的组件,后一种像是容器一样由前一种组成,但不管是哪一种对于用户而言都提供了类似的功能,这种功能由一个或者多个接口统一表达,这个时候就可以用组合模式。因此组合模式的关键是一个抽象类component,既可以代表原子组件,又可以代表复合组件。为客户提供一个统一的接口。比如说图元,是一个非常典型的例子。 3
Composite模式应用于XML XML数据同样也可看成是树型结构,因此 XML解释器将其由字符串转换成一种树型模型 DOM。然后就可以基于DOM对XML进行数据提 取、遍历及各种编辑操作。 DOM是W3C制订的标准,完全基于 Composite模式。其中所有节点都用Node接口 表示,然后由其派生出Element,Text和 Attribute等接口。
DOM应用图例 public interface Node { public String getNodeName(); public short getNodeType(); public String getPrefix(); ... public Node getParentNode(); public boolean hasChildNodes(); public NodeList getChildNodes(); public Node appendChild(Node newChild); public Node removeChild(Node oldChild); public Node insertBefore(Node newChild, Node refChild); }
动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子类更为灵活。 装饰模式 意图 动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子类更为灵活。 适用性 不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 处理那些可以撤消的职责。 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。 其核心在于不改变接口的前提下扩展功能。 6
Decorator模式用于Java的I/O Java的I/O系统是完全构建在Decorator模式 之上的。其使用方法和我们前面介绍的例子是非 常相似的。 实现方面则比示例要复杂些,接口和抽象类都 用上了。 借助Decorator模式,Java的I/O系统非常灵 活,且扩展性佳。
为其他对象提供一种代理以控制对这个对象的访问。 代理模式 意图 为其他对象提供一种代理以控制对这个对象的访问。 适用性 在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用P r o x y 模式。下面是一 些可以使用P r o x y 模式常见情况: 1) 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用N X P r o x y 类实现了这一目的。Coplien[Cop92] 称这种代理为“大使” (A m b a s s a d o r )。 2 )虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的I m a g e P r o x y 就是这样一种代理的例子。 3) 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。例如,在C h o i c e s 操作系统[ C I R M 9 3 ]中K e m e l P r o x i e s 为操作系统对象提供 了访问保护。 4 )智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括: 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为S m a r tP o i n t e r s[ E d e 9 2 ] )。 当第一次引用一个持久对象时,将它装入内存。 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
代理模式 EJB是典型的分布式组件,其客户端全都是通过Proxy对象与之交互的。这样容器可以从中透明地加入安全、事务、惰性载入和生存期管理等服务。
运用共享技术有效地支持大量细粒度的对象。 享元模式 意图 运用共享技术有效地支持大量细粒度的对象。 适用性 一个应用程序使用了大量的对象。 完全由于使用大量的对象,造成很大的存储开销。 对象的大多数状态都可变为外部状态。 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。 应用程序不依赖于对象标识。由于F l y w e i g h t 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
为子系统中的一组接口提供一个一致的界面,F a c a d e 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 外观模式 意图 为子系统中的一组接口提供一个一致的界面,F a c a d e 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 适用性 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。F a c a d e 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过f a c a d e 层。 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入f a c a d e 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。 当你需要构建一个层次结构的子系统时,使用f a c a d e 模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过f a c a d e 进行通讯,从而简化了它们之间的依赖关系
将抽象部分与它的实现部分分离,使它们都可以独立地变化。 桥梁模式 意图 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 适用性 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时B r i d g e 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。 (C + +)你想对客户完全隐藏抽象的实现部分。在C + +中,类的表示在类接口中是可见的。 有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。R u m b a u g h 称这种类层次结构为“嵌套的普化”(nested generalizations )。 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是C o p l i e n 的S t r i n g 类[ C o p 9 2 ],在这个类中多个对象可以共享同一个字符串表示(S t r i n g R e p )。 Abstraction 和 Implementor之间的链接称之为桥梁(bridge), Abstraction部分被称之为前端,Implementor部分被称之为后端,非常容易扩充,很好的体现了开闭原则
将一个类的接口转换成客户希望的另外一个接口。A d a p t e r 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器模式 意图 将一个类的接口转换成客户希望的另外一个接口。A d a p t e r 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适用性 你想使用一个已经存在的类,而它的接口不符合你的需求。 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。 (仅适用于对象A d a p t e r )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。 分为类适配器和对象适配器。对象适配器采用组合机制,类适配器采用多继承机制。
a class that has only one instance for each unique state Pattern: Flyweight a class that has only one instance for each unique state
Problem of redundant objects problem: redundant objects can bog down system many objects have same state intrinsic vs. extrinsic state example: File objects that represent the same file on disk new File("mobydick.txt") new File("mobydick.txt") ... new File("notes.txt")
Flyweight pattern flyweight: an assurance that no more than one instance of a class will have identical state achieved by caching identical instances of objects to reduce object construction similar to singleton, but has many instances, one for each unique-state object useful for cases when there are many instances of a type but many are the same can be used in conjunction with Factory pattern to create a very efficient object-builder examples in Java: String, Image / Toolkit, Formatter
Flyweight and Strings Flyweighted strings Java Strings are flyweighted by the compiler in many cases can be flyweighted at runtime with the intern method String fly = "fly", weight = "weight"; String fly2 = "fly", weight2 = "weight"; Which of the following expressions are true? fly == fly2 weight == weight2 "fly" + "weight" == "flyweight" fly + weight == "flyweight" String flyweight = new String("fly" + "weight"); flyweight == "flyweight" String interned = (fly + weight).intern(); interned == "flyweight"
Implementing a Flyweight flyweighting works best on immutable objects immutable: cannot be changed once constructed class pseudo-code sketch: public class Flyweighted { static collection of instances private constructor static method to get an instance: if (we have created this kind of instance before), get it from the collection and return it else, create a new instance, store it in the collection and return it }
Flyweight sequence diagram
Implementing a Flyweight public class Flyweighted { private static Map instances; private Flyweighted() {} public static synchronized Flyweighted getInstance(Object key) { if (!myInstances.contains(key)) { Flyweighted fw = new Flyweighted(key); instances.put(key, fw); return fw; } else { return instances.get(key); }
Class before flyweighting A class to be flyweighted public class Point { private int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public String toString() { return "(" + x + ", " + y + ")";
Class after flyweighting A class that has been flyweighted! public class Point { private static Map<String, Point> instances = new HashMap<String, Point>(); public static Point getInstance(int x, int y) { String key = x + ", " + y; if (instances.containsKey(key)) { // re-use existing pt return instances.get(key); } Point p = new Point(x, y); instances.put(key, p); return p; private final int x, y; // immutable private Point(int x, int y) { ...
Facade Pattern: Problem Client Classes Need to communicate with Subsystem classes
Facade Pattern: Solution Client Classes Facade Subsystem classes
Facade Pattern: Why and What? Subsystems often get complex as they evolve. Need to provide a simple interface to many, often small, classes. But not necessarily to ALL classes of the subsystem. Façade provides a simple default view good enough for most clients. Facade decouples a subsystem from its clients. A façade can be a single entry point to each subsystem level. This allows layering.
Facade Pattern: Participants and Communication Participants: Façade and subsystem classes Clients communicate with subsystem classes by sending requests to façade. Façade forwards requests to the appropriate subsystem classes. Clients do not have direct access to subsystem classes.
Facade Pattern: Benefits Shields clients from subsystem classes; reduces the number of objects that clients deal with. Promotes weak coupling between subsystem and its clients. Helps in layering the system. Helps eliminate circular dependencies.
Example: A compiler Compiler Compile() Invocations Stream BytecodeStream CodeGenerator Scanner Token Parser Symbol PnodeBuilder Pnode StatementNode ExpressionNode StackMachineCodegenerator RISCCodegenerator
Façade Pattern: Code [1] class Scanner { // Takes a stream of characters and produces a stream of tokens. public: Scanner (istream&); virtual Scanner(); virtual Token& Scan(); Private: istream& _inputStream; };
Façade Pattern: Code [2] class parser { // Builds a parse tree from tokens using the PNodeBuilder. public: Parser (); virtual ~Parser() virtual void Parse (Scanner&, PNodeBuilder&); };
Façade Pattern: Code [3] class Pnodebuilder { // Builds a parse tree incrementally. Parse tree // consists of Pnode objects. public: Pnodebuilder (); virtual Pnode* NewVariable ( ) const; Char* variableName // Node for a variable. virtual Pnode* NewAssignment ( ) const; Pnode* variable, Pnode* expression // Node for an assignment. // Similarly...more nodes. Private: Pnode* _node; };
Façade Pattern: Code [4] class Pnode { // An interface to manipulate the program node and its children. public: virtual void GetSourcePosition (int& line, int& index); // Manipulate program node. virtual void Add (Pnode*); // Manipulate child node. virtual void Remove (Pnode*); // …. virtual void traverse (Codegenerator&); // Traverse tree to generate code. PNode(); protected: };
Façade Pattern: Code [5] class CodeGenerator { // Generate bytecode. public: virtual void Visit (StatementNode*); // Manipulate program node. // …. virtual void Visit (ExpressionNode*); Protected: CodeGenerator (BytecodeStream&); }; BytecodeStream& _output;
Façade Pattern: Code [6] void ExpressionNode::Traverse (CodeGenerator& cg) { cg.Visit (this); ListIterator<Pnode*> i(_children); For (i.First(); !i.IsDone(); i.Next();{ i.CurrentItem()Traverse(cg); };
Façade Pattern: Code [7] class Compiler { // Façade. Offers a simple interface to compile and // Generate code. public: Could also take a CodeGenerator Parameter for increased generality. Compiler(); } virtual void Compile (istream&, BytecodeStream&); void Compiler:: Compile (istream& input, BytecodeStream& output) { Scanner scanner (input); PnodeBuilder builder; Parser parser; parser.Parse (scanner, builder); RISCCodeGenerator generator (output); Pnode* parseTree = builder.GetRootNode(); parseTreeTraverse (generator); }
Facade Pattern: Another Example from POS [1] Assume that rules are desired to invalidate an action: Suppose that when a new Sale is created, it will be paid by a gift certificate Only one item can be purchased using a gift certificate. Hence, subsequent enterItem operations must be invalidated in some cases. (Which ones?) How does a designer factor out the handling of such rules?
Facade Pattern: Another Example [2] Define a “rule engine” subsystem (e.g. POSRuleEngineFacade). It evaluates a set of rules against an operation and indicates if the rule has invalidated an operation. Calls to this façade are placed near the start of the methods that need to be validated. Example: Invoke the façade to check if a new salesLineItem created by makeLineItem is valid or not.
Composite Pattern 意图 将对象组合成树形结构以表示“部分-整体”的层次结构。 适用性 你想表示对象的部分-整体层次结构。 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。 结构型模式设计如何组合类和对象已获得更大的结构。这里面主要包括:组合模式,装饰者模式,代理模式,享元模式,外观模式,桥接器模式,适配器模式等。 有些应用中,组件分为两种,一种是单个的原子组件,另一种是由原子组件复合而成的组件,后一种像是容器一样由前一种组成,但不管是哪一种对于用户而言都提供了类似的功能,这种功能由一个或者多个接口统一表达,这个时候就可以用组合模式。因此组合模式的关键是一个抽象类component,既可以代表原子组件,又可以代表复合组件。为客户提供一个统一的接口。比如说图元,是一个非常典型的例子。
Composite模式应用于XML XML数据同样也可看成是树型结构,因此 XML解释器将其由字符串转换成一种树型模型 DOM。然后就可以基于DOM对XML进行数据提 取、遍历及各种编辑操作。 DOM是W3C制订的标准,完全基于 Composite模式。其中所有节点都用Node接口 表示,然后由其派生出Element,Text和 Attribute等接口。
DOM Example public interface Node { public String getNodeName(); public short getNodeType(); public String getPrefix(); ... public Node getParentNode(); public boolean hasChildNodes(); public NodeList getChildNodes(); public Node appendChild(Node newChild); public Node removeChild(Node oldChild); public Node insertBefore(Node newChild, Node refChild); }
动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子类更为灵活。 Decorator Pattern 意图 动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子类更为灵活。 适用性 不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 处理那些可以撤消的职责。 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。 其核心在于不改变接口的前提下扩展功能。
Decorator application to Java I/O Java I/O System is based on Decorator Pattern. With Decorator Pattern,Java’s I/O System is very flexible and powerful.
为其他对象提供一种代理以控制对这个对象的访问。 Proxy Pattern 意图 为其他对象提供一种代理以控制对这个对象的访问。 适用性 在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用P r o x y 模式。下面是一 些可以使用P r o x y 模式常见情况: 1) 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用N X P r o x y 类实现了这一目的。Coplien[Cop92] 称这种代理为“大使” (A m b a s s a d o r )。 2 )虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的I m a g e P r o x y 就是这样一种代理的例子。 3) 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。例如,在C h o i c e s 操作系统[ C I R M 9 3 ]中K e m e l P r o x i e s 为操作系统对象提供 了访问保护。 4 )智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括: 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为S m a r tP o i n t e r s[ E d e 9 2 ] )。 当第一次引用一个持久对象时,将它装入内存。 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
Proxy Pattern EJB是典型的分布式组件,其客户端全都是通过Proxy对象与之交互的。这样容器可以从中透明地加入安全、事务、惰性载入和生存期管理等服务。
将抽象部分与它的实现部分分离,使它们都可以独立地变化。 Bridge Pattern 意图 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 适用性 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时B r i d g e 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。 (C + +)你想对客户完全隐藏抽象的实现部分。在C + +中,类的表示在类接口中是可见的。 有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。R u m b a u g h 称这种类层次结构为“嵌套的普化”(nested generalizations )。 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是C o p l i e n 的S t r i n g 类[ C o p 9 2 ],在这个类中多个对象可以共享同一个字符串表示(S t r i n g R e p )。 Abstraction 和 Implementor之间的链接称之为桥梁(bridge), Abstraction部分被称之为前端,Implementor部分被称之为后端,非常容易扩充,很好的体现了开闭原则
将一个类的接口转换成客户希望的另外一个接口。A d a p t e r 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 Adaptor Pattern 意图 将一个类的接口转换成客户希望的另外一个接口。A d a p t e r 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适用性 你想使用一个已经存在的类,而它的接口不符合你的需求。 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。 (仅适用于对象A d a p t e r )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。 分为类适配器和对象适配器。对象适配器采用组合机制,类适配器采用多继承机制。
Template Pattern 意图 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Templ ate Method 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 适用性 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke 和Johnsn 所描述过的“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。 控制子类扩展。模板方法只在特定点调用“h o o k ”操作(参见效果一节),这样就只允许在这些点进行扩展。 下面关于行为模式,行为模式涉及到算法和对象间职责的分配。不仅描述对象或者类的模式,还描述了他们之间的通信模式。 模板模式,具体由子类实现的步骤叫做钩子操作,体现了父类对子类扩展的控制