设计模式—— 装饰者模式

Posted 玛丽莲茼蒿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式—— 装饰者模式相关的知识,希望对你有一定的参考价值。

目录

问题描述

版本(一)

版本(二)—— 装饰者模式 

1. 版本(一)存在的缺点

2. 装饰者模式

3. 装饰者模式实现咖啡订单系统

装饰者模式的应用——java I/O

 一个非常好的例子—— 编写自己的java I/O装饰者


问题描述

 

 

版本(一)

只使用继承

package HeadFirst.DecoratorPattern;

abstract public class Beverage 
    protected String description;
    //蒸奶
    protected boolean milk = false; //是否要
    protected int milkAmount = 0;  //要几份
    protected double milkCost = 2.0;  //一份多少钱
    //豆浆
    protected boolean soy = false;
    protected int soyAmount = 0;
    protected double soyCost = 1.0;
    //摩卡
    protected boolean mocha = false;
    protected int mochaAmount = 0;
    protected double mochaCost = 3.0;
    //奶泡
    protected boolean whip = false;
    protected int whipAmount = 0;
    protected double whipCost = 2.0;

    public String getDescription()
        return this.description;
    

    public double cost()  //计算所有小料花的价钱
        double total = 0.0;
        if(milk) total = total + this.milkAmount*this.milkCost;
        if(soy) total = total + this.soyAmount*this.soyCost;
        if(mocha) total = total + this.mochaAmount*this.mochaCost;
        if(whip) total = total + this.whipAmount*this.whipCost;
        return total;
    

    //-----------运行时设置要与不要---------
    public void setMilk(boolean milk) 
        this.milk = milk;
    
    public void setSoy(boolean Soy)
        this.soy = soy;
    
    public void setMocha(boolean mocha) 
        this.mocha = mocha;
    
    public void setWhip(boolean whip) 
        this.whip = whip;
    

    //------------运行时设置要几份---------
    public void setMilkAmount(int milkAmount) 
        this.milkAmount = milkAmount;
    
    public void setSoyAmount(int soyAmount) 
        this.soyAmount = soyAmount;
    
    public void setMochaAmount(int mochaAmount) 
        this.mochaAmount = mochaAmount;
    
    public void setWhipAmount(int whipAmount) 
        this.whipAmount = whipAmount;
    


DarkRoast.java为例 

package HeadFirst.DecoratorPattern;

public class DarkRoast extends Beverage
    private double cost = 12.0;
    
    DarkRoast()
        description = "DarkRoast";
    
    public double cost() //重写父类的cost方法
        return super.cost() + this.cost;
    

客户端点单测试:

package HeadFirst.DecoratorPattern;

public class Client 
    public static void main(String[] args) 
        //来了一位顾客,点了一倍DarkRoast,加1份蒸奶,加2份摩卡
        DarkRoast darkRoast = new DarkRoast();
        darkRoast.setMilk(true);
        darkRoast.setMilkAmount(1);
        darkRoast.setMocha(true);
        darkRoast.setMochaAmount(2);

        System.out.println("您需要支付:"+darkRoast.cost()+"元");
    

 运行结果:

您需要支付:20.0元

其实这样已经挺好的了哈哈哈。

  • 如果要更改咖啡涨价或者小料涨价,我们只需要打开父类或者子类修改价钱就行了(尽管这样违背了开闭原则,但是开闭原则有时候该违背就要违背!)。
  • 如果要增加新的小料奥利奥Oreo,只需要打开父类,加入新的成员变量Oreo、OreoAmount、OreoCost,加入新的成员方法setOreoAmount()和setOreo(),然后在cost方法中给Oreo再加一行就行了(当然,也违背了开闭原则)
  • 如果要退出咖啡新品,更简单的了,直接增加一个咖啡子类就好啦,根本不违背开闭原则

非常好的问题:为什么Duck问题只使用继承不行,而咖啡点单问题勉强可以呢?

非常好的回答:和Duck问题不同,鸭子超类每增加一种行为(比如吃玉米粒),就要重新考虑所有子类是否具备这一种行为,然后所有子类都要重写这一行为。但是咖啡点单问题特殊在,是否具备某种小料,是顾客点单时才能确定的,程序员实现咖啡子类的时候是不需要去确定的,所以不存在重写的。

 

版本(二)—— 装饰者模式 

1. 版本(一)存在的缺点

 版本(一)的设计方式存在两个缺点,我认为第二点比较严重:

  • 违背了开闭原则,这个前面已经详细解释过了
  • 设计死板。
    • 版本(一)规定了每种咖啡的类中包含了所有的小料,哪怕这种小料根本不适合加到这种咖啡中(永远用不到)。比如咖啡店出了一款新品“红茶”,那么“奥利奥碎”这种小料就永远用不到。
    • 如果顾客只点了一杯DarkRoast,什么小料都不加,

开闭原则

软件的每个地方都采用开闭原则也没有必要,这会导致代码变得复杂且难以理解。

装饰者模式可以完美解决版本(一)存在的缺点。 

2. 装饰者模式

定义:动态地将装饰品附加到被装饰对象上

         装饰者模式完全遵循开闭原则

实现装饰者模式的关键在于被装饰者和装饰者继承共同的父类,尽管这并不符合现实中的逻辑(奶茶和珍珠怎么能是同一父类呢)。

应用场景

