跟着驴肉火烧学习Java的装饰模式

群里面有一个小伙伴提出问题,装饰模式和继承和派生到底区别在哪里。事实上,除了代码量的缩减以外,装饰模式更大的意义是设计方法的一种改进:

用数学模型可以这样抽象地说——在面向对象的编程中,继承和派生是一种全集和子集的关系;而后引入了接口Interface的概念,将类于类之间交集的关系抽象成了接口。装饰模式是将这两种思想相结合,利用装饰者,对原来的对象进行修饰并赋予新的特性。赋予特性的顺序,数量,均不受类的约束,而是在运行时增删改。

装饰的本质,是在具有相同特性的对象上增加修饰,他不是对类的操作,而是对对象的修饰。装饰者在接收到被装饰者时,不需要管被装饰者是谁,只需要给被装饰者加一层自己对他的修饰就行。

驴肉火烧案例模型

我特别爱吃驴肉火烧,尤其是保定口的驴肉火烧,肥瘦焖子青椒肥肠,无肉不欢的我总是喜欢吃这么几种驴肉火烧的组合方式:

  • 肥瘦火烧
  • 肥瘦火烧+焖子
  • 肥瘦火烧+青椒
  • 肥瘦火烧+焖子+青椒
  • 肥瘦火烧+青椒+焖子(别问为啥,先后顺序口感不同,强迫症!节目效果必须这样做)
  • 肥瘦火烧+焖子+青椒+肥肠(土豪配置)
  • ……

如果咱用继承与派生的方式来抽象模型的话,会形成这样的结构:

  • 肥瘦火烧(父类)
  • 焖子火烧 extends 肥瘦火烧(肥瘦火烧+焖子的子类)
  • 青椒火烧 extends 肥瘦火烧(肥瘦火烧+青椒的子类)
  • 焖子青椒火烧 extends 焖子火烧(焖子火烧+青椒的子类)
  • 青椒焖子火烧 extends 青椒火烧(青椒火烧+焖子的子类)
  • 焖子青椒肥肠火烧 extends 焖子青椒火烧(焖子青椒火烧+肥肠的子类)
  • ……

也就是说,这些原材料有多少种的排列组合方式,我们就要派生出多少种类。OMG,这个代码量虽然比过程化结构设计省了好多,那还是有大量的劳动要做啊!

如果你是焖子店老板,必须提前准备6+种火烧给我这挑剔的顾客选,累屎啦!

换一种思路,如果先前就备货肥瘦火烧,然后将焖子,青椒,肥肠现切进肥瘦火烧中,那么老板的工作量是不是就少了好多呢?

这就是装饰模式的优势:

我们的备菜是4个类——肥瘦火烧、焖子、青椒、肥肠

  • 肥瘦火烧
  • 焖子修饰一下肥瘦火烧
  • 青椒修饰一下肥瘦火烧
  • 焖子修饰一下然后青椒修饰一下肥瘦火烧
  • ……

可以派生出24种不同的火烧排列组合方式,然而代码量只有4个类的代码量!LOL,老板乐开了花。

装饰模式

回到概念上来,装饰模式,即对对象进行装饰。

装饰者和被装饰者,事实上同属一个父类~ 装饰者的目的是对被装饰者属性,行为进行扩充。

生动的Java代码

实现装饰模式,需要3要素:

  1. 抽象出共同的属性方法 interface LvHuoCommonMethods
  2. 被装饰者class LvHuo implements LvHuoCommonMethods
  3. 装饰者class XiuShiZheCaiJiao implements LvHuoCommonMethods等

上代码:

抽象出的共同属性:

/**
 * LvHuoCommonMethods.java
 * 驴肉火烧的基础方法
 */
public interface LvHuoCommonMethods {
    public void getPeiFang();
    public double getJiaGe();
}

被装饰者:

/**
 * LvHuo.java
 * 驴肉火烧,被修饰的类
 */
public class LvHuo implements LvHuoCommonMethods {
    @Override
    public double getJiaGe() {
        System.out.println("基本驴肉火烧:5元");
        return 5.0;
    }

    @Override
    public void getPeiFang() {
        System.out.println("火烧一个。");
        System.out.println("肥瘦1两。");
    }
}

