设计模式-装饰者模式-以蛋糕装饰为例

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

参考:《HeadFirst设计模式》


1.关于装饰者模式

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

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

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

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

2.蛋糕抽象类:ICake

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

/**
 * 

生日蛋糕接口

* * @author hanchao */
public interface ICake { /** * 展示制作过程 */ void showMakingProcess(); /** * 总花费 */ float getCost(); /** * 显示花费 */ void showCost(); }

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

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

子类1:水果蛋糕:FruitCake

/**
 * 

实现方式1:每种蛋糕一个子类:水果蛋糕

* * @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

/**
 * 

实现方式1:每种蛋糕一个子类:奶油蛋糕

* * @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

/**
 * 

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

* * @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

/**
 * 

蛋糕装饰品

* * @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

/**
 * 

水果

* * @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

/**
 * 

奶油

* * @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

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

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

/**
 * 

蛋糕胚

* * @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(new CreamDecorator(new CakeEmbryo()), "6片菠萝", 20f), "6颗草莓", 30f);
        cake.showMakingProcess();
        cake.showCost();

        System.out.println();

        log.info("实现方式3:装饰者模式:制作一个奶油蛋糕:");
        cake = new CreamDecorator(new CakeEmbryo());
        cake.showMakingProcess();
        cake.showCost();
    }

优点:

  • 复用性高:只要使用某种装饰品,则必然复用其代码。
  • 扩展性高:如果需求发送变化,例如新增一种装饰品巧克力,则只需要新增一种装饰品ChocolateDecorator即可。

6.实际应用

场景:

  • HttpServletRequestbody是流格式,只可读取一次。
  • 通过自定义包装类RequestBodyWrapper.java继承HttpServletRequestWrapper,重写getInputStream和getReader实现了对HttpServletRequest请求的包装,将body数据提取出来放入byte数组中,从而实现request中body数据的多次使用。

URL: Spring MVC代码实例系列-11:Spring MVC实现简单的权限控制拦截器和请求信息统计拦截器

7.总结

最后以UML类图来总结本文的生日蛋糕装饰场景以及装饰者模式。

设计模式-装饰者模式-以蛋糕装饰为例_第1张图片

你可能感兴趣的:(Java设计模式)