设计模式系列教程

设计模式系列教程—Decorator Pattern(装饰者模式

2019-05-14  本文已影响0人  Vander1991

3 Decorator Pattern(装饰者模式)

3.1设计原则一类应该对扩展开放,对修改关闭

前言:装饰者模式主要是为了解决继承滥用的问题,以下将使用对象组合的方式做到在运行时装饰类。
1)案例分析一:
REQ1:星星吧咖啡店咖啡种类扩展飞快,Vander作为其老板,准备尽快更新订单系统来满足这一发展。原先的设计如下:


image.png

分析:随着饮品的发展,每种饮料都可以自由搭配,而且本身饮料也很多,除了咖啡之外,鸳鸯、奶茶、可乐、雪碧、酸奶、豆浆等等,而且调料还可以自由选择。使用不同的调料需要付不同的价格,例如一小份牛奶加入咖啡中,加收1块钱等等。如果继续按照上述的设计继续,则继承Beverage抽象类的饮料将非常多,例如coffeewithonemilk,milkTeaWithSteamAndSoy等等,这样能产生无数的搭配,并且牛奶价格上升之后,每个涉及到牛奶的类的cost函数还需要修改,这简直就是噩梦。
解决方法1:Vander 就开始设计了


image.png

超类cost()将计算所有调料的价格,子类覆盖的cost()方法扩展超类的功能,把指定的饮料类型的价钱也加上。

public class Beverage {

    private String desc;
    
    private boolean milk;
    
    private boolean soy;
    
    private boolean mocha;
    
    private boolean whip;

    public boolean hasMilk() {
        return this.milk;
    }
    
    public boolean hasSoy() {
        return this.soy;
    }
    
    public boolean hasMocha() {
        return this.mocha;
    }
    
    public boolean hasWhip() {
        return this.whip;
    }
    
    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
    
    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 float cost() {
        float flavourCost = 0.0f;
        if(hasMilk()) {
            flavourCost = flavourCost + 1.0f;
        } 
        if(hasSoy()) {
            flavourCost = flavourCost + 2.0f;
        }
        if(hasMocha()) {
            flavourCost = flavourCost + 3.0f;
        }
        if(hasWhip()) {
            flavourCost = flavourCost + 4.0f;
        }
        return flavourCost;
    }
    
}
public class DarkRoast extends Beverage {

    private float cost;
    
    public float getCost() {
        return cost + super.cost();
    }

    public void setCost(float cost) {
        this.cost = cost;
    }
    
}

存在的问题:
这个方法出现了以下4个问题:
1、当调料价格改变的时候需要修改Beverage类的代码。
2、一旦有新的调料,需要加上新的方法,并改变超类中的cost()方法。
3、如果有新的饮料(Tea),对这些饮料而言,某些调料(如soy、stream等)可能并不适合,但是这个设计方式中,Tea子类仍将继承那些不合适的方法,例如:hasSoy()(加入豆浆)
4、顾客万一不只是要一份摩卡,想加入两份摩卡调料,上述的方法根本无法应对。

解决方法3(使用装饰者模式)
REQ2:首先有这么一种需求,顾客买了一杯DarkRoast,想加Mocha,然后再加奶泡Whip,最后要计算这杯DarkRoast的金额。
装饰者模式可以用下面的图来说明,该图上可以看到Whip包裹着Mocha,而Mocha又包裹了DarkRoast,并且这三个类的基类都是Beverage,DarkRoast继承自Beverage,且有一个用来计算费用的cost()方法,Mocha对象是一个装饰者,它的类型反映了它所装饰的对象;Whip也是一个装饰者,所以它也反映了DarkRoast类型,并包括一个cost()方法。
最后到结账的时候,先调用最外层的Whip,得到了Whip的价格,然后再调用Mocha的cost,此时Whip的价格传给了Mocha,这样Mocha再加上自己的价格,现在就得到了Mocha+Whip的价格,然后再调用DarkRoast的价格,最后就得到了这杯咖啡的价格。这样相当于就做到“在运行时决定类的行为”。

image.png
image.png
这里要说明的是,Beverage可以用接口也可以用抽象类,若需要加入属性的话,就使用抽象类,若不需要属性则可以使用接口,方便日后代码可以extends其他的类。
以下是关键代码:
Beverage beverage = new DarkRoast();
beverage = new Whip(beverage);
beverage = new Mocha(beverage);
public class Mocha implements CondimentDecorator {

    private Beverage beverage;
    
    public float cost() {
        return this.beverage.cost() + 1.0f;
    }
    
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDesc() {
        return this.beverage.getDesc() + " with " + "Mocha";
    }

}

REQ3:那么问题来了,现实的Java世界中有哪些用了装饰者模式呢,你是否看过这样的一句new LineNumberInputStream(new BufferedInputStream(new FileInputStream)),这句话看似很复杂,其实这就是典型的装饰者模式,FileInputStream是被装饰的“组件”,Java I/O程序库提供了几个组件,包括了FileInputStream、StringBufferInputStream、ByteArrayInputStream… …等。这些类都提供了最基本的字节读取功能。
BfferedInputStream是具体的装饰者,加入了两种行为,利用缓存输入来改进性能,用readline()方法(用来一次读取一行文本输入数据)来增强接口。
LineNumberInputStream也是一个具体的装饰者,它加上了计算行数的功能。
下面进行一个小练习,写一个IO装饰者来讲输入流中所有的大写字母转成小写。

public class LowcaseInputStream extends FilterInputStream {

   public LowcaseInputStream(InputStream in) {
       super(in);
   }
   
   public int read() throws IOException {
       int c = super.read();
       return ( c == -1? c : Character.toLowerCase((char)c));
   }
   
   public int read(byte[] b, int offset, int len) throws IOException {
       int result = super.read(b, offset, len);
       for(int i = offset; i < offset + result; i++) {
           b[i] = (byte)Character.toLowerCase((char)b[i]);
       }
       return result;
   }

}
public class Main {

    public static void main(String[] args) {
        InputStream inputStream;
        int c;
        try {
            inputStream = new FileInputStream("test.txt");
            inputStream = new BufferedInputStream(inputStream);
            inputStream = new LowcaseInputStream(inputStream);
            while((c = inputStream.read()) >= 0) {
                System.out.print((char)c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}

装饰者模式的缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能造成使用此API程序员的困扰。但是其实了解了装饰者的原理,就可以容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的功能。

面向对象基础

抽象、封装、多态、继承

四大原则

设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程。
设计原则三:多用组合,少用继承。
设计原则四:为交互对象之间的松耦合设计而努力。
设计原则五:对扩展开放,对修改关闭。

模式

装饰者模式:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。

上一篇下一篇

猜你喜欢

热点阅读