装饰者菜椒:

/**
 * XiuShiZheCaiJiao.java
 * 修饰者菜椒
 */
public class XiuShiZheCaiJiao implements LvHuoCommonMethods {
    private LvHuoCommonMethods huoshao;

    public XiuShiZheCaiJiao(LvHuoCommonMethods huoshao_){
        super();
        this.huoshao = huoshao_;
    }

    @Override
    public void getPeiFang(){
        huoshao.getPeiFang();
        System.out.println("剁两段菜椒放到火烧里。");
    }

    @Override
    public double getJiaGe(){
        double price = huoshao.getJiaGe() + 0.5;
        System.out.println("菜椒另付5毛。");
        return price;
    }
}

装饰者焖子:

/**
 * XiuShiZheMenZi.java
 * 修饰者焖子
 */
public class XiuShiZheMenZi implements LvHuoCommonMethods {
    private LvHuoCommonMethods huoshao;

    public XiuShiZheMenZi(LvHuoCommonMethods huoshao_){
        super();
        this.huoshao = huoshao_;
    }

    @Override
    public void getPeiFang(){
        huoshao.getPeiFang();
        System.out.println("切一片焖子剁碎放到火烧里。");
    }

    @Override
    public double getJiaGe(){
        double price = huoshao.getJiaGe() + 2;
        System.out.println("焖子另付2元。");
        return price;
    }
}

主方法:

/**
 * MainContainer.java
 * 这个方法,将做火烧的顺序调换,生成了两个不同的火烧
 *
 * 可以尝试下
 *
 * 只加焖子
 * 只加菜椒
 *
 * 多加一个修饰者咋搞,加一个修饰者肥肠试试?
 */
public class MainContainer {
    public static void main(String args[]){
        /* ↓↓↓*********** Important!***********↓↓↓ */
        LvHuoCommonMethods lvrouhuoshao1 = new LvHuo();                           //做个火烧
        LvHuoCommonMethods menzihuoshao1 = new XiuShiZheMenZi(lvrouhuoshao1);     //加焖子
        LvHuoCommonMethods menzicaijiaohuoshao1 = new XiuShiZheCaiJiao(menzihuoshao1);    //加菜椒
        /* ↑↑↑*********** Important!***********↑↑↑ */
        menzicaijiaohuoshao1.getPeiFang();
        System.out.println("你需要付款:" + menzicaijiaohuoshao1.getJiaGe() + "元");
        System.out.println();
        System.out.println();
        LvHuoCommonMethods lvrouhuoshao2 = new LvHuo();                           //做个火烧
        LvHuoCommonMethods caijiaohuoshao2 = new XiuShiZheCaiJiao(lvrouhuoshao2);     //加菜椒
        LvHuoCommonMethods caijiaomenzihuoshao2 = new XiuShiZheMenZi(caijiaohuoshao2);    //加焖子
        caijiaomenzihuoshao2.getPeiFang();
        System.out.println("你需要付款:" + caijiaomenzihuoshao2.getJiaGe() + "元");
    }
}

运行的结果如下:

火烧一个。
肥瘦1两。
切一片焖子剁碎放到火烧里。
剁两段菜椒放到火烧里。
基本驴肉火烧:5元
焖子另付2元。
菜椒另付5毛。
你需要付款:7.5元


火烧一个。
肥瘦1两。
剁两段菜椒放到火烧里。
切一片焖子剁碎放到火烧里。
基本驴肉火烧:5元
焖子另付2元。
菜椒另付5毛。
你需要付款:7.5元

读者如果有耐性听我BB到这里,可以再有耐心一点,加上一个修饰者肥肠~

然后在主方法里做一个 肥肠肥肠菜椒焖子肥肠火烧~

如果做出来,那么对修饰者的理解也就差不多了。本例子只对IO流进行修饰,如果被修饰的主体变成了列表、堆栈,那么可以做的事情就很多了。大型的项目里,这样的方法能极大地减轻工作量!

发表评论?

3 条评论。

  1. 😆 😆 😆
    吃货眼里的世界~
    咩咩咩咩

  2. 吃货的世界我们很难懂!

  3. 😳 😳 😳 能不能好好吃饭~哈哈哈

发表评论


注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>