23种设计模式分析(1):创建型模式

来源:互联网 发布:淘宝装修店铺视频教程 编辑:程序博客网 时间:2024/05/02 00:59

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

  毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
  GoF(“四人帮”,指Gamma, Helm, Johnson & Vlissides, Addison-Wesley四人)的《设计模式》(1995年出版)是第一次将设计模式提升到理论高度,并将之规范化,本书提出了23种基本设计模式,自此,在可复用面向对象软件的发展过程中,新的大量的设计模式不断出现。
  现在,可复用面向对象软件系统现在一般划分为三大类:应用程序、工具箱和框架(Framework)。我们平时开发的具体软件都是应用程序;Java的API属于工具箱;而框架是构成一类特定软件可复用设计的一组相互协作的类。EJB(Enterprise JavaBeans)是Java应用于企业计算的框架。
  框架通常定义了应用体系的整体结构类和对象的关系等等设计参数,以便于具体应用实现者能集中精力于应用本身的特定细节。框架主要记录软件应用中共同的设计决策,框架强调设计复用,因此框架设计中必然要使用设计模式。

  另外,设计模式有助于对框架结构的理解,成熟的框架通常使用了多种设计模式,如果你熟悉这些设计模式,毫无疑问,你将迅速掌握框架的结构,我们一般开发者如果突然接触EJB JavaEE等框架,会觉得特别难学,难掌握,那么转而先掌握设计模式,无疑是给了你剖析EJB或JavaEE系统的一把利器。


1.1.1 设计模式分类


  各种设计模式在其粒度和抽象级别上各不相同。因为有很多的设计模式,我们需要通过某种方式来组织它们。此部分对设计模式进行分类,以便于找出相关的设计模式,而且有利于发现新的设计模式。
  我们以两个标准来对设计模式进行分类。其中一个标准,称为目的(Purpose),反映了这个设计模式是干什么的。根据其目的(Purpose),模式可分为创建(Creational)、结构(Structural)、和行为(Behavioral)。“创建型模式”关心对象的创建过程;“结构型模式”涉及类或对象的组合;“行为型模式”刻画了类和对象交互及分配职责的方式。
  第二个标准,称为范围(Scope)。范围描述了模式主要是应用于对象,还是主要应用于类。“类模式”主要处理类与其子类的关系,这种关系是通过继承得来的,因此它们是静态-固定、由编译时决定的;“对象模式”处理对象关系,这种关系是运行时决定的,是动态关系。几乎所有的模式都在某种程度上使用了继承,因此只有那些标明为“类模式”的模式才重点关注类关系,大多数模式都在“对象模式”范畴。
  这里,我们根据两条准则对模式进行分类,如下图,其中Adapter模式可以作用在类上也可以作用在对象上。


图1-1 设计模式分类

  “创建型类模式”将部分的对象创建工作延迟到了子类,而“创建型对象模式”将其延迟到了另外的对象。“结构型类模式”应用继承机制来合成类,而“结构型对象模式”则规定了装配对象的方式。行为型类模式用继承机制来描述算法和控制流,而行为型对象模式则规定一组对象如何合作以完成某项单一对象不能达成的任务。
  GOF的《设计模式》中把23个设计模式分成三大类:Creational(创建型)5个;Structural(结构型)7个;Behavioral(行为型)11个。
  创建型:Factory Method, Abstract Factory, Builder, Prototype, Singleton共5个;
  结构型:Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy共7个;
  行为型:Observer, State, Strategy, Visitor, Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento共11个。
  组织模式有另外的方式。有些模式通常在一起使用,例如,复合模式通常与迭代器模式或访问者模式一同使用。有些设计模式是可替换的,如原型模式常常替换抽象工厂模式。有些设计模式的设计是相似的,虽然它们各有不同的意图,如复合模式与装饰模式的结构图非常相似。
  另一种方式是根据模式的“相关模式”部分所描述的它们怎样互相引用来组织设计模式。下图给出了模式关系的图形说明。

