1.1 需要了解工厂制造细节吗?
"简单工厂只是最基本的创建实例相关的设计模式。但真实情况中,有更多复杂的情况需要处理。简单工厂生成实例的类,知道了太多的细节,这就导致这个类很容易出现难维护、灵活性差问题,让人感觉到了不好的味道。"
"知道很多细节不太好吗?"
"现实中,我们要想过好生活,是不太需要也不太可能知道所有细节的。比如说,我们知道猪长什么样子,也知道红烧肉很好吃,但一头猪是通过怎么样的过程变成红烧肉的呢?养殖、运输、屠宰、销售过程的批发、零售,还有饭店或家里的烹饪过程,对我们来说,都是不需要去了解的。我们去饭店吃饭,只要点了红烧肉,过一会儿它就被送出来了,好吃就行了,你说对不对?"
"将来如果能生产出这样一台机器,送进去是猪,出来就是红烧肉,那就好了。"
"嘿嘿!这样的机器我不知道是否生产得出来。不过,整个过程也算是一种封装吧。在我们的程序中,确实存在封装实例创建过程的模式——工厂方法模式,这个模式可以让创建实例的过程封装到工厂类中,避免耦合。它与简单工厂模式是一个体系的,你可以去研究一下。"
1.2 简单工厂模式实现
工厂类是这样写的
package code.chapter1.calculator4;
public class OperationFactory {
public static Operation createOperate(String operate){
Operation oper = null;
switch (operate) {
case "+":
oper = new Add();
break;
case "-":
oper = new Sub();
break;
case "*":
oper = new Mul();
break;
case "/":
oper = new Div();
break;
}
return oper;
}
}
客户端是这样的
package code.chapter8.calculator1;
import java.util.Scanner;
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
try {
Scanner sc = new Scanner(System.in);
System.out.println("请输入数字A:");
double numberA = Double.parseDouble(sc.nextLine());
System.out.println("请选择运算符号(+、-、*、/):");
String strOperate = sc.nextLine();
System.out.println("请输入数字B:");
double numberB = Double.parseDouble(sc.nextLine());
Operation oper = OperationFactory.createOperate(strOperate);
double result = oper.getResult(numberA,numberB);
System.out.println("结果是:"+result);
}
catch(Exception e){
System.out.println("您的输入有错:"+e.toString());
}
System.out.println();
System.out.println("**********************************************");
}
}
1.3 工厂方法模式实现
先建一个工厂接口:
package code.chapter8.calculator1;
public interface IFactory {
public Operation createOperation();
}
package code.chapter8.calculator1;
//加法工厂
public class AddFactory implements IFactory {
public Operation createOperation(){
return new Add();
}
}
package code.chapter8.calculator1;
//乘法工厂
public class MulFactory implements IFactory {
public Operation createOperation(){
return new Mul();
}
}
package code.chapter8.calculator1;
//减法工厂
public class SubFactory implements IFactory {
public Operation createOperation(){
return new Sub();
}
}
package code.chapter8.calculator1;
//除法工厂
public class DivFactory implements IFactory {
public Operation createOperation(){
return new Div();
}
}
package code.chapter8.calculator1;
public class OperationFactory {
public static Operation createOperate(String operate){
Operation oper = null;
IFactory factory = null;
switch (operate) {
case "+":
factory = new AddFactory();
break;
case "-":
factory = new SubFactory();
break;
case "*":
factory = new MulFactory();
break;
case "/":
factory = new DivFactory();
break;
}
oper = factory.createOperation();
return oper;
}
}
1.4 简单工厂vs.工厂方法
"怪就怪在这里呀,以前我们不是说过,如果我现在需要增加其他运算,比如求x的n次方(xn),或者求a为底数b的对数(logab),这些功能的增加,在简单工厂里,我是先去加求x的n次方的指数运算类,然后去更改OperationFactory类,当中加'Case'语句来做判断。现在用了工厂方法,加指数运算类没问题,去改OperationFactory类的分支也没问题,但又增加了一个指数工厂类,这不等于不但没有降低难度,反而增加类,把复杂性增加了吗?为什么要这样?"
"问得好。简单工厂模式的最大优点在于工厂类中包含必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。就像你的计算器,让客户端不用管该用哪个类的实例,只需要把'+'给工厂,工厂自动就给出了相应的实例,客户端只要去做运算就可以了,不同的实例会实现不同的运算。但问题也就在这里,如你所说,如果要加一个'求x的n次方(xn)'的功能,我们需要给OperationFactory类的方法里加'Case'的分支条件。目前来看,这个OperationFactory类,承载了太多功能,这可不是好办法。这就等于说,我们不但对扩展开放了,对修改也开放了,这样就违背了什么原则?"
"哦,是的,违背的是开放-封闭原则。"
"对,也就是说,我们加减乘除运算的部分已经相当成熟了,但是因为增加新的功能,就要去改已经很成熟的类代码,这就好比很多鸡蛋放在了一个篮子里,这是很危险的。"
"那么工厂方法模式,就可以解决这个问题吗?我感觉我本来是4个运算类,1个工厂类,共5个类,现在多出了4个运算工厂类和1个工厂接口,问题依然没有解决。"
"哈哈,那是因为你没有真的理解工厂方法。举一个例子,我们公司本来只有一家工厂,生产四种不同的产品。后来发展得特别好,需要增加新的两种产品放在另一个地方开设新的工厂。新的工厂不应该影响原有工厂的正常工作,你说怎么办?"
"新工厂建在别的地方,应该不影响原有的工厂运作,最多就是建好后,总公司那里再增加一些协调管理部门就好了。"
"说得非常好。就编程来说,我们应该尽量将长的代码分派切割成小段,再将每一小段'封装'起来,减少每段代码之间的耦合,这样风险就分散了,需要修改或扩展的难度就降低了。加减乘除四个类算是一个工厂的产品,不妨叫它们(基础运算工厂)类,现在增加指数、对数运算类,如果算是另一种工厂的两种产品,不妨称它为'高级运算工厂'类,你觉得有必要去影响原有的基础运算工厂运作吗?"
"哦!我明白你的意思了。并不是要去创建加法工厂、减法工厂这样的类,而是将加减乘除用一个基础工厂来创建,现在增加了新的产品,又不想影响原有的工厂代码,于是就扩展一个新的工厂来处理。我马上改。"
"下面是原有的工厂结构,加减乘除运算已经非常稳定,尽量不要去改变它们。"
"增加了一种新的工厂和两种新的运算类(以后可以扩展更多的高级运算,比如正余弦、正余切等),不要影响原有的代码和运作。"
"增加两个运算类。"
package code.chapter8.calculator2;
//指数运算类,求numberA的numberB次方
public class Pow extends Operation {
public double getResult(double numberA, double numberB){
//此处缺两参数的有效性检测
return Math.pow(numberA,numberB);
}
}
package code.chapter8.calculator2;
//对数运算类,求以numberA为底的numberB的对数
public class Log extends Operation {
public double getResult(double numberA, double numberB){
//此处缺两参数的有效性检测
return Math.log(numberB)/Math.log(numberA);
}
}
工厂接口不变:
package code.chapter8.calculator2;
public interface IFactory {
public Operation createOperation(String operType);
}
"基础运算工厂类,此类已经比较成熟稳定,实现后应该封装到位,不建议轻易修改。"
package code.chapter8.calculator2;
//基础运算工厂
public class FactoryBasic implements IFactory {
public Operation createOperation(String operType){
Operation oper = null;
switch (operType) {
case "+":
oper = new Add();
break;
case "-":
oper = new Sub();
break;
case "*":
oper = new Mul();
break;
case "/":
oper = new Div();
break;
}
return oper;
}
}
"高级运算工厂类,也许还有扩展产品的可能。"
package code.chapter8.calculator2;
//高级运算工厂
public class FactoryAdvanced implements IFactory {
public Operation createOperation(String operType){
Operation oper = null;
switch (operType) {
case "pow":
oper = new Pow();//指数运算类实例
break;
case "log":
oper = new Log();//对数运算类实例
break;
//此处可扩展其他高级运算类的实例化,但修改
//当前工厂类不会影响到基础运算工厂类
}
return oper;
}
}
"左侧新的OperationFactory类与右侧原来的OperationFactory类对比。"
package code.chapter8.calculator2;
public class OperationFactory {
public static Operation createOperate(String operate){
Operation oper = null;
IFactory factory = null;
switch (operate) {
case "+":
case "-":
case "*":
case "/":
//基础运算工厂实例
factory=new FactoryBasic();
break;
case "pow":
case "log":
//高级运算工厂实例
factory=new FactoryAdvanced();
break;
}
//利用多态返回实际的运算类实例
oper = factory.createOperation(operate);
return oper;
}
}
"你或许会发现,新的Opera口与具体工厂类,并不存在具体的实现,与原来的OperationFactory类对比,实例化的过程延迟到了工厂子类中。"不过新的OperationFactory类依然存在'坏味道',当增加新的运算子类时,它本身也是需要更改的,这个先放在一边,以后可以解决。"
"Perfect!我明白了。这就是前面提到的针对接口编程,不要对实现编程吧?"
"是的。我们来看工厂方法的定义。注意关键词——延迟到子类。"
工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。[DP]
工厂方法模式(Factory Method)结构图
package code.chapter8.factorymethod;
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
Creator[] creators = new Creator[2];
creators[0] = new ConcreteCreatorA();
creators[1] = new ConcreteCreatorB();
for(Creator item : creators){
Product product = item.factoryMethod();
product.make();
}
System.out.println();
System.out.println("**********************************************");
}
}
//Product类
abstract class Product {
public abstract void make();
}
//ConcreteProductA类
class ConcreteProductA extends Product {
public void make(){
System.out.println("产品A制造");
}
}
//ConcreteProductB类
class ConcreteProductB extends Product {
public void make(){
System.out.println("产品B制造");
}
}
//Creator类
abstract class Creator {
public abstract Product factoryMethod();
}
class ConcreteCreatorA extends Creator{
public Product factoryMethod(){
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends Creator{
public Product factoryMethod(){
return new ConcreteProductB();
}
}
"我们讲过,既然这个工厂类与分支耦合,那么我就对它下手,根据依赖倒转原则,我们把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法。然后,所有的要生产具体类的工厂,就去实现这个接口,这样,一个简单工厂模式的工厂类,变成了一个工厂抽象接口具体生成对象的工厂。每个工厂可以有多个不同产品,而工厂之间,又是相对隔离封装状态。这样过去已经比较完善的工厂和产品体系,就不需要再去改动它们,而另外需要变更的代码,完全可以通过扩展来变化,这就完全符合了开放-封闭原则的精神。"
"哦,工厂方法从这个角度讲,的确要比简单工厂模式来得强。"
"严格来说,是一种升级。当只有一个工厂时,就是简单工作模式,当有多个工厂时,就是工厂方法模式。类似由一维进化成了二维,更强大了。"
1.5 商场收银程序再再升级
package code.chapter6.decorator5;
public class CashContext {
private ISale cs; //声明一个ISale接口对象
//通过构造方法,传入具体的收费策略
public CashContext(int cashType){
switch(cashType){
case 1:
this.cs = new CashNormal();
break;
case 2:
this.cs = new CashRebate(0.8d);
break;
case 3:
this.cs = new CashRebate(0.7d);
break;
case 4:
this.cs = new CashReturn(300d,100d);
break;
case 5:
//先打8折,再满300返100
CashNormal cn = new CashNormal();
CashReturn cr1 = new CashReturn(300d,100d);
CashRebate cr2 = new CashRebate(0.8d);
cr1.decorate(cn); //用满300返100算法包装基本的原价算法
cr2.decorate(cr1); //打8折算法装饰满300返100算法
this.cs = cr2; //将包装好的算法组合引用传递给cs对象
break;
case 6:
//先满200返50,再打7折
CashNormal cn2 = new CashNormal();
CashRebate cr3 = new CashRebate(0.7d);
CashReturn cr4 = new CashReturn(200d,50d);
cr3.decorate(cn2); //用打7折算法包装基本的原价算法
cr4.decorate(cr3); //满200返50算法装饰打7折算法
this.cs = cr4; //将包装好的算法组合引用传递给cs对象
break;
}
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
"以前不觉得,现在发现确实是太多的new实例了,尤其是5和6,装饰模式的使用,在这个CashContext类中,显得特别的复杂。"
创建几个工厂类来处理这些new?
"从上面的代码来看,我感觉至少应该有原价销售类、打折类、满减返利类、先打折再满减类和先满减再打折类,一共五个工厂类。"
"尝试抽象一下,能不能合并一部分呢?"
"我想想。好像原价类,可以想象成打折的参数为1的打折类。但打折与满减返利好像合并不了。"
"它俩是合并不了,但先打折后满减类能不能涵盖它们俩?"
"我懂了。如果有'先打折后满减类'存在,那它应该有三个初始化参数:折扣值、满减条件、满减返利值,那么打折类,其实就是满减返利值条件为0的情况,另外满减类,就相当于折扣参数为1的情况。"那就只需要'先打折再满减'和'先满减再打折'两个工厂类了。"
1.6 简单工厂+策略+装饰+工厂方法
"我的实现方法,首先原有的ISale、CashSuper、CashNormal、CashReturn、CashRebate等类都不变。"
"增加IFactory接口。"
package code.chapter8.factorymethod1;
public interface IFactory {
public ISale createSalesModel(); //创建销售模式
}
"增加实现IFactory接口的两个类,'先打折再满减'类和'先满减再打折'类,其中红框部分代码为装饰模式的实现。"
package code.chapter8.factorymethod1;
//先打折再满减类
public class CashRebateReturnFactory implements IFactory {
private double moneyRebate = 1d;
private double moneyCondition = 0d;
private double moneyReturn = 0d;
public CashRebateReturnFactory(double moneyRebate,double moneyCondition,double moneyReturn){
this.moneyRebate=moneyRebate;
this.moneyCondition=moneyCondition;
this.moneyReturn=moneyReturn;
}
//先打x折,再满m返n
public ISale createSalesModel(){
CashNormal cn = new CashNormal();
CashReturn cr1 = new CashReturn(this.moneyCondition,this.moneyReturn);
CashRebate cr2 = new CashRebate(this.moneyRebate);
cr1.decorate(cn); //用满m返n算法包装基本的原价算法
cr2.decorate(cr1); //打x折算法装饰满m返n算法
return cr2; //将包装好的算法组合返回
}
}
package code.chapter8.factorymethod1;
//先满减再打折类
public class CashReturnRebateFactory implements IFactory {
private double moneyRebate = 1d;
private double moneyCondition = 0d;
private double moneyReturn = 0d;
public CashReturnRebateFactory(double moneyRebate,double moneyCondition,double moneyReturn){
this.moneyRebate=moneyRebate;
this.moneyCondition=moneyCondition;
this.moneyReturn=moneyReturn;
}
//先满m返n,再打x折
public ISale createSalesModel(){
CashNormal cn2 = new CashNormal();
CashRebate cr3 = new CashRebate(this.moneyRebate);
CashReturn cr4 = new CashReturn(this.moneyCondition,this.moneyReturn);
cr3.decorate(cn2); //用打x折算法包装基本的原价算法
cr4.decorate(cr3); //满m返n算法装饰打x折算法
return cr4; //将包装好的算法组合返回
}
}
"有了上面的这些准备后,CashContext类就简单多了,它针对的是ISale接口、IFactory接口编程,然后两个工厂类,对于各个打折满减算法CashSuper、CashNormal、CashReturn、CashRebate等具体类一无所知。实现了松耦合的目的。"
package code.chapter8.factorymethod1;
public class CashContext {
private ISale cs; //声明一个ISale接口对象
//通过构造方法,传入具体的收费策略
public CashContext(int cashType){
IFactory fs=null;
switch(cashType) {
case 1://原价
fs = new CashRebateReturnFactory(1d,0d,0d);
break;
case 2://打8折
fs = new CashRebateReturnFactory(0.8d,0d,0d);
break;
case 3://打7折
fs = new CashRebateReturnFactory(0.7d,0d,0d);
break;
case 4://满300返100
fs = new CashRebateReturnFactory(1,300d,100d);
break;
case 5://先打8折,再满300返100
fs = new CashRebateReturnFactory(0.8d,300d,100d);
break;
case 6://先满200返50,再打7折
fs = new CashReturnRebateFactory(0.7d,200d,50d);
break;
}
this.cs = fs.createSalesModel();
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
"我感觉工厂方法克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点。"
大鸟:"说得好,它们都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可实现,降低了客户程序与产品对象的耦合。工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。就像生活中,凡是在基层工作过的人都知道,具体事情做得越多,越容易犯错误。相反,如果做官做得高了,说出的话就会比较抽象、笼统,很多时候犯错误的可能性反而就越来越小了。"
"工厂方法模式是不是本质就是对获取对象过程的抽象?"
"说得非常对,就是这样。工厂方法的好处有这么几条:第一,对于复杂的参数的构造对象,可以很好地对外层屏蔽代码的复杂性,注意是指创建新实例的构造对象。比如说我们用了'先打折再满减'类工厂,其实就屏蔽了装饰模式的一部分代码,让CashContext不再需要了解装饰的过程。第二,很好的解耦能力。这点刚才你也说了,这就是针对接口在编程。当我们要修改具体实现层的代码时,上层代码完全不了解实现层的情况,因此并不会影响到上层代码的调用,这就达到了解耦的目的。"
"对了。你说这还不是最佳的做法?那应该如何做呢?还有就是这样还是没有避免修改客户端的代码呀?"
"哈,之前我就提到过,利用'反射'可以解决避免分支判断的问题。不过今天还是不急,等以后再谈。"
"好的好的。你饿了吗?我们要不去撸串吃点夜宵?"
"走!去吃封装羊肉去。"