模板类图

  • 被装饰者和装饰者继承共同的父类
  • 具体的装饰类把Component的引用作为成员变量
  • 里面的超类可以是抽象类也可以是接口
  • 装饰者可以在被装饰者行为之前/后加上自己的行为,甚至将被装饰者行为整个取代掉

装饰者模式的优缺点

缺点:

  • 装饰者会导致设计过程中出现许多小对象,如果过度使用,程序会变得很复杂。new BBBBB( new BBBB ( new BBB ( new BB ( new B()))))

3. 装饰者模式实现咖啡订单系统

 下面这个图更容易理解一些,“装饰者一层层包裹上去”

 

 抽象类 Beverage.java

abstract public class Beverage 
    protected String description = "Unknown";
    protected double cost;
    
    public abstract String getDescription();
    public abstract double cost();

 抽象类 CondimentDecorator.java

abstract public class CondimentDecorator extends Beverage

DarkRoast.java 

public class DarkRoast extends Beverage
    DarkRoast()
        description = "DarkRoast";
        cost = 0.99;
    
    @Override
    public String getDescription()  //重写父类的getDescription方法
        return this.description;
    
    @Override
    public double cost() //重写父类的cost方法
        return this.cost;
    

 Milk.java

public class Milk extends CondimentDecorator
    Beverage beverage; // !!!

    Milk(Beverage beverage)  //实现Milk对咖啡的包裹
        this.beverage = beverage;
        this.cost = 0.1;//蒸奶0.1一份
    
    @Override
    public String getDescription() 
        return beverage.getDescription()+",Milk";
    
    @Override
    public double cost() 
        return beverage.cost() + this.cost;
    

Mocha.java 

public class Mocha extends CondimentDecorator
    Beverage beverage;

    Mocha(Beverage beverage)
        this.beverage = beverage;
        this.cost = 0.2;
    

    @Override
    public String getDescription() 
        return beverage.getDescription()+",Mocha";
    

    @Override
    public double cost() 
        return beverage.cost()+this.cost;
    

客户端测试:

public class Client 
    public static void main(String[] args) 
        Beverage darkRoast = new DarkRoast(); //一杯DarkRoast
        darkRoast = new Milk(darkRoast);  //加一份Milk
        darkRoast = new Mocha(darkRoast); //加一份Mocha
        System.out.println(darkRoast.getDescription());
        System.out.println("您需要支付:"+darkRoast.cost()+"元");
    

 输出:

DarkRoast,Milk,Mocha
您需要支付:1.29元

非常好的问题:有不变的咖啡,变化的小料,为什么不能封装变化(写一个小料interface,然后小料各自实现interface成为类,以组合/依赖的方式和咖啡类组合),使用策略模式呢?

非常好的回答:因为顾客不来的话,程序员根本不知道什么小料该和什么咖啡组合啊!和策略模式不同的是,装饰者模式适用于没有稳定对象的场景,即只有在运行时才知道对象是什么样子。就拿例题来说,如果一个顾客要DarkRoust里加1份摩卡,2份蒸奶,3份豆浆,4份奶泡;另一个顾客要HouseBlend加10份蒸奶和2份奶泡,需求成千上万,谁又能全部想到呢?就算穷举一遍,哪个系统能把它们都封装成类呢?所以符合顾客需求的类/对象只能在运行时生成,也就是在【前台点单】时生成,这就是装饰者模式和策略模式的最大区别。

非常好的问题:这种场景是不是用工厂模式、生成器模式、桥接模式也能实现

回答:有待学习

 

装饰者模式的应用——java I/O

以InputStream为例,看看javaI/O是如何利用装饰者模式的

其实,我们之前学习java I/O的时候,“处理流包裹在节点流上”就是装饰者去包裹被装饰者。

 一个非常好的例子—— 编写自己的java I/O装饰者

装饰者类LowerCaseInputStream.java 

import java.io.*;

public class LowerCaseInputStream extends FilterInputStream 
    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected LowerCaseInputStream(InputStream in)   //实现LowerCaseInputStream对in的包裹
        super(in);
    

    @Override
    public int read() throws IOException 
        int c = super.read();
        if(c == -1)
            return -1;
        else
            return Character.toLowerCase((char)c);  //变小写
        
    

    @Override
    public int read(byte[] b, int off, int len) throws IOException 
        //字节怎么变成字符???
        return -1;
    

    @Override
    public int read(byte[] b) throws IOException 
        //字节怎么变成字符???
        return -1;
    

 测试

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Client 
    public static void main(String[] args) 
        LowerCaseInputStream lowerCaseInputStream = null; //装饰
        try 
            //1)
            FileInputStream fileInputStream = new FileInputStream(new File("E:\\\\Java Project\\\\helloworld\\\\iostream\\\\UpperCase.txt"));
            lowerCaseInputStream = new LowerCaseInputStream(fileInputStream);

            //2)读
            int data;
            while((data = lowerCaseInputStream.read())!=-1)
                System.out.print((char)data);
            
         catch (IOException e) 
            e.printStackTrace();
         finally 
            try 
                if(lowerCaseInputStream != null)
                lowerCaseInputStream.close();
             catch (IOException e) 
                e.printStackTrace();
            
        
    

 其中,我们的输入文件UpperCase.txt里的内容是:

STUPID TEACHER!

 程序输出:

以上是关于设计模式—— 装饰者模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之装饰者模式

装饰者模式

装饰者模式入门

JS设计模式装饰者模式

Java IO 装饰者模式

[23种设计模式]---装饰者模式