图1-2 按相关模式来组织设计模式

  显然,存在着许多组织设计模式的方法。从多角度去思考模式有助于对它们的功能、差异和应用场合的更深入理解。
  23个设计模式描述如下:
  1.Factory Method工厂方法模式:
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
  2.Abstract Factory抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  3.Builder建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  4.Prototype原型模式:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
  5.Singleton单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  6.Adapter适配器模式:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  7.Bridge桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  8.Composite复合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性。
  9.Decorator装饰模式:动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类方式更为灵活。
  10.Facade外观模式:为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  11.Flyweight享元模式:运用共享技术有效地支持大量细粒度的对象。
  12.Proxy代理模式:为其他对象提供一个代理以控制对这个对象的访问。
  13.Interpreter 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
  14.Template Method模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  15.Chain of Responsibility职责链模式:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
  16.Command命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
  17.Iterator迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。 
  18.Mediator中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  19.Memento 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
  20.Observer观察者模式:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
  21.State状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。 
  22.Strategy策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
  23.Visitor访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。


1.1.2 Simple Factory / Factory Method / Abstract Factory


  1. Simple Factory Pattern(简单工厂模式)

  专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。简单工厂(Simple Factory)模式又称为静态工厂方法(Static Factory Method)模式,属于类的创建型模式,通常它根据变量的不同返回不同的类的实例。UML类图如下:

图2-1 简单工厂模式

  简单工厂模式的实质是由一个工厂类根据传入的参量,动态决定应该创建出哪一个产品类的实例。简单工厂模式实际上不属于23个GoF模式,但它可以作为GoF的工厂方法模式(Factory Method)的一个引导。从上图可以看出,简单工厂模式涉及到工厂角色、抽象产品角色和具体产品角色三个参与者。
  (1) 工厂(Creator)角色:是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
  (2) 抽象产品(Product)角色:是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共的接口。
  (3) 具体产品(Concrete Product)角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

  实例: 电子付款系统
  在电子付款系统中,会存在很多种电子交易方式,包括虚拟支票、信用卡、有线传送等。


图2-2 电子付款方式

  实现如下:

[java] view plain copy
  1. //抽象电子付款类  
  2. abstract class EFT {  
  3.   
  4.     abstract void process();  
  5. }  
  6.   
  7. //具体子类,虚拟支票  
  8. class VirtualCheck extends EFT {  
  9.   
  10.     @Override  
  11.     public void process() {  
  12.         System.out.println("虚拟支票处理中");  
  13.     }  
  14. }  
  15.   
  16. //具体子类,万事达卡  
  17. class MasterCard extends EFT {  
  18.   
  19.     @Override  
  20.     public void process() {  
  21.         System.out.println("万事达卡处理中");  
  22.     }  
  23. }  
  24.   
  25. //简单工厂类  
  26. class EFTFactory {  
  27.   
  28.     public EFT createEFT(String type) {  
  29.         switch (type.toLowerCase()) {  
  30.             case "virtualcheck":  
  31.                 return new VirtualCheck();  
  32.             case "mastercard":  
  33.                 return new MasterCard();  
  34.             default:  
  35.                 return null;  
  36.         }  
  37.     }  
  38. }  
  39.   
  40. //客户应用测试  
  41. class Client {  
  42.   
  43.     public static void main(String[] args) {  
  44.         EFT eft;  
  45.         EFTFactory eftFactory = new EFTFactory();  
  46.         eft = eftFactory.createEFT("VirtualCheck");  
  47.         eft.process();  
  48.         eft = eftFactory.createEFT("MasterCard");  
  49.         eft.process();  
  50.     }  
  51. }  
  优势和缺陷:
  在简单工厂模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面中摆脱出来,仅仅需要负责“消费”对象就可以了,而不必管这些对象究竟是如何创建以及如何组织的。这样就明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
  不过,凡事有利就有弊,简单工厂模式的缺点也正体现在其工厂类上。由于工厂类集中了所有实例的创建逻辑,很容易违反GRASP的高内聚的责任分配原则。将全部创建逻辑都集中到一个工厂类还有另外一个缺点,那就是当系统中的具体产品类不断增多时,可能会出现要求工厂类根据不同条件创建不同实例的需求。这种对条件的判断和对具体产品类的判断交错在一起,很难避免模块功能的蔓延,对系统的扩展和维护也非常不利。另外,只用一个类根据if分支来决定创建哪一类型的对象,一旦需要扩展,就要修改类的代码增加if分支,破坏了开闭原则(Open Closed Principle, OCP,即一个软件实体应当对扩展开放,对修改关闭。换句话说,我们在设计一个模块的时候,应当使这个模块在不被修改的前提下可以被扩展)。
  应用情景:
  下列情况适于应用简单工厂模式:
  (1)  工厂类负责创建的对象比较少。
  (2)  客户只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心。
  由于简单工厂很容易违反GRASP的高内聚责任分配原则,因此一般只在很简单的情况下应用。

  2. Factory Method Pattern (工厂方法模式)

  定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。工厂方法模式又称工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,属于类的创建型模式。

  在简单工厂模式中,一个工厂类处于对产品类进行实例化的中心位置,它知道每一个产品类的细节,并决定何时哪一个产品类应当被实例化。简单工厂模式的优点是能够使客户端独立于产品的创建过程,并且在系统中引入新产品时无需对客户端进行修改,缺点是当有新产品要加入系统中时,必须修改工厂类,以加入必要的处理逻辑。简单工厂模式的致命弱点就是处于核心地址的工厂类,因为一旦它无法确定要对哪个类进行实例化,就无法使用该模式,而工厂方法模式则可以很好地解决这一问题。
  工厂方法模式的UML图如下:

