美文网首页设计模式系列教程
设计模式系列教程—Decorator Pattern(装饰者模式

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

作者: Vander1991 | 来源:发表于2019-05-14 20:31 被阅读0次

    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程序员的困扰。但是其实了解了装饰者的原理,就可以容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的功能。

    面向对象基础

    抽象、封装、多态、继承

    四大原则

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

    模式

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

    相关文章

      网友评论

        本文标题:设计模式系列教程—Decorator Pattern(装饰者模式

        本文链接:https://www.haomeiwen.com/subject/bioeaqtx.html