Head First Design Mode(5)-工厂模式

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 

工厂模式:    

    烘烤OO的精华——烘烤某些松耦合的OO设计;

    除了使用new操作符之外,还有更多制造对象的方法;

    本章我们将了解到实例化的这个活动不应该总是公开的进行,认识到初始化经常造成“耦合”问题,并了解工厂模式如何从复杂的依赖中帮你脱困;

 

new——具体:

    每次使用new的时候都是在针对实现编程,因为它实例化了一个具体 的类;

    我们知道代码绑定具体类会导致代码更脆弱,更缺乏弹性;

    我们可能会根据if条件判断类在运行时具体需要实例化哪个子类;

    

new的问题:

    new并没有问题,问题在于“改变”影像了new的使用;

    在为一个父类变量赋值时,所有实例化子类的情形会根据不同的条件进行,这就是一种改变,直到运行时你才会直到具体实例化的是哪个子类;

    而这种改变会造成耦合,比如条件的修改,新增子类的实例化等,这些都需要修改代码;

 

我们的代码没有“对修改关闭”,用新的具体来扩展代码,必须重新打开它;

 

我们的设计原则:

    对扩展开放 对修改关闭;

    那我们先将变化分类出来:将实例化具体类的代码从应用中抽离,或者封装,使他们不会干扰应用的其他部分;

 

我们以比萨店的场景进行举例:

    你有一家 比萨店,顾客可以对比萨进行下单;

    

我们编码如下:

Head First Design Mode(5)-工厂模式

 