图2-3 工厂方法模式

  其中的类或对象之间的关系为:
  (1) Product(产品角色):定义产品的接口。
  (2) ConcreteProduct(真实的产品):实现接口的Product的类。
  (3) Creator(工厂角色):声明工厂方法(FactoryMethod),返回一个产品。
  (4)  ConcreteCreator(真实的工厂):实现FactoryMethod工厂方法,由客户调用,返回一个产品的实例。

  实例1:Windows的COM组件与MFC

  应用Factory Method的最典型的例子当数COM的类厂,如果没有类厂的帮助,而是将组件类的创建工作交由我们自己完成,则我们必须知道组件类的类名,而如果这样,则COM的封装性和可重用性都将大大受损。正是由于运用了Factory Method,COM组件只负责对外提供接口,封装内部实现,包括创建过程的特性才得以完整体现。在MFC中,CDocTemplate是另一个典型的应用Factory Method的例子,MFC实现中通过派生不同的子类CSingleDocTemplate/CMultiDocTemplate来支持不同的文档类型,感兴趣的朋友可以自己研究CDocTemplate及其子类的相关代码,相关代码在afxwin.h中,与上面讨论的Factory Method不同的是,这里的文档类型(单文档/多文档)并没有严格一致的类与之对应。

  实例2: 多文档系统
  应用工厂方法模式提供的最大灵活性生成不同的对象。抽象类可以返回一个默认的对象,而每一个派生的子类都可以返回扩展了的其他对象。

图2-4 多文档系统

  实现如下:

[java] view plain copy
  1. import java.util.ArrayList;  
  2.   
  3. //产品基类  
  4. abstract class Page {  
  5. }  
  6.   
  7. //工厂抽象类  
  8. abstract class Document {  
  9.   
  10.     protected ArrayList<Page> pages = new ArrayList<>();  
  11.   
  12.     public Document() {  
  13.         this.createPages();  
  14.     }  
  15.   
  16.     public ArrayList<Page> getPages() {  
  17.         return pages;  
  18.     }  
  19.   
  20.     //工厂方法  
  21.     abstract public void createPages();  
  22. }  
  23.   
  24. //具体产品类——技能页  
  25. class SkillsPage extends Page {  
  26.   
  27. }  
  28.   
  29. //具体产品类——教育页  
  30. class EducationPage extends Page {  
  31.   
  32. }  
  33.   
  34. //具体产品类——经验页  
  35. class ExperiencePage extends Page {  
  36. }  
  37.   
  38. //具体产品类——介绍页  
  39. class IntroductionPage extends Page {  
  40. }  
  41.   
  42. //具体产品类——结果页  
  43. class ResultPage extends Page {  
  44. }  
  45.   
  46. //具体产品类——结论页  
  47. class ConclusionPage extends Page {  
  48. }  
  49.   
  50. //具体产品类——总结页  
  51. class SummaryPage extends Page {  
  52. }  
  53.   
  54. //具体产品类——文献页  
  55. class BibliographyPage extends Page {  
  56. }  
  57.   
  58. //具体工厂类——个人简历,包括技能、教育、经验  
  59. class Resume extends Document {  
  60.   
  61.     public void createPages() {  
  62.         pages.add(new SkillsPage());  
  63.         pages.add(new EducationPage());  
  64.         pages.add(new ExperiencePage());  
  65.     }  
  66. }  
  67.   
  68. //具体工厂类——报告,包括介绍、结果、结论、总结、文献  
  69. class Report extends Document {  
  70.   
  71.     @Override  
  72.     public void createPages() {  
  73.         pages.add(new IntroductionPage());  
  74.         pages.add(new ResultPage());  
  75.         pages.add(new ConclusionPage());  
  76.         pages.add(new SummaryPage());  
  77.         pages.add(new BibliographyPage());  
  78.     }  
  79. }  
  80.   
  81. //客户应用测试  
  82. class MyClient {  
  83.   
  84.     public static void main(String[] args) {  
  85.         Document[] docs = new Document[2];  
  86.         docs[0] = new Resume();  
  87.         docs[1] = new Report();  
  88.         for (Document document : docs) {  
  89.             System.out.println("\n" + document + "--------------");  
  90.             for (Page page : document.getPages()) {  
  91.                 System.out.println("\t" + page);  
  92.             }  
  93.         }  
  94.     }  
  95. }  
  实例3:手机工厂
  现实中不同品牌的手机应由不同的工厂制造,下面手机工厂的示例,所应用的模式就是工厂方法(FactoryMethod)模式。


