工厂制造细节无需知--工厂方法模式

发布于:2024-04-09 ⋅ 阅读:(78) ⋅ 点赞:(0)

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不再需要了解装饰的过程。第二,很好的解耦能力。这点刚才你也说了,这就是针对接口在编程。当我们要修改具体实现层的代码时,上层代码完全不了解实现层的情况,因此并不会影响到上层代码的调用,这就达到了解耦的目的。"
"对了。你说这还不是最佳的做法?那应该如何做呢?还有就是这样还是没有避免修改客户端的代码呀?"
"哈,之前我就提到过,利用'反射'可以解决避免分支判断的问题。不过今天还是不急,等以后再谈。"
"好的好的。你饿了吗?我们要不去撸串吃点夜宵?"
"走!去吃封装羊肉去。"