现在需要更多的比萨类型,我们将orderPizza方法加上一个类型的参数:String type;

    进而在orderPizza方法中通过type.equals(“sometype")进行if条件判断,实例化不同的Pizza子类型,赋值给Pizza pizza变量;

    这里可以将Pizza定义为一个接口,所有的Pizza子类型都需实现该接口;

    在实例化具体的pizza之后,我们继续进行比萨的准备-烘烤-切片-装盒!

 

这种设计的问题 在更多的比萨类型新增 或 减少时就会提现出来:

    随着比萨菜单的修改,orderPizza方法中的这样一段if条件下多个实例化,必须一再修改;

    这将导致,无法让orderPizza()对修改关闭;

 

我们可已经这段变化的代码移到一个专门的类中,这个类只管创建Pizza,我们将这个类的对象称之为“工厂”;

 

工厂(factory):

    处理创建对象的细节,如果有一个SimplePizzaFactory,那么orderPizza()就变成此对象的客户;

    当客户下单,pizzaStore调用orderPizza()的时候,就叫比萨工厂做一个,orderPizza()只关心从工厂得到一个比萨;

    这个比萨(类)实现了Pizza接口,可以调用prepare bake cut box进行对应的操作;

 

提前熟悉下,我们会学习到三种工厂相关的模式:

    简单工厂模式(更像一种编程习惯);

    工厂方法模式;

    抽象工厂模式;

 

工厂的好处:

    一个工厂可以有很多的客户,工厂封装了根据条件创建对象这一“变化”的过程,当需要变更时,修改此类即可;

 

简单工厂模式——:

 

现在我们根据上述思路重新实现PizzaStore类:

    我们把new操作符替换成工厂对象的创建方法;

Head First Design Mode(5)-工厂模式

 

编译运行:

bogon:简单工厂模式(更像一种编程习惯) huaqiang$ java SimplePizzaDriver

PizzaSub1 init!

PizzaSub1 prepare!

PizzaSub1 bake!

PizzaSub1 cut!

PizzaSub1 box!

 

定义简单工厂:

    上述编码示例,对应的就是简单工厂,相比于称之为设计模式,更像一种编程习惯,由于使用的比较多,我们称之为 简单工厂模式;

    其中的SimplePizzaFactory的createPizza()方法也通常声明为静态;

    工厂生产的产品定义为具体类,同一实现了相同的接口,被工厂创建之后返回给客户;    

    “实现一个接口”泛指“实现某个超类型”(可以是类或接口);

 

类图如下:

Head First Design Mode(5)-工厂模式

 

经过简单工厂模式的热身,我们继续介绍两个重量级的模式,他们都是工厂;

 

工厂方法模式——:

 

加盟比萨店:

    加盟店可以利用我们已有比萨店的代码,让比萨的流程一致,毕竟我们是连锁店;

        一家加盟店能制造北方口味的比萨:厚饼 重味道的酱料 大量的芝士;

        一家加盟店能制造南方口味的比萨:薄饼 轻味道的酱料 少量的芝士;

 

为了让加盟店和比萨的创建绑定在一起实现一定的质量控制,又想让比萨的口味设置保持一定的弹性,我们需要“框架”:

    目标:

        让比萨制作活动局限于PizzaStore类,同时又能让这些加盟店依然可以*地制作该区域的风味;

    方案:

        把createPizza()方法放回到PizzaStore中,不过要把它设置为“抽象方法”,然后为每一个区域创建一个PizzaStore的子类;

 

我们编码如下:

Head First Design Mode(5)-工厂模式

 

编译运行:

bogon:工厂方法模式 huaqiang$ javac *.java

bogon:工厂方法模式 huaqiang$ java FactoryMethodDriver

PizzaSub1 init!

PizzaSub1 prepare!

PizzaSub1 bake!

PizzaSub1 cut!

PizzaSub1 box!

 

我们用了一个PizzaStore的超类,每个区域的比萨店都继承这个PizzaStore,每个子类各自决定如何制造比萨;

    “不变的”内容,统一定在了抽象超类中;

    “变的”内容,则由子类覆盖父类方法;

 

各个区域的比萨店之间的差异在于他们制作比萨的风味,现在要让createPizza()能够应对这些变化来负责创建正确的种类的比萨——让PizzaStore的各个子类负责定义自己的createPizza()方法;我们得到了一些PizzaStore具体的子类,每个子类都有自己的制作比萨的变体,而仍然适合PizzaStore框架,并使用调试好的orderPizza()方法;

 

解耦:

    orderPizza()方法对Pizza对象做了许多事情(准备 烘烤 切片 装盒);但由于Pizza对象是抽象的,orderPizza()并不知道那些实际的具体类参与进来,这就是解耦;

    相应的pizza也是调用createPizza()取得的,具体做的是哪一种比萨,由具体的比萨店来决定;

 

新开一家比萨店:

    继承PizzaStore,然后提供createPizza()方法实现自己的比萨风味即可;

    超类的orderPizza()方法,并不知道正在创建的比萨是哪一种,他只知道这个比萨可以被准备、烘烤、切片、装盒;

 

声明一个工厂方法:

    我们再仔细看下加盟比萨店的示例;

    相比于简单工厂模式由一个对象负责所有具体类的实例化,现在通过对PizzaStore做的一些修改,变成由一群子类来负责实例化;

    实例化比萨的责任被移到了一个“方法”中,此方法就如同是一个“工厂”;

 

工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样,客户程序中关于超类的代码就和子类对象创建代码解耦了;

    abstract Product factoryMethod(String type);

        工厂方法是抽象的,所以用子类来处理对象的创建;

        工厂方法必须返回一个产品,超类中的其他方法,通常用到工厂方法的返回值;

        工厂方法将客户(超类中)和实际创建产品的代码分隔开来;

 

不要忽略“比萨”本身:

    Pizza可以是一个抽象超类,也可以定义为接口;

    我们具体的比萨类,扩展自Pizza;

 

比萨加盟店的实例 和 我们上述的说明 对应的就是 工厂方法模式 的一个应用场景和说明;

 

认识工厂方法模式:

    所有的工厂模式都是用来封装对象的创建;

    工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象的创建过程封装的目的;

 

类图:

    创建者(Creater)类:

        创建者通常会包含依赖于抽象产品的代码,而这些抽象产品由子类制造;

        创建者不需要真的知道在制造那种具体产品;

Head First Design Mode(5)-工厂模式

 

    产品(Product)类:

Head First Design Mode(5)-工厂模式

 

创建者和产品:

    将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架;

    此外,工厂方法将生产知识封装进哥哥创建者,这种做法也可以被视为一个框架;

 

创建者和产品类层级是平行的:

    它们都有各自的抽象类,各自的抽象类 还有许多具体的子类,每个子类都有自己特定的实现;

        创建者的子类封装创建产品子类的知识;

        创建者的抽象类关联使用产品抽象类的知识;

 

定义工厂方法模式:

    正式定义:

        工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;

    工厂方法模式能够封装具体类型的实例化;

        抽象的Creater提供了一个创建对象的方法的接口,成为“工厂方法”;在抽象的Creater中,任何其他市县的方法,都可以使用到这个工厂方法所制

造的产品,但只有子类真正实现这个工厂方法并创建产品(选择类子类,自然就决定了实际创建的产品);

 

工厂方法模式类图:

Head First Design Mode(5)-工厂模式

 

优点:

    将产品的实现从使用中解耦,增加改变产品的实现,Creater并不会受到影响,因为Creater和任何的ConcreteProduct之间不是紧耦合;

    工厂方法不一定是抽象的,我们也可以定义一个默认的工厂方法来产生某些具体的产品;

    和简单工厂模式相比,简单工厂的做法可已经对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品;

    将创建对象的代码集中在一个对象或方法中,可以避免代码中的重复,方便维护;客户实例化对象只会依赖接口,而不是具体的类;帮助我们针对接口编程,而不针对实现接口,让代码更具有弹性,方便今后扩展;

 

让我们假设下,如果不使用工厂模式的类设计会如何?

    PizzaStore_1->PizzaStoreSub_N->PizzaSub_N;

    一个比萨店类会依赖多个具体的比萨店,每个比萨店 又要依赖 具体的比萨类型,这就导致 如果一个比萨的实现改变了,最终可能会影响到PizzaStore;

    这种依赖并不是我们想要的,我们有一个OO原则,来阐明这一点;

 

依赖导致原则(Dependency Inversion Principle):

    要依赖抽象,不要依赖具体类;

 

此原则说明了:不要让高层组件依赖底层组件,而且,不管高层或底层组件,“两者”都应该依赖于抽象;

    高层组件:是由其他底层组件定义其行为的类;PizzaStore就是一个高层组件,因为他的行为是由比萨定义的;

 

工厂方法模式就是对这一原则的应用:

    PizzaStore依赖每个Pizza类型,虽然已经抽象了一个Pizza,但是PizzaStore还是依赖了具体的Pizza类,应为在其orderPizza()方法中,更具条件实例化了具体的Pizza类型,所以这个抽象并没有什么影响力;

    而工厂方法刚好将实例化代码的代码独立了出来;

    遵循依赖倒置原则,工厂方法不是唯一的,确实最有效的;

 

遵循依赖倒置原则的指导方针:

    1)变量不可以持有具体类的引用;(使用new就会持有具体类的引用)

    2)不要让类派生自具体类;(派生自具体类就会依赖具体类,所以请派生一个抽象(接口或抽象类))

    3)不要覆盖基类中已实现的方法;(基类已实现的方法应该由所有子类共享,否则就不是一个合适的抽象)

 