图2-5 手机工厂

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //手机接口  
  2. interface Mobile {  
  3.   
  4.     public void Call();  
  5. }  
  6.   
  7. //手机工厂接口  
  8. interface MobileFactory {  
  9.   
  10.     //工厂方法  
  11.     Mobile produceMobile();  
  12. }  
  13.   
  14. //摩托罗拉手机:实现手机接口  
  15. class Motorola implements Mobile {  
  16.   
  17.     @Override  
  18.     public void Call() {  
  19.         System.out.println("摩托罗拉手机");  
  20.     }  
  21. }  
  22.   
  23. //诺基亚手机:实现手机接口  
  24. class Nokia implements Mobile {  
  25.   
  26.     @Override  
  27.     public void Call() {  
  28.         System.out.println("诺基亚手机");  
  29.     }  
  30. }  
  31.   
  32. //摩托罗拉工厂:实现生产手机的方法,返回摩托罗拉手机  
  33. class MotorolaFactory implements MobileFactory {  
  34.   
  35.     @Override  
  36.     public Mobile produceMobile() {  
  37.         System.out.println("摩托罗拉工厂制造了");  
  38.         return new Motorola();  
  39.     }  
  40. }  
  41.   
  42. //诺基亚工厂:实现生产手机的方法,返回诺基亚手机  
  43. class NokiaFactory implements MobileFactory {  
  44.   
  45.     @Override  
  46.     public Mobile produceMobile() {  
  47.         System.out.println("诺基亚工厂制造了");  
  48.         return new Nokia();  
  49.     }  
  50. }  
  51.   
  52. //客户应用测试  
  53. class ClientForTest {  
  54.   
  55.     public static void main(String[] args) {  
  56.         MobileFactory mf;  
  57.         Mobile m;  
  58.         mf = new MotorolaFactory();  
  59.         m = mf.produceMobile();  
  60.         m.Call();  
  61.         mf = new NokiaFactory();  
  62.         m = mf.produceMobile();  
  63.         m.Call();  
  64.     }  
  65. }  
  优势和缺陷:

  在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节。工厂方法模式的核心是一个抽象工厂类,各种具体工厂类通过抽象工厂类将工厂方法继承下来。如此使得客户可以只关心抽象产品和抽象工厂,完全不用理会返回的是哪一种具体产品,也不用关系它是如何被具体工厂创建的。
  (1)  基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
  (2) 使用工厂方法模式的另一个优点是在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这符合开闭原则,从而使得系统的可扩展性非常好。优秀的面向对象设计鼓励使用封装(Encapsulation)和委托(Delegation)来构造软件系统,工厂方法模式正是使用了封装和委托的典型例子,其中封装是通过抽象工厂来体现的,而委托则是通过抽象工厂将创建对象的责任完成交给具体工厂来体现。
  (3) 使用工厂方法模式的缺点是在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,当两者都比较简单时,系统会有相对额外的开销。

  3. Abstract Factory Pattern(抽象工厂模式)

  提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂(Abstract Factory)模式又称为Kit模式,属于对象创建型模式。
  抽象工厂模式与工厂方法模式最大的区别在于:工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则针对的是多个产品等级结构。正因如此,在抽象工厂模式中经常会用到产品族(Product Family)这一概念,它指的是位于不同的产品登记结构中,并且功能互相关联的产品系列,如下图:

