Java中的设计模式-三(装饰模式)

108 阅读5分钟

释义

  从字面理解,装饰,人的装饰可以是衣服,饰品等。但是在程序设计中,就可以理解为对一个类,一个对象等的一种装饰,具体如何进行装饰。我们可以对超市促销程序继续进行修改。也可以说是添加装饰。现在有这样一个需求,先打折后再开始满减。完全可以新增一个计算类,如下面的代码示例,然后switch里面加一个分支创建新增的计算类即可。但是想一下,满减,打折,已经存在的计算类,需要重新写一遍,只因为需要将两者结合成一个类,感觉不是很符合一个完美的程序设计理念。

private double zk = 0.8;
public double acceptCash(double price,int num){
    double result = price * num * zk:
    if (moneyCondition>0 && result >= moneyCondition)
        result = result - Math,floor(result / moneyCondition) * moneyReturn;
    return result;
}

具体实现

  先直接说下装饰模式的处理逻辑,就是通过不断对一种事物进行装饰来达到最终需要的效果,在该需求中。就是对原价的商品,先装饰一层打折,再装饰一层满减。满减和打折的类已经存在。逻辑也已经存在,那就不需要再额外的添加类来处理相关的逻辑。
  根据该逻辑先确定,被装饰类,和装饰类。我们需求是要对原价做一定的计算处理,那么我们就可以把原价计算类CashNormal类当作被装饰类,而满减类CashReturn和打折类CashRebate就是装饰类。

public interface ISale {  
    public double acceptCash(double price, int num);  
}

  定义一个Isale的接口。把装饰类和被装饰类都实现该接口,因为他们都需要实现计算这个功能。 区别就是,由于装饰类有两个,我们需要使用一个父类来实现该接口。而被装饰类只有一个,直接进行实现。

public class CashSuper implements ISale {  
    protected ISale iSale;  
  
    public void decorate(ISale iSale) {  
        this.iSale = iSale;  
    }  
  
    @Override  
    public double acceptCash(double price, int num) {  
        return iSale.acceptCash(price, num);  
    }  
}
public class CashNormal implements ISale {  
    @Override  
    public double acceptCash(double price, int num) {  
        return price * num;  
    }  
}
public class CashRebate extends CashSuper {  
  
    private double moneyRebate = 1d;  

    public CashRebate(double moneyRebate) {  
        this.moneyRebate = moneyRebate;  
    }  

    @Override  
    public double acceptCash(double price, int num) {  
        double v = price * num * moneyRebate;  
        if (super.iSale != null) {  
            return super.acceptCash(v, 1);  
        }  
        return v;  
    }  
}
public class CashReturn extends CashSuper {  
  
    private double moneyCondition;  
    private double moneyReturn;  

    public CashReturn(double moneyCondition, double moneyReturn) {  
        this.moneyCondition = moneyCondition;  
        this.moneyReturn = moneyReturn;  
    }  


    @Override  
    public double acceptCash(double price, int num) {  
        double total = price * num;  
        if (total >= moneyCondition) {  
            total -= Math.floor(total / moneyCondition) * moneyReturn;  
        }  
        if (super.iSale != null) {  
            return super.acceptCash(total, 1);  
        }  
        return total;  
    }  
}

  和前两个案例最大的区别就是在CashSuper类中,加入了一个注入Isale类型参数的逻辑。只要实现了Isale类就可以注入到CashSuper中,而CashSuper是装饰类的父类(此时被装饰类CashNormal已经不再继承该类)。并且,CashSuper不再是一个抽象类,抽象的工作交给了Isale,这里CashSuper有一个具体的实现,使用Isale类型对象进行调用。代码结构设计结束,下面我们看具体的使用。回到CashContext中添加一个先满减后打折的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);  
    }  
}

  根据上面所叙述,抽象工作交给了Isale,那么可以看到生成的对象已经从CashSuper类型改为了Isale类型。

    //创建被装饰类,该类直接执行计算是原价
    CashNormal cashNormal = new CashNormal();  
    //生成满减对象
    CashReturn cashReturn = new CashReturn(300, 100);  
    //生成打折对象
    CashRebate cashRebate = new CashRebate(0.8);  
    //将满减装饰给cashNormal
    cashReturn.decorate(cashNormal);  
    //将满减装饰给cashReturn
    cashRebate.decorate(cashReturn);
    //将cashReturn复制给ISale类型
    cs = cashRebate;  

  继续对该分支进行一一解析,最后的调用是使用CashReturn进行调用的。根据debug可以看出执行顺序,CashRebate的acceptCash先执行,进行了一次满减,这里对Isale进行了一次判空。将代码再次粘贴过来分析一下。

@Override  
public double acceptCash(double price, int num) {  
    double v = price * num * moneyRebate;  
    if (super.iSale != null) {  
        return super.acceptCash(v, 1);  
    }  
    return v;  
}  

  这里判断父类中的iSale是否有值,如果不进行装饰,不执行decorate方法的话,那么它就是空的,也就是前四个switch分支中,就不再执行父类的方法,当前类执行完,计算就已经结束。但是对于第五个分支中,给CashRebate中注入了ISale类型参数,那么此时就不为空,就会继续执行super中的方法,super中使用了iSale.acceptCash();即执行注入的对象的acceptCash方法,该案例中注入的是CashReturn,那么就会执行其中的计算方式,该类是负责满减的,那就又执行了一次满减。同理,该对象中又注入了原价计算逻辑。最后将打折又满减后的价钱以数量1的逻辑进行一次计算(每次调用super中的方法都默认数量为1,因为最开始的计算类中已经对价格和数量进行计算,根据该业务不再多进行计算)。
  这里总有一种递归的感觉,只要执行类还有注入对象,那就通过super调用的方式继续执行对象中的方法。而该对象还可以被注入对象,可以无限循环下去。那么如果需要的话,可以无限的满减,打折,满减,打折循环下去。这里先被注入的最后才会执行,正如人穿衣服进行装饰一样,最后穿上的才是最先看见的。 image.png