抽象工厂模式——:

 

再看比萨店——如何确保原料的一致性

    比萨的加盟店要求使用相同的配料,以保证品质,但是不同的比萨店可能会从各自所在地区进行原材料的采集;

    显然,南方的酱料 不同于 北方的酱料,其他的材料也不尽相同;

 

原料家族会不断壮大,虽然每个加盟店的比萨配料名称一致,但是不同的比萨店的配料都会有各自品种的特点;

 

所有对乡村的比萨都是使用相同的组件制成的,但是每个区域对于这些组件却有不同的实现;

 

建造原料工厂:

    我们建一个工厂来生产原料,负责创建家族中每一种原料,比如生产酱料和面团;

    我们先为工厂定义一个接口:PizzaIngredientFactory;其中定义了各种创建原料的方法;

        那么,为每一个区域创建一个工厂,就可以创建继承/实现PizzaIngredientFactory的子类/具体类;

        这些类,也可以在合适的区域间共享(PizzaIngredientFactory需要是一个抽象类);

    新的方案就是,将新的原料工厂整合进旧的PizzaStore代码中;

        创建PizzaStoreSub1所在区域的原料工厂-PizzaIngredientFactorySub1;

        现在原料和 原料工厂 都已经准备好了,注意原料也可以抽象为原料接口,这里的场景名不复杂就不继续抽象了;

 

重做比萨:

    通过原料工厂实现Pizza和原料之间的解耦;    

    把工厂传递给每一个比萨,以便比萨能从工厂中取得原料;

Head First Design Mode(5)-工厂模式

 

我们发现:

    上述类图中既有我们已经学习的工厂方法模式,也有我们即将介绍的抽象工厂模式;

    二者两种模式,其实都是从简单工厂模式演化而来的:

        简单工厂模式中的Factory类,如果移到了客户类成为一个抽象方法,有其子类提供创建,对应的就是 工厂方法模式;

        简单工厂模式中的Factory类,如果抽象为一个接口,由其子类提供创建原料家族的实现,对应的就是 抽象工厂模式;

    

我们继续往下看:

    我们引入新类型的工厂,也就是所谓的抽象工厂,来创建比萨原料家族;

    通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口编码,代码实现实际工厂解耦;

 

定义抽象工厂模式:

    抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类;

 

就好像子类的prepare()中所做的(图中方法错误 应去掉 abstract前缀);

 

类图:

    客户需要ProductA1和ProductB1,只需要使用一个具体的工厂取创建即可,而不需要实例化任何产品对象;

Head First Design Mode(5)-工厂模式

 

可以看到:

    抽象工厂定义了一个负责创建一组产品的接口,抽象工厂的子类提供具体的方法,其中使用了多个工厂方法;

    当需要创建产品家族和想让制造的相关产品集合起来时,可以使用抽象工厂;

    工厂方法则可以把客户代码从需要实例化的具体类中解耦,如果还不知道需要实例化哪些具体的类可使用,继承为子类然后实现工厂方法即可;

 

总结:

1.所有的工厂都是用来封装对象的创建;

2.简单工厂,虽然不是真正的设计模式,但仍不失为一个简单方法,可以将客户程序从具体类中解耦;

3.工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象;

4.抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中;

5.所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合;

6.工厂方法允许类将实例化延迟到子类进行;

7.抽象工厂创建相关的对象家族,而不需要依赖它们的具体类;

8.依赖倒置原则,指导我们避免依赖具体类型,而尽量依赖抽象;

9.工厂很厉害,帮助我们更好的针对抽象编程,而不是针对具体类编程;

 

OO基础:

    抽象;

    封装

    继承;

    多态;

OO原则:

    封装变化

    多用组合,少用继承

    针对接口编程,不针对实现编程

    为交互对象之间的松耦合设计而努力;

    类应该对扩展开放,对修改关闭;

    ——依赖抽象,不要依赖具体类;

OO模式:

    策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;

    观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;

    装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;

    ——简单工厂模式;

    ——工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;

    ——抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确具体的类;