图2-6 产品族

  图上三个箭头所指就是三个功能相互关联的产品,它们位于三个不同的产品登记结构中的相同位置上,共同组成了一个产品族。抽象工厂就是要生成这样的产品族。而在这种产品族中,各产品之间有关联耦合,抽象工厂会将这种关联耦合设计成一个抽象类。抽象工厂模式符合GRASP的纯虚构模式,同时取得高内聚低耦合的效果。
  其UML类图如下:

图2-7 抽象工厂模式

  其中的类或对象之间的关系为:
  (1) AbstractFactory(抽象工厂):声明生成抽象产品的方法。
  (2) ConcreteFactory(具体工厂):执行生成抽象产品的方法,生成一个具体的产品。
  (3) AbstractProduct(抽象产品):为一种产品声明接口。
  (4) Product(具体产品):定义具体工厂生成的具体产品的对象,实现产品接口。
  (5) Client(客户):我们的应用程序,使用抽象产品和抽象工厂生成对象。

  抽象工厂负责创建不同的有联系的多个产品,不同的抽象工厂创建的产品不同,但产品之间的关系相同,抽象工厂是GRASP模式的纯虚构的表现。

  实例1: 大陆生态系统
  大家都知道,动物世界中各大陆的动物是不一样的,各种动物可以分成两样,一种食草,一种食肉。食肉的动物吃食草动物。美洲狼属于食肉动物,野牛属于食草动物,美洲狼猎吃野牛;非洲的狮子属于食肉动物,角马属于食草动物,狮子猎吃角马。类的关系图如下:

图2-8 大陆生态系统

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //抽象大陆工厂  
  2. abstract class ContinentFacctory {  
  3.   
  4.     abstract public Herbivore createHerbivore();  
  5.   
  6.     abstract public Carnivore createCarnivore();  
  7. }  
  8.   
  9. //非洲大陆,有角马、狮子  
  10. class AfricaFactory extends ContinentFacctory {  
  11.   
  12.     @Override  
  13.     public Herbivore createHerbivore() {  
  14.         return new Wildebeest();  
  15.     }  
  16.   
  17.     @Override  
  18.     public Carnivore createCarnivore() {  
  19.         return new Lion();  
  20.     }  
  21. }  
  22.   
  23. //美洲大陆,有野牛、狼  
  24. class AmericaFactory extends ContinentFacctory {  
  25.   
  26.     @Override  
  27.     public Herbivore createHerbivore() {  
  28.         return new Bison();  
  29.     }  
  30.   
  31.     @Override  
  32.     public Carnivore createCarnivore() {  
  33.         return new Wolf();  
  34.     }  
  35. }  
  36.   
  37. //食草动物  
  38. abstract class Herbivore {  
  39. }  
  40.   
  41. //食肉动物:吃食草动物  
  42. abstract class Carnivore {  
  43.   
  44.     abstract public void eat(Herbivore h);  
  45. }  
  46.   
  47. //角马  
  48. class Wildebeest extends Herbivore {  
  49. }  
  50.   
  51. //狮子  
  52. class Lion extends Carnivore {  
  53.   
  54.     @Override  
  55.     public void eat(Herbivore h) {  
  56.         System.out.println(this + " eats " + h);  
  57.     }  
  58. }  
  59.   
  60. //野牛  
  61. class Bison extends Herbivore {  
  62. }  
  63.   
  64. //狼  
  65. class Wolf extends Carnivore {  
  66.   
  67.     @Override  
  68.     public void eat(Herbivore h) {  
  69.         System.out.println(this + " eats " + h);  
  70.     }  
  71. }  
  72.   
  73. //动物世界  
  74. class AnimalWorld {  
  75.   
  76.     private Herbivore herbivore;  
  77.     private Carnivore carnivore;  
  78.   
  79.     //创建两种动物分类  
  80.     public AnimalWorld(ContinentFacctory factory) {  
  81.         carnivore = factory.createCarnivore();  
  82.         herbivore = factory.createHerbivore();  
  83.     }  
  84.   
  85.     //运行食物链  
  86.     public void RunFoodChain() {  
  87.         carnivore.eat(herbivore);  
  88.     }  
  89. }  
  90.   
  91. //客户应用测试  
  92. class ClientAnimal {  
  93.   
  94.     public static void main(String[] args) {  
  95.         //创造并运行非洲动物世界  
  96.         ContinentFacctory africa = new AfricaFactory();  
  97.         AnimalWorld world = new AnimalWorld(africa);  
  98.         world.RunFoodChain();  
  99.   
  100.         //创造并运行美洲动物世界  
  101.         ContinentFacctory america = new AmericaFactory();  
  102.         world = new AnimalWorld(america);  
  103.         world.RunFoodChain();  
  104.     }  
  105. }  
  实例2: 电脑产品
  IBM,Dell都是著名的计算机生产厂家,他们采用的主办、硬盘及CPU。但配件间、主板与CPU一定要互相兼容。例如下面例子中的微星MSIK7N2G配AMD的CPU;微星MSI865PE配Intel的CPU。类图如下:

