当前位置 博文首页 > 韩超的博客 (hanchao5272):设计模式-装饰者模式-以蛋糕装饰为例

    韩超的博客 (hanchao5272):设计模式-装饰者模式-以蛋糕装饰为例

    作者:[db:作者] 时间:2021-09-05 16:13

    超级链接: Java常用设计模式的实例学习系列-绪论

    参考:《HeadFirst设计模式》


    1.关于装饰者模式

    装饰者模式是一种结构型模式。

    装饰者模式:动态地给一个对象添加一些额外的职责。

    本文以生日蛋糕装饰这一场景来学习装饰者模式

    • 生日蛋糕在制作过程中,可以添加一系列装饰,例如:奶油、水果、饼干等等。
    • 每种装饰品都有独特的计量单位,例如:10个饼干、6片水果等等,每种装饰品都有其价格。
    • 每种蛋糕的装饰流程更不相同,例如:水果蛋糕装饰流程:蛋糕胚+奶油+水果,奶油蛋糕装饰流程:蛋糕+奶油。
    • 在装修完成之后,列出装饰过程及价格。
    • 随着市场变化,可能会增减新的装饰品,例如:因价格上涨,暂无饼干可用;新引进了装饰品巧克力。

    2.蛋糕抽象类:ICake

    无论什么实现方式,对于蛋糕来说都需要一个抽象类,下面的ICake定义了蛋糕的基本操作,如:展示制作过程、显示花费等。

    /**
     * <p>生日蛋糕接口</P>
     *
     * @author hanchao
     */
    public interface ICake {
    
        /**
         * 展示制作过程
         */
        void showMakingProcess();
    
        /**
         * 总花费
         */
        float getCost();
    
        /**
         * 显示花费
         */
        void showCost();
    }
    

    3.实现方式1:每种蛋糕一个子类

    大概思路就是:蛋糕店的每个蛋糕品种,都对应着一个ICake的子类。

    子类1:水果蛋糕:FruitCake

    /**
     * <p>实现方式1:每种蛋糕一个子类:水果蛋糕</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class FruitCake implements ICake {
    
        /**
         * 展示制作过程
         */
        @Override
        public void showMakingProcess() {
            log.info("选取一个蛋糕胚...");
            log.info("包裹一层奶油...");
            log.info("摆放6片菠萝...");
            log.info("摆放6颗草莓...");
        }
    
        /**
         * 计算总花费
         */
        @Override
        public float getCost() {
            return 320.99f;
        }
    
        /**
         * 显示花费
         */
        @Override
        public void showCost() {
            log.info("总价:{}", 320.99f);
        }
    }
    

    子类2:奶油蛋糕:CreamCake

    /**
     * <p>实现方式1:每种蛋糕一个子类:奶油蛋糕</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class CreamCake implements ICake {
        /**
         * 展示制作过程
         */
        @Override
        public void showMakingProcess() {
            log.info("选取一个蛋糕胚...");
            log.info("包裹一层奶油...");
        }
    
        /**
         * 显示总花费
         */
        @Override
        public float getCost() {
            return 120.99f;
        }
    
        /**
         * 显示花费
         */
        @Override
        public void showCost() {
            log.info("总价:{}", 120.99f);
        }
    }
    

    ….

    缺点:

    这样来看,如果蛋糕店有100种蛋糕,则需要实现100个子类。

    这种实现方式的缺点很明显:

    • 实现复杂:几百种蛋糕需要几百个类。
    • 复用性低:每种蛋糕类的代码基本不会复用,存在大量重复代码。
    • 扩展性差:如果需求发送变化,例如蛋糕胚价格发送变化,则可能修改几百个类。违背设计原则对修改关闭,对扩展放开

    4.实现方式2:所有蛋糕共用一个大类

    大概思路就是:所有蛋糕共用一个大类,所有的蛋糕装饰方法如添加奶油、水果等等都放在这个类中。

    超级大类:SuperCake

    /**
     * <p>实现方式2:所有蛋糕共用一个大类</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class SuperCake implements ICake {
        /**
         * 制作过程
         */
        private List<String> processList = new ArrayList<>();
        /**
         * 价格
         */
        private float cost;
    
        /**
         * 展示制作过程
         */
        @Override
        public void showMakingProcess() {
            processList.forEach(log::info);
        }
    
        /**
         * 显示总花费
         */
        @Override
        public float getCost() {
            return cost;
        }
    
        /**
         * 显示花费
         */
        @Override
        public void showCost() {
            log.info("总价:{}", cost);
        }
    
        /**
         * 选取蛋糕胚
         */
        public void selectCake() {
            processList.add("选取一个蛋糕胚...");
            cost += 30f;
        }
    
        /**
         * 添加奶油
         */
        public void addCream() {
            processList.add("包裹一层奶油...");
            cost += 10f;
        }
    
        /**
         * 添加水果
         */
        public void addFruit(String name, float price) {
            processList.add("摆放" + name + "...");
            cost += price;
        }
    }
    

    缺点:

    • 扩展性差:如果需求发送变化,例如新增一种装饰品巧克力,则必然需要修改SuperCake,违背设计原则对修改关闭,对扩展放开

    5.实现方式3:装饰者模式

    首先,需要定义一个装饰者的抽象类。

    • 因为装饰行为是层层包裹的,所以装饰者本身应该与蛋糕是同一类型,及装饰者decorator应该实现ICake
    • 装饰者decorator的装饰目标是蛋糕本身,所以装饰者decorator应该拥有ICake的成员变量。

    根据上述分析,进行编码。

    装饰者抽象类:AbstractCakeDecorator

    /**
     * <p>蛋糕装饰品</P>
     *
     * @author hanchao
     */
    @Slf4j
    public abstract class AbstractCakeDecorator implements ICake {
        /**
         * 包含一个蛋糕对象
         */
        @Getter
        private ICake cake;
    
        public AbstractCakeDecorator(ICake cake) {
            this.cake = cake;
        }
    
        /**
         * 展示制作过程
         */
        @Override
        public abstract void showMakingProcess();
    
        /**
         * 总花费
         */
        @Override
        public abstract float getCost();
    
        /**
         * 显示花费
         */
        @Override
        public void showCost() {
            log.info("总价:{}", getCost());
        }
    }
    

    装饰者实现类:水果装饰:FruitDecorator

    /**
     * <p>水果</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class FruitDecorator extends AbstractCakeDecorator {
    
        private String fruit;
    
        private float cost;
    
        public FruitDecorator(ICake cake, String fruit, float cost) {
            super(cake);
            this.fruit = fruit;
            this.cost = cost;
        }
    
        /**
         * 展示制作过程
         */
        @Override
        public void showMakingProcess() {
            super.getCake().showMakingProcess();
            log.info("摆放" + fruit + "...");
        }
    
        /**
         * 显示总花费
         */
        @Override
        public float getCost() {
            return super.getCake().getCost() + cost;
        }
    }
    

    装饰者实现类:奶油装饰:FruitDecorator

    /**
     * <p>奶油</P>
     *
     * @author hancha
     */
    @Slf4j
    public class CreamDecorator extends AbstractCakeDecorator {
    
        public CreamDecorator(ICake cake) {
            super(cake);
        }
    
        /**
         * 展示制作过程
         */
        @Override
        public void showMakingProcess() {
            super.getCake().showMakingProcess();
            log.info("包裹一层奶油...");
        }
    
        /**
         * 显示总花费
         */
        @Override
        public float getCost() {
            return super.getCake().getCost() + 12f;
        }
    }
    

    被装饰者:蛋糕胚:CakeEmbryo

    无论做多少装饰,最初都有一个原始的被装饰者。

    放到蛋糕装饰场景中,无论添加多少蛋糕装饰,蛋糕胚是最原始的东西。

    /**
     * <p>蛋糕胚</P>
     *
     * @author hnchao
     */
    @Slf4j
    public class CakeEmbryo implements ICake {
        /**
         * 展示制作过程
         */
        @Override
        public void showMakingProcess() {
            log.info("选取一个蛋糕胚...");
        }
    
        /**
         * 显示总花费
         */
        @Override
        public float getCost() {
            return 30f;
        }
    
        /**
         * 显示花费
         */
        @Override
        public void showCost() {
            log.info("总价:{}", getCost());
        }
    }
    

    应用代码:DecoratorDemo

    注意其装饰的方式:层层包裹

    		public static void main(String[] args) {
            //实现方式3:装饰者模式
            log.info("实现方式3:装饰者模式:制作一个水果蛋糕");
            cake = new FruitDecorator(new FruitDecorator(