设计模式 装饰者模式

一、定义

关于装饰者模式的定义,设计模式书中肯定都有,我就直接引用了:

装饰者模式动态低将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。 –摘自《Head First 设计模式》

其实装饰者模式的重点在于给对象动态的附加职责,通过对象组合的方式,运行时装饰对象,在不改变任何底层代码的情况下,给现有对象赋予新的职责。现在不清楚没关系,我们接下来慢慢探究,当看完这篇文章之后,一定要记得回过头来,好好琢磨一下定义哦。

二、需求

现在我们要为卖煎饼的大妈设计一套系统,让大妈能更好的算账收钱。大妈主要经营煎饼(7元),烤冷面(6元)两种小吃,并可以往其中添加配料烤肠(1元),鸡蛋(1元),鸡里脊肉(3元),酥脆(1元,酥脆为煎饼独有),如果生意红火,可能会考虑扩大经营其他小吃或添加配料种类。现需要设计出一套系统,以便快速计算出每位顾客所购小吃的价格。

这是一个小吃类接口:

/**
 * 这是小吃的接口
 */
public interface ISnack {

    //获得小吃的描述
    String getDescription();
    //获得小吃的价格
    double cost();
}

符合针对接口编程,不针对实现编程的OO原则

这是一个配料类的接口,配料类接口IDecoratorSnack继承自ISnack接口,因为需要两接口的实现类(装饰者与被装饰对象)具有相同的超类型,只有这样IDecoratorSnack接口才能取代ISnack接口。

/**
 * 这个是小吃配料的接口
 */
public interface IDecoratorSnack extends ISnack{
    //除了配料的描述和价格,你完全可以根据自己的需要编写配料方法,例如大份小份:
    //只需要定义setSize()和getSize()方法
}

这是小吃和配料价格的枚举类,将价格统一写在这里,即容易修改,也方便自己或他人查阅。

/**
 * 这是小吃和配料的价格枚举,当价格改变的时候,可以轻易改变
 * @author zmj
 */
public enum Price {

    //鸡蛋、烤肠、鸡里脊肉、薄脆、烤冷面、煎饼
    Egg(1.0), Sausage(1.0), Chicken(3.0), Crackers(1.0), ColdRoastSnake(6.0), PancakeSnack(7.0);

    double price;

    private Price(double price) {
        this.price = price;
    }
}

这是具体的煎饼小吃

/**
 * 这个是煎饼类
 * @author zmj
 */
public class PancakeSnack implements ISnack{

    @Override
    public String getDescription() {
        return "煎饼";
    }

    @Override
    public double cost() {
        return Price.PancakeSnack.price;
    }
}

小吃类(被装饰者)既可以单独使用,也可以被配料类(装饰者)包着使用,因为装饰者和被装饰者对象具有相同的超类型,所以在任何需要原始对象(被包装的)的场合,都可以用装饰过的对象替代它

这是具体的烤冷面小吃

/**
 * 这是烤冷面类
 */
public class ColdRoastSnake implements ISnack{

    @Override
    public String getDescription() {
        return "烤冷面";
    }

    @Override
    public double cost() {
        return Price.ColdRoastSnake.price;
    }
}

这是具体的鸡蛋配料

/**
 * 这个是配料鸡蛋类
 */
public class Egg implements IDecoratorSnack{

    //持有被修饰的对象
    ISnack iSnack;

    public Egg(ISnack iSnack) {
        this.iSnack = iSnack;
    }

    @Override
    public String getDescription() {
        return iSnack.getDescription() + " 添加鸡蛋";
    }

    @Override
    public double cost() {
        return iSnack.cost() + Price.Egg.price;
    }
}

每个装饰者都持有一个被装饰者的对象,这个被装饰者对象不一定是原始对象,也可能是被包装了多层的对象。通过这种组合,加入了新的行为。符合对扩展开放,对修改关闭的OO原则

这是具体的烤肠配料

/**
 * 这个是配料烤肠类
 */
public class Sausage implements IDecoratorSnack{

    //持有被修饰对象的引用
    ISnack iSnack;

    public Sausage(ISnack iSnack) {
        this.iSnack = iSnack;
    }

    @Override
    public String getDescription() {
        return iSnack.getDescription() + " 添加香肠";
    }

    @Override
    public double cost() {
        //被修饰对象的价格+烤肠价格
        return iSnack.cost() + Price.Sausage.price;
    }
}

这是具体的鸡里脊肉配料

/**
 * 这是配料鸡肉类
 */
public class Chicken implements IDecoratorSnack{

    ISnack iSnack;

    public Chicken(ISnack iSnack) {
        this.iSnack = iSnack;
    }

    @Override
    public String getDescription() {
        return iSnack.getDescription() + " 添加鸡里脊肉";
    }

    @Override
    public double cost() {
        return iSnack.cost() + Price.Chicken.price;
    }
}

这是具体的薄脆配料

/**
 * 这是配料薄脆类
 */
public class Crackers implements IDecoratorSnack{

    ISnack iSnack;

    public Crackers(ISnack iSnack) {
        this.iSnack = iSnack;
    }

    @Override
    public String getDescription() {
        return iSnack.getDescription() + " 添加薄脆";
    }

    @Override
    public double cost() {
        return iSnack.cost() + Price.Crackers.price;
    }
}

利用装饰者模式,常常会造成设计中产生大量的小类,如果过度使用,会使程序变得很复杂。另外可能还会出现类型问题,如果把代码写成依赖于具体的被装饰者类型,不针对抽象接口进行编程,那么就会出现问题。

测试一下我们的这个设计吧

public class DecoratorPatternTest {

    public static void main(String[] ags) {

        //一位顾客要了一个烤冷面,加了一个肠
        ISnack snackTwo = new ColdRoastSnake();
        snackTwo = new Sausage(snackTwo);
        System.out.println(snackTwo.getDescription() + "价格:" + snackTwo.cost() + "元");

        //一位顾客要了一个煎饼,加了两个鸡蛋,一个肠,一个鸡里脊肉
        ISnack snackOneISnack = new PancakeSnack();
        snackOneISnack = new Egg(snackOneISnack);
        snackOneISnack = new Egg(snackOneISnack);
        snackOneISnack = new Sausage(snackOneISnack);
        snackOneISnack = new Chicken(snackOneISnack);
        System.out.println(snackOneISnack.getDescription() + "价格:" + snackOneISnack.cost() + "元");

    }
}

最终的运行结果

这里写图片描述

源码点击下载

猜你喜欢

转载自blog.csdn.net/MingJieZuo/article/details/79851559