图2-9 电脑产品

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.demo.designpattern;  
  2.   
  3. //定义CPU接口  
  4. interface CPU {  
  5.   
  6.     String designCPU();  
  7. }  
  8.   
  9. //定义AMD类,实现CPU接口  
  10. class AMD implements CPU {  
  11.   
  12.     @Override  
  13.     public String designCPU() {  
  14.         return "Athlon XP 2800+";  
  15.     }  
  16. }  
  17.   
  18. //定义Intel类,实现CPU接口  
  19. class Intel implements CPU {  
  20.   
  21.     @Override  
  22.     public String designCPU() {  
  23.         return "奔腾4 3.2C";  
  24.     }  
  25. }  
  26.   
  27. //定义硬盘接口  
  28. interface HardDisc {  
  29.   
  30.     String designHardDisc();  
  31. }  
  32.   
  33. //定义Maxtor类,实现硬盘接口  
  34. class Maxtor implements HardDisc {  
  35.   
  36.     @Override  
  37.     public String designHardDisc() {  
  38.         return "MaXLine Plus II 200G";  
  39.     }  
  40. }  
  41.   
  42. //定义WestDigit类,实现硬盘接口  
  43. class WestDigit implements HardDisc {  
  44.   
  45.     @Override  
  46.     public String designHardDisc() {  
  47.         return "WD2500JD 250G";  
  48.     }  
  49. }  
  50.   
  51. //定义主板接口,包含参数为CPU的公共方法Attach()  
  52. interface MainBoard {  
  53.   
  54.     void Attach(CPU cpu) throws Exception;  
  55. }  
  56.   
  57. //主板微星MSI865PE,支持Intel的CPU  
  58. class MSI865PE implements MainBoard {  
  59.   
  60.     @Override  
  61.     public void Attach(CPU icpu) throws Exception {  
  62.         if ("com.demo.designpattern.intel".equals(icpu.getClass().getName().toLowerCase())) {  
  63.             System.out.println("MSI865PE");  
  64.         } else {  
  65.             throw new Exception("主板MSI865PE只能配Intel的CPU");  
  66.         }  
  67.     }  
  68. }  
  69.   
  70. //主板微星MSIK7N2G,支持AMD的CPU  
  71. class MSIK7N2G implements MainBoard {  
  72.   
  73.     @Override  
  74.     public void Attach(CPU icpu) throws Exception {  
  75.         if ("com.demo.designpattern.amd".equals(icpu.getClass().getName().toLowerCase())) {  
  76.             System.out.println("MSIK7N2G");  
  77.         } else {  
  78.             throw new Exception("主板MSIK7N2G只能配AMD的CPU");  
  79.         }  
  80.     }  
  81. }  
  82.   
  83. //定义抽象电脑工厂类  
  84. abstract class ComputerFactory {  
  85.   
  86.     protected CPU icpu;  
  87.     protected HardDisc iHD;  
  88.     protected MainBoard iMB;  
  89.   
  90.     public void Show() {  
  91.         try {  
  92.             System.out.println(this.getClass().getName() + "生产的电脑配置");  
  93.             System.out.println("CPU: " + icpu.designCPU());  
  94.             System.out.println("HardDisk: " + iHD.designHardDisc());  
  95.             System.out.print("MainBoard: ");  
  96.             iMB.Attach(icpu);  
  97.         } catch (Exception e) {  
  98.             System.err.println(e.getMessage());  
  99.         }  
  100.     }  
  101. }  
  102.   
  103. //抽象电脑工厂类派生类IBM,定义其返回的系列配件产品  
  104. class IBM extends ComputerFactory {  
  105.   
  106.     public IBM() {  
  107.         icpu = new Intel();  
  108.         iHD = new WestDigit();  
  109.         iMB = new MSI865PE();  
  110.     }  
  111. }  
  112.   
  113. //抽象电脑工厂类派生类DELL,定义其返回的系列配件产品  
  114. class Dell extends ComputerFactory {  
  115.   
  116.     public Dell() {  
  117.         icpu = new AMD();  
  118.         iHD = new Maxtor();  
  119.         iMB = new MSIK7N2G();  
  120.     }  
  121. }  
  122.   
  123. //客户应用测试  
  124. class ClientComputer {  
  125.   
  126.     public static void main(String[] args) {  
  127.         IBM ibm = new IBM();  
  128.         ibm.Show();  
  129.         Dell dell = new Dell();  
  130.         dell.Show();  
  131.     }  
  132. }  
  优势和缺陷:
  抽象工厂模式的主要优点是隔离了具体类的生成,使得客户不需要知道什么被创建了。这种隔离使得更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变这个软件系统的行为。另外,应用抽象工厂模式符合GRASP纯虚构的模式,可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛应用。

  我们在创建这些对象的时候,并不需要指定它们的具体类,这些具体类的对象是由工厂对象负责实例化的。主要的好处是,客户代码不仅不知道工厂类的具体类型,而且也不知道具体的产品类型,具体的产品类型信息被封装到具体的工厂中了。所以,客户类只操纵工厂接口和产品接口,不知道具体的工厂产生产品的实现细节,创建的复杂性被封装了。
  使用抽象工厂模式的最大好处是,当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是非常使用的一种设计模式。
  抽象工厂模式的缺点是在添加新的产品对象时,难易扩展抽象工厂以便生产新种类的产品。这是因为AbstractFatory接口规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对AbstractFatory及其所有子类的修改。
  设计模式提倡的是:“优先使用组合,而不是继承”。但abstract factory模式中,并未使用组合,而只是依赖(实例化),也就是说abstract factory模式的内部,仍然有不小的耦合。设计模式的主要目标是“把变化的和不变的分离,提高弹性”。但在abstract factory中,弹性是有限的。即你可以从抽象基类派生出不同的具体工厂实现生产不同的具体产品,但所有这些都受限于你的接口,如果产品的改变非常之大,以至于你的接口变化了,那么客户类的代码也得随之改变。也就是说,减小耦合失败了。总之,当接口发生变化时候,所有实现类都需要做改动。还可能导致相关的客户端都需要重新改动,如果接口调用顺序又相互关联的话,那问题就更多了。

  基本上来说,Abstract Factory模式和Factory Method模式所作的事情是一样的,都是用来创建与具体程序代码无关的对象,只是面对的对象层次不一样,Abstract Factory创建一系列的对象组,这些对象彼此相关。而Factory Method往往只是创建单个的对象。
  这里有必要先陈叙一个在设计模式,或者说在整个面向对象设计领域所遵循的一个设计原则:针对接口编程,而不是针对具体的实现。这个思想可以说是设计模式的基石之一。现在的很多对象模型,比如EJB,COM+等等,无不是遵照这个基本原则来设计的。针对接口编程的好处有很多,通过接口来定义对象的抽象功能,方便实现多态和继承;通过接口来指定对象调用之间的契约,有助于协调对象之间的关系;通过接口来划分对象的职责,有助于寻找对象,等等。
  Abstract Factory和Factory Method,还有其他的一些创建型的设计模式,都是为了实现这个目的而设计出来的。它们创建一个个符合接口规范的对象/对象组,使得用同一个Factory创建出来的对象/对象组可以相互替换。这种可替换性就称为多态,是面向对象的核心思想之一。而多态,是通过动态绑定来实现的。

  应用情景:

  在必须协调一组对象的创建时,可以应用Abstract Factory模式。它提供了一种方式,将如何执行对象实例化的规则从使用这些对象的客户对象中提取出来。首先,找出实例化的规则,定义了一个带接口的抽象类,其中的接口为每种需要实例化的对象提供一个方法。然后,从这个类为每个组实现具体类。最后,由客户对象决定使用具体工厂来创建所需的对象。它主要适用于以下几种情况:

  (1) 系统需要屏蔽有关对象如何创建、如何组织和如何表示。
  (2) 系统需要由关联的多个对象来构成。
  (3) 有关联的多个对象需要一起应用并且它们的约束是强迫的(不可分离)。
  (4) 你想提供一组对象而不显示他们的实现过程,只显示它们的接口。

  三种Factory模式的比较:
  Simple Factory在于对产品创建过程的简单封装,它简单地根据输入来决定创建何种产品(这些产品不一定属于同一产品族),因此,任何产品种类的更新都将对Simple Factory的代码造成影响;Factory Method面对的是一个产品族,它引入了ConcreteFactory来决定创建产品族中的何种产品,当产品种类增加时,只需创建新的ConcreteFactory来创建新的产品;而Abstract Factory面对的则是多个产品系列,它是Factory Method的延伸,Abstract Factory在一个ConcreteFactory中包含了多个Factory Method,以用于创建多个不同产品族中的多个产品。
  需要注意的是,以上所说的产品并非仅限于单个的产品,可以包括一次创建出来的一组相同或者相关产品,从这个意义上讲,三种Factory特别是Factory Method与Abstract Factory之间的界限并非十分明显。
  Abstract Factory着重于创建一系列相关的对象,而这些对象与具体的Abstract Factory相关。而Factory Method则着重于创建单个的对象,这个对象决定于一个参数或者一个外部的环境变量的值;或者,在一个抽象类中定义一个抽象的工厂方法(也成为虚拟构造器),然后在实现的子类中返回具体的产品对象。
  Factory Method可以借助一个参数或者一个外部的标志来判断该具体生成哪一个子类的实例。比如对于不同的具体情况,需要有不同的Abstract Factory来生成相应的对象组。这时候,Factory Method有时也可作为一个Abstract Factory对象的静态方法出现(称为简单工厂方法),使得其能够在具体的对象被创建之前就能够被调用。
  在Java中,应用工厂模式的地方实在太多,下面我们来看一个在JAXP中这两个模式的应用。JAXP是用来处理XML文档的一个API。我们都知道XML文件的一个特点就是其平台无关,流通性能好。因而往往也需要处理他们的程序具有更好的平台无关性。Java语言是一个比较好的平台无关语言,可以作为一个选择,但是对XML进行解析的解析器确有很多。有时候需要在不同的解析器之间进行切换,这时候,JAXP的良好设计就能够体现出来了。它能够允许在不同解析器之间竟进行切换的时候,不用更改程序的代码。
  我们再拿JAXP中的DOM解析器来作为例子,来例示Abstract Factory和Factory Method的用法。

