Java中的设计模式-五(工厂方法模式)

121 阅读4分钟

释义

  工厂方法也是工厂模式的一种,我们回忆第一节的简单工厂方法,在简单工厂方法中,我们将各种不同的操作具体划分为一个实体类。这样我们就可以减少对原本业务客户端的代码改动。当我们需要新增需求的时候,我们增加业务类,并在工厂类中添加case即可。
  简单工厂模式的最大优点就是在工厂类中包含一定的逻辑判断,根据客户端的选择动态的实例化相关的类。但是,也正如上面所描述,如果需要新增或者修改需求,那么就要对工厂类进行改动。这就违背了设计模式中要遵循的规则之一:开放-封闭原则。再次使用前几节中的超市促销案例,我们将原价、满减、打折的计算方式各自封装了一个类,然后用工厂去决定实例化哪一个类。如果我们要添加一个满减和打折同时触发的活动,就需要再新增计算类,并增加工厂类中的case。

public class CashContext {  
  
    private ISale cs;  

    public CashContext(int cashType) {  
        switch (cashType) {  
            case 1:  
                cs = new CashNormal();  
                break;  
            case 2:  
                cs = new CashRebate(0.7);  
                break;  
            case 3:  
                cs = new CashRebate(0.8);  
                break;  
            case 4:  
                cs = new CashReturn(300, 100);  
                break;  
            case 5:  
                CashNormal cashNormal = new CashNormal();  
                CashReturn cashReturn = new CashReturn(300, 100);  
                CashRebate cashRebate = new CashRebate(0.8);  
                cashReturn.decorate(cashNormal);  
                cashRebate.decorate(cashReturn);  
                cs = cashRebate;  
        }  
    }  

    public double getResult(double price, int num) {  
        return cs.acceptCash(price, num);  
    }  
}

  上面是在使用装饰模式的时候增加的代码。可以看到,当增加一个先打折再满减的时候,尽管使用了装饰模式,避免了多余类的创建,但是我们在类似工厂的CashContext中增加了大量的逻辑,显得特别复杂。下面直接上具体改动措施,然后再进行详解。

public abstract class IFactory {  
    public abstract ISale createSalesModel();  
}
public class CashRebateReturnFactory extends 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;  
    }  

    @Override  
    public ISale createSalesModel() {  
        CashNormal cashNormal = new CashNormal();  
        CashReturn cashReturn = new CashReturn(moneyCondition, moneyReturn);  
        CashRebate cashRebate = new CashRebate(moneyRebate);  
        cashReturn.decorate(cashNormal);  
        cashRebate.decorate(cashReturn);  
        return cashRebate;  
    }  
}
public class CashReturnRebateFactory extends 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;  
    }  

    @Override  
    public ISale createSalesModel() {  
        CashNormal cashNormal = new CashNormal();  
        CashReturn cashReturn = new CashReturn(moneyCondition, moneyReturn);  
        CashRebate cashRebate = new CashRebate(moneyRebate);  
        cashRebate.decorate(cashNormal);  
        cashReturn.decorate(cashRebate);  
        return cashReturn;  
    }  
}

  先添加一个抽象工厂类IFactory,并使用CashRebateReturnFactory和CashReturnRebateFactory继承该工厂,里面有三个参数,注释已经详细标明含义,在createSalesModel中,正是原先case里面的逻辑。这里我们做一个计算方式分类,原价可以看作是满0减0的满减计算,并打10折(即不打折)。满减可以看作打10折后进行满减。这几种情况的结合,可以分为两类,先满减,或先打折。不过区别就是根据需要可能满0减0,或打10折。所以这里分为了两个计算类即可。原价的时候,折扣传1,两个满减参数各传0。

//修改前
public CashContext(int cashType) {  
    switch (cashType) {  
        case 1:  
            cs = new CashNormal();  
            break;  
        case 2:  
            cs = new CashRebate(0.7);  
            break;  
        case 3:  
            cs = new CashRebate(0.8);  
            break;  
        case 4:  
            cs = new CashReturn(300, 100);  
            break;  
        case 5:  
            CashNormal cashNormal = new CashNormal();  
            CashReturn cashReturn = new CashReturn(300, 100);  
            CashRebate cashRebate = new CashRebate(0.8);  
            cashReturn.decorate(cashNormal);  
            cashRebate.decorate(cashReturn);  
            cs = cashRebate;  
    }
}
//修改后
public class CashContext {  
  
    private ISale cs;  

    public CashContext(int cashType) {  
        IFactory iFactory = null;  
        switch (cashType) {  
            case 1:  
                iFactory = new CashRebateReturnFactory(1, 0, 0);  
                break;  
            case 2:  
                iFactory = new CashRebateReturnFactory(0.8, 0, 0);  
                break;  
            case 3:  
                iFactory = new CashRebateReturnFactory(1, 300, 100);  
                break;  
            case 4:  
                iFactory = new CashReturnRebateFactory(0.8, 300, 100);  
                }  
            cs = iFactory.createSalesModel();  
              
    }  

    public double getResult(double price, int num) {  
        return cs.acceptCash(price, num);  
    }  
}

  改动后,构造方法还是返回Isale类型实体。改动前,根据选择的case具体的生成相应的计算类。并在getResult通过策略模式的方式在工厂内部进行行为计算。看着像是多了一层工厂的封装,将复杂的逻辑封装进了IFactory的子类实现中,这也是工厂方法的一个特性。这也符合设计模式的另一个规则:依赖倒转原则,将工厂类抽象出一个接口。所有要生产具体类的工厂,就是先实现该接口,或者继承一个抽象类。
  工厂方法本质是对工厂模式的又一种抽象,类似一维进化成了二维,可扩展性更强。改动时对其他已经完善的业务影响极小,即更加符合开闭原则。针对该案例,超市的促销活动如果仅限于满减,打折两种的结合。那么就不用在CashContext类中增加复杂的逻辑,而且也不用再增加计算类。如果有更新的活动,比如积分等。可以新创建一个工厂和具体的产品类。以达到与其他业务的解耦性。 image.png