该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
工厂模式:
烘烤OO的精华——烘烤某些松耦合的OO设计;
除了使用new操作符之外,还有更多制造对象的方法;
本章我们将了解到实例化的这个活动不应该总是公开的进行,认识到初始化经常造成“耦合”问题,并了解工厂模式如何从复杂的依赖中帮你脱困;
new——具体:
每次使用new的时候都是在针对实现编程,因为它实例化了一个具体 的类;
我们知道代码绑定具体类会导致代码更脆弱,更缺乏弹性;
我们可能会根据if条件判断类在运行时具体需要实例化哪个子类;
new的问题:
new并没有问题,问题在于“改变”影像了new的使用;
在为一个父类变量赋值时,所有实例化子类的情形会根据不同的条件进行,这就是一种改变,直到运行时你才会直到具体实例化的是哪个子类;
而这种改变会造成耦合,比如条件的修改,新增子类的实例化等,这些都需要修改代码;
我们的代码没有“对修改关闭”,用新的具体来扩展代码,必须重新打开它;
我们的设计原则:
对扩展开放 对修改关闭;
那我们先将变化分类出来:将实例化具体类的代码从应用中抽离,或者封装,使他们不会干扰应用的其他部分;
我们以比萨店的场景进行举例:
你有一家 比萨店,顾客可以对比萨进行下单;
我们编码如下:
现在需要更多的比萨类型,我们将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操作符替换成工厂对象的创建方法;
编译运行:
bogon:简单工厂模式(更像一种编程习惯) huaqiang$ java SimplePizzaDriver
PizzaSub1 init!
PizzaSub1 prepare!
PizzaSub1 bake!
PizzaSub1 cut!
PizzaSub1 box!
定义简单工厂:
上述编码示例,对应的就是简单工厂,相比于称之为设计模式,更像一种编程习惯,由于使用的比较多,我们称之为 简单工厂模式;
其中的SimplePizzaFactory的createPizza()方法也通常声明为静态;
工厂生产的产品定义为具体类,同一实现了相同的接口,被工厂创建之后返回给客户;
“实现一个接口”泛指“实现某个超类型”(可以是类或接口);
类图如下:
经过简单工厂模式的热身,我们继续介绍两个重量级的模式,他们都是工厂;
工厂方法模式——:
加盟比萨店:
加盟店可以利用我们已有比萨店的代码,让比萨的流程一致,毕竟我们是连锁店;
一家加盟店能制造北方口味的比萨:厚饼 重味道的酱料 大量的芝士;
一家加盟店能制造南方口味的比萨:薄饼 轻味道的酱料 少量的芝士;
为了让加盟店和比萨的创建绑定在一起实现一定的质量控制,又想让比萨的口味设置保持一定的弹性,我们需要“框架”:
目标:
让比萨制作活动局限于PizzaStore类,同时又能让这些加盟店依然可以自由地制作该区域的风味;
方案:
把createPizza()方法放回到PizzaStore中,不过要把它设置为“抽象方法”,然后为每一个区域创建一个PizzaStore的子类;
我们编码如下:
编译运行:
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)类:
创建者通常会包含依赖于抽象产品的代码,而这些抽象产品由子类制造;
创建者不需要真的知道在制造那种具体产品;
产品(Product)类:
创建者和产品:
将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架;
此外,工厂方法将生产知识封装进哥哥创建者,这种做法也可以被视为一个框架;
创建者和产品类层级是平行的:
它们都有各自的抽象类,各自的抽象类 还有许多具体的子类,每个子类都有自己特定的实现;
创建者的子类封装创建产品子类的知识;
创建者的抽象类关联使用产品抽象类的知识;
定义工厂方法模式:
正式定义:
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;
工厂方法模式能够封装具体类型的实例化;
抽象的Creater提供了一个创建对象的方法的接口,成为“工厂方法”;在抽象的Creater中,任何其他市县的方法,都可以使用到这个工厂方法所制
造的产品,但只有子类真正实现这个工厂方法并创建产品(选择类子类,自然就决定了实际创建的产品);
工厂方法模式类图:
优点:
将产品的实现从使用中解耦,增加改变产品的实现,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和原料之间的解耦;
把工厂传递给每一个比萨,以便比萨能从工厂中取得原料;
我们发现:
上述类图中既有我们已经学习的工厂方法模式,也有我们即将介绍的抽象工厂模式;
二者两种模式,其实都是从简单工厂模式演化而来的:
简单工厂模式中的Factory类,如果移到了客户类成为一个抽象方法,有其子类提供创建,对应的就是 工厂方法模式;
简单工厂模式中的Factory类,如果抽象为一个接口,由其子类提供创建原料家族的实现,对应的就是 抽象工厂模式;
我们继续往下看:
我们引入新类型的工厂,也就是所谓的抽象工厂,来创建比萨原料家族;
通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口编码,代码实现实际工厂解耦;
定义抽象工厂模式:
抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类;
就好像子类的prepare()中所做的(图中方法错误 应去掉 abstract前缀);
类图:
客户需要ProductA1和ProductB1,只需要使用一个具体的工厂取创建即可,而不需要实例化任何产品对象;
可以看到:
抽象工厂定义了一个负责创建一组产品的接口,抽象工厂的子类提供具体的方法,其中使用了多个工厂方法;
当需要创建产品家族和想让制造的相关产品集合起来时,可以使用抽象工厂;
工厂方法则可以把客户代码从需要实例化的具体类中解耦,如果还不知道需要实例化哪些具体的类可使用,继承为子类然后实现工厂方法即可;
总结:
1.所有的工厂都是用来封装对象的创建;
2.简单工厂,虽然不是真正的设计模式,但仍不失为一个简单方法,可以将客户程序从具体类中解耦;
3.工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象;
4.抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中;
5.所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合;
6.工厂方法允许类将实例化延迟到子类进行;
7.抽象工厂创建相关的对象家族,而不需要依赖它们的具体类;
8.依赖倒置原则,指导我们避免依赖具体类型,而尽量依赖抽象;
9.工厂很厉害,帮助我们更好的针对抽象编程,而不是针对具体类编程;
OO基础:
抽象;
封装
继承;
多态;
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为交互对象之间的松耦合设计而努力;
类应该对扩展开放,对修改关闭;
——依赖抽象,不要依赖具体类;
OO模式:
策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;
观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;
装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;
——简单工厂模式;
——工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;
——抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确具体的类;