图2-10 DOM中工厂模式的应用

  上图中为了方便起见,只画出了抽象类和接口,DocumentBuilderFactory和DocumentBuilder都是抽象类。
  DocumentBuilderFactory的静态方法newInstance()根据一个外部的环境变量javax.xml.parsers.DocumentBuilderFactory的值来确定具体生成DocumentBuilderFactory的哪一个子类。这儿的newInstance()是一个工厂方法。当DocumentBuilderFactory被创建后,可以调用其newDocumentBuilder()来创建具体一个DocumentBuilder的子类。然后再由DocumentBuilder来生成Document等DOM对象。
  下面是创建一个DOM对象的代码片段:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //第一步:创建一个DocumentBuilderFactory。  
  2. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();  
  3. //第二步:创建一个DocumentBuilder  
  4. DocumentBuilder db = dbf.newDocumentBuilder();  
  5. //第三步:解析XML文件得到一个Document对象  
  6. Document doc = db.parse(new File(filename));   
  在这儿,DocumentBuilderFactory是一个抽象工厂,其newInstance()方法是一个静态的工厂方法,DocumentBuilder,Document,Node等等对象所组成的一个产品组,是和DocumentBuilderFactory相关的。这也就是Factory Method模式的含义所在。
  当然,Factory Method模式应用的很广。这是一个具体的例子,但他不应该限制我们的思路,Factory Method和Abstract Factory是解决面向对象设计中一个基本原则“面向接口编程”的主要方法。
  使用注意事项:

  (1) Factory Method模式的两种情况:一是工厂方法在抽象类中,它不提供它所声明的工厂方法的实现;二是工厂方法在具体的或抽象类中均可,且它提供一个工厂方法的缺省实现,这时候的工厂方法通常是静态的。
  (2) 工厂方法是可以带参数的。
  (3) 工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。


  参考文献:http://www.cnblogs.com/vrliym/archive/2010/10/20/1857105.html

0 0
原创粉丝点击