序:上一篇博文关于模式中粗略的谈了下何谓模式、模式的要素、理解模式的核心关注点以及在java中使用模式常涉及到的抽象类与接口异同问题,在接下来的篇章里将陆续介绍GOF 23的模式。由于博文仅倾向于模式的理解与相似模式间关系,所以,博文可能会以某类模式一起论述的形式出现。这些主要是个人回顾性的总结,具有较强的随意性,必定存在论述上的不周或过于累赘,还望朋友们海涵指正。
我们知道设计模式的最基本的原则是状态变化部分和不变部分尽可能地分离,比如桥接(Bridge)模式采用的是抽象和抽象的实现分离,这种分离想达到的效果就是较好的复用,就是对开-闭原则的最大限度支持。我们学习模式,为的就是理解模式,充分重用前辈经验,站在巨人的肩膀上,为设计出好的设计找寻方法。
由GOF整理的23种设计模式共分三大类:创建模式、结构模式和行为模式。
人们往往认为最为简单的就是创建模式,也许是因为日常工作中涉及到的机会更多些,尤其是工厂类模式,若有面试时一般情况下面试官会说除工厂类模式之外请你介绍下你较为熟悉的模式。不过,在创建模式中,单例(Singleton)模式却是常被拿来作为笔试考察的,侧重于单例模式的实现(所谓的饿汉、懒汉、注册、双重检查等实现方式优劣以及多类加载器、多JVM情况下Singleton的表现)及其使用场景。
尽管创建模式被认为相对简单,但加深对它们的认识还是必要的。创建模式,它抽象了类的实例化过程,针对类,使用继承改变了真正被实例化的类,针对对象,则会将对象实例化委托给另一个对象。从该角度看,创建模式利用继承和引用的方式来完成需要的各种对象的创建工作,把和对象的创建相关的事务统一进行处理,并通过创建不同类别的对象来应对信息系统外部的变化,为系统提供灵活性。当系统的灵活性不是依赖于多态继承机制,并随着系统演化得越来越依赖于对象的复合,创建模式变得更为重要,甚至成为系统灵活机制的核心,原因就在于创建模式可以通过委托方式来满足这一需求。
创建模式包含有简单工厂(SimpleFactory)模式、抽象工厂(AbstractFactory)模式、建造者(Builder)模式、单例(Singleton)模式和原型(Prototype)模式,以下篇幅的介绍对象是简单工厂模式、抽象工厂模式、建造者模式以及由工厂和抽象工厂引出的工厂方法模式,单例和原型将在以后篇幅给予阐述。
#简单工厂(SimpleFactory)模式:又称静态工厂方法模式,是由一个工厂对象决定创建出哪一种产品的实例。下图是工厂模式的一般性示意图:
从示意图中我们可以看出该模式的优点是客户端Client无需关心产品的创建,创建责任有核心的工厂类Factory负责;而缺点是核心的工厂类Factory必须知道要创建哪一类产品及如何被创建,尤其是当产品类有不同的接口种类时,核心的工厂类Factory需要判断何时生产何种商品,使得系统将来扩展比较困难。因此,该模式此时对“开-闭”原则的支持度为有限支持,即不影响客户端,但需要维护工厂自身逻辑。
关于实现,我们首先看Product,它应该是抽象类呢还是接口呢?
这个问题其实在关于模式中曾提到过的,并给出了个人认为较为合适的答案。答案的核心在于是否需要多继承及是否彼此间有相同的商业逻辑需要共享进行代码上移。一般情况下推荐使用接口(现代开发最佳实践面向接口编程),这里给出几种具体场景的个人认为该使用何种方式的答案。其一是可能需要多继承,此时,Product此时均应为接口,若有此种情况同时兼有商业逻辑需要共享,则遵循代码上移的原则,可再抽象出一层AbstractProduct实现Product接口完成此使命,若在关于模式中举的水果和蔬菜的例子,它们各自有共享商业逻辑代码的需要,同时又存在多继承的可能性(蔬菜的西红柿变身水果);其二是不存杂多继承,但需要共享商业逻辑代码,Product应为抽象类。
其次看核心的工厂类的工厂方法,若该工厂只负责生产一种产品类型,借助java反射是完全可以做到完全支持“开-闭”原则的,即增加新产品不会影响原有系统功能,如下实现说明:
//不完全支持开-闭原则的实现,因为if...else模式使得新加入产品时需要维护Factory public class Factory { public static Product factory(String which) { if(which.equalsIgnoreCase("product1") ){ return new ConcreteProduct1(); } else if(which.equalsIgnoreCase("product1")) { return new ConcreteProduct2(); } else { throw new RuntimeException("the product type doesn't exist"); } } } //完全支持开-闭原则,因为java反射消除了新增产品对Factory的维护 public class Factory { public static Product factory(String productpath) { try{ return (Product) Class.forName(productpath).newInstance(); }catch(Exception e){ throw new RuntimeException("the product type doesn't exist"); } } }
若该工厂负责生产至少两种产品类型,则该简单工厂模式只能保持它对开-闭原则的不完全支持,因为,核心的工厂类Factory需要判断何时生产何种商品,不免会因产品的增加而需要维护工厂自身逻辑。对于这种产品族工厂生产需求,为了满足对开-闭原则的支持需要使用后面将要介绍的抽象工厂模式。
这里稍微提一下针对抽象(接口)编程,针对抽象编程--利用具体产品类的超类类型,将它的真实类隐藏起来。其优点是提供了系统的可扩展性。(若将来有新的具体子类被加入到系统中来,那么工厂类可以将交给客户端的对象换成新的子类实例,而对客户端无任何影响)这种将工厂方法的返回类型设置成抽象产品类型的做法,叫做针对抽象编程,这是依赖倒转原则(DIP)的应用。
最后,我们来看一下针对简单工厂方法在单产品和产品族下存在的更好的模式,即工厂方法和抽象工厂。
#工厂方法(FactoryMethod):把对象的创建延迟到子类中进行,其核心是在父类和子类之间分配创建的责任,即把原先应该有父类直接完成的事情交给子类去做。
工厂方法与产品存在层对应关系,一般的工厂方法模式示意图如下:
由上面工厂方法的示意图我们可以看出工厂方法与简单工厂有如下几点关系:1、结构上明显不同。工厂方法的核心是一个抽象工厂类(XCreator),简单工厂模式把核心放在一个具体类上。2、工厂方法模式保持了简单工厂模式的优点,且客服了它的缺点。简单工厂模式中,一个工厂类处于产品类实例化的中心位置上,它知道每一个产品,决定哪一个产品应当被实例化,存在一定的优缺点;工厂方法模式是简单工厂模式的进一步抽象和推广。因使用了多态性,工厂方法模式保持了简单工厂模式的优点,且客服了它的缺点。工厂方法模式中核心的工厂类不再负责所有产品的创建,而是将具体的创建工作交给了子类去做,这个核心类则摇身变为了一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。这种进一步抽象的结果,使得工厂方法模式允许在不修改具体工厂角色的情况下引入新的产品,遵循“开-闭”(实指单产品下相比SimpleFactory的if...else实现模式)。
为了更加直观的对该模式产生认识,给出以下示例性代码:
//client invoke sample public class Client { private XCreator xConcreteCreator1,xConcreteCreator2; private XProudct xConcreteProduct1,xConcreteProduct2; public static void main(String[] args) { xConcreteCreator1 = new XConcreteCreator1(); xConcreteProduct1 = xConcreteCreator1 .factory(); xConcreteCreator2 = new ConcreteCreator2(); xConcreteProduct2 = xConcreteCreator2 .factory(); } } //creator sample public interface XCreator{ public XProduct factory(); } public class XConcreteCreator1 implements XCreator { public XProduct factory() { retrun new XConcreteProduct1();//maybe more complisity creating the product } public class XConcreteCreator2 implements XCreator { public XProduct factory() { retrun new XConcreteProduct2();//maybe more complisity creating the product } //product sample public interface XProduct{} public class XConcreteProduct1 implements XProduct {} public class XConcreteProduct2 implements XProduct {}
关于工厂方法的更好学习实例推荐参考java源码中迭代子实现,其结构示意图如下图左,工厂方法的一般性调度流程示意图如下图右:
实际上,使用工厂方法FaintMethod的核心根源并不在于仅仅把对象的创建延迟到子类中,那只是处理方法。使用的原则是客户对象需要处理很多的某种子类对象,客户对象面临两个问题,一个是选择创建哪一个子类的对象,一个是被选定的子类对象如何人被建立。但是这两个逻辑,工厂方法模式把他们分开了。在模式之外选择建立哪一个子类对象,而在具体建立这个对象的时候,又使用工厂类的子类来屏蔽建立的这个子类对象的特殊性。这样,客户就可以独立于需要使用的子类对象的变化而始终保持稳定了。 理解工厂方法的时候,需要认识到工厂方法的子类仅仅是屏蔽了产品子类创建的不一致性,而具体选择哪一个产品则是外部逻辑提供的。工厂方法的价值在于,产品对象的建立和初始化的不同被屏蔽了,因此,如果子类对象的创建方法没有大的不同,那么使用工厂方法模式是无价值的。
#抽象工厂(AbstractFactory )模式:抽象工厂引入产品簇的概念(工厂与产品存在层次结构)!该模式是所有形态的的工厂模式(简单工厂、工厂方法、抽象工厂)中最为抽象&最具一般性(如下图,左边代表工厂,右边代表不同产品形成的产品簇,而右图是一般性的实现)。该模式用意是向客户端提供一个接口,使得客户端在不必指定产品具体类型的情况下,创建多个产品簇中的产品对象。
上图展示了由ProductA、ProductB为类别的两类产品,存在簇1{ProductA1,ProductB1}和簇2{ProductA2,ProductB2},并分别有工厂ConcreteCreator1和ConcreteCreator2负责创建,为了更直观展示给朋友们形成感性认识,下面给出示例性代码:
public interface Creator { public ProductA fatoryA(); public ProductB factoryB(); } public class ConcreteCreator1 implements Creator { public ProductA factoryA() { retrun new ProductA1(); } public ProductB factoryB() { retrun new ProductB1(); } } public class ConcreteCreator2 implements Creator { public ProductA factoryA() { retrun new ProductA2(); } public ProductB factoryB() { retrun new ProductB2(); } } public interface ProductA {} public class ProductA1 implements ProductA { public ProductA1(){} } public class ProductA2implements ProductA { public ProductA2(){} } public interface ProductB {} public class ProductB1 implements ProductB { public ProductB1(){} } public class ProductB2implements ProductB { public ProductB2(){} }
在上面阐述简单工厂(SimpleFactory)模式时曾提及对于产品族需求的工厂,简单工厂(SimpleFactory)模式无法很好的支持开-闭原则,那么,抽象工厂(AbstractFactory)模式对开-闭原则的支持度到底又如何呢?简单地说,是有选择的完全支持。在产品等级结构的数目不变的情况下,增加新的产品族,对原有系统不会造成任何影响,此时是完全支持开-闭原则。然而,在产品族的数目不变的情况下,加新的产品等级结构,则原有系统实现的FactoryMethod都应做相应的调整,此时是处于不支持开-闭原则状态的。
关于何时应该考虑使用抽象工厂,前人总结了以下几点:1、一个系统不应当依赖于产品实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。2、系统拥有的产品有多余一个的产品族,而系统只消费其中某一族的产品。(与抽象工厂的起源有关)3、同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。4、系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
以上这几点在抽象工厂(AbstractFactory)模式的起源中有很好的体现,其实,抽象工厂起源于跨操作系统的产品实现,通过下图的抛砖引玉,相信大家已不言自明了:
#建造者(Builder )模式:将一个产品的内部表象与产品的生产过程分离,从而可以使一个建造过程生成具有不同内部表象的产品对象。若我们把工厂模式看做现实世界的生产车间,负责某种产品的创建工作,那么我们可以把建造者模式看做是组装车间,负责产品组装形成相对复杂的产品。比如采用Builder模式来生成汽车,我们便可以用工厂方法生产汽车所需的各类部件(轮胎、车灯、方向盘、发动机等等),然后由Builder负责组装成最终产品汽车。下图是通用性的Builder模式示例图:
这样以来,我们便可以看到简单工厂模式、工厂方法、抽象工厂模式与建造者模式它们之间的一些关系与侧重点。简单工厂模式负责产品的创建,使得客户端无需关心产品的创建过程,工厂方法是针对简单工厂模式存在的缺点(不完全支持开-闭原则,增加新产品虽不影响客户端,但需要维护简单工厂逻辑)而引入的,抽象工厂模式引入了产品族的概念,是工厂模式的进一步扩展,而建造者模式则更侧重于复杂产品的生产,期间可以利用工厂模式完成builderPart工作。
以下是使用建造者模式几种场景以及期待达到的效果。
场景:1、需要生成的产品对象有复杂的内部结构。(工厂各车间:每个内部成分本身可以是对象,也可以仅是产品对象的一部分,自身并不构成对象概念)
2、需要生成的产品对象的属性相互依赖。(工厂流水线:builder模式可以强制实行一种分步骤进行的建造过程,因此,若产品对象的一个属性必须在另一个属性被赋值之后才可以赋值,使用builder模式便是一个很好的设计思想)
3、在对象创建过程中会使用到系统中的其它一些对象,这些对象在产品的创建过程中不易得到。
效果:使用builder模式主要有以下效果:
1、builder模式的使用使得产品的内部表象可以独立地变化。客户端不必知道产品内部组成的细节。
2、每一个builder都相对独立,与其它的builder无关。
3、模式所建造的最终产品更易于控制。
在这里我们着重看下建造者(Builder)模式与工厂(Factory)(主要是AbstractFactory)模式间的关系。两者很相似,都是用来创建同时属于几个产品族的对象的模式。不同之处在于--AbstractFactory模式中(相对简单产品),每一次工厂对象被调用时都会返回一个完整的对象,至于client如何处理该对象与工厂自身无关;Builder模式(相对复杂产品,侧重产品组装流水),它则是一点一点地建造出一个复杂的产品,而这个产品的组装过程就发生在builder角色内部。这两种模式,AbstractFactory更加微观,而Builder更加宏观。一个系统可以由一个bulider和一个AbstractFactory组成,client通过调用这个builder角色,间接地调用另一个AbstractFactory角色,AbstractFactory模式返还不同产品族的零件,而Builder模式则把他们组装起来。
从某种角度来说,Build模式也是我们完成生产某种产品工作的策略,这不免会使我们想到策略(Strategy)模式。事实上,Builder模式是Strategy模式的一种特殊情况,而区别在于各自的目标不同。Builder模式适用于为client客户端一点一点地建造新的对象,而不同类型的具体Builder角色虽然都拥有相同的接口,但它们所创建出来的对象可能完全不同;Strategy模式目的是为算法提供抽象的接口,即利用里氏替换满足“开-闭”,通过具体策略类封装某算法,不同的策略类对象为一种一般性的服务提供不同的实现。
与此同时,我们看到Builder组装产品的过程类似于模板(Template)模式,比如builder.builderPart1()-->builder.builderPart2()-->retrieveResult()这样一条流水生产产品,事实上,失去Director角色后的Builder模式顺理成章地发展到了Template模式。
说到这里,大家可能已经意识到模式间是存在关系的,这里便涉及到如何区别与联系这些模式间关系的问题,在这里,我给出一些个人建议。 首先要看模式的类别,一般来说,结构模式和行为模式服务于创造模式,创造模式和服务于客户所需的最终产品。创建模式侧重于产品的创建方法,结构模式侧重于产品的结构间关系结构设计,行为模式侧重于出于创建产品的目的,在创作过程中表现出的行为。其次要看各模式的目的及使用场景与达到的效果,这个过程主要是考察重用粒度与对开-闭原则的支持度,在关于模式中我们已经提到过,保障重用原则的是代码上移&数据下移,保障开-闭原则的是依赖倒转、接口隔离、迪米特原则、里氏替换原则和组合/聚集,不再赘述。《暂完》