• 设计模式之装饰者模式(Decorator Pattern)


    一.什么是装饰者模式?

    装饰者模式能够完美实现“对修改关闭,对扩展开放”的原则,也就是说我们可以在不修改被装饰者的前提下,扩展被装饰者的功能。

    再来看看我们的文件操作代码:

    1
    InputStream in = new BufferedInputStream(new FileInputStream(file));

    被“包裹”在最内层的InputStream对象是new FileInputStream(file),是基本的文件输入流,用BufferedInputStream对象来扩展它的功能,甚至我们还可以这样:

    P.S.注意上面用到的动词——“包裹”,这就是装饰者模式的核心了

    1
    InputStream in = new LineNumberInputStream(new BufferedInputStream(new FileInputStream(file)));

    继续添加一层新的装饰LineNumberInputStream,以扩展获取行号的功能,当然,我们还可以扩展更多的功能,只要继续添加新的装饰就好了

    回过头来想想,我们给FileInputStream添加了一层层装饰,获得了一个个功能,在此过程中,我们实现了功能的动态扩展,但并没有修改被装饰者FileInputStream的任何东西。

    这就是所谓的“对修改关闭,对扩展开放”原则。

    二.举个例子

    假设我们要开店卖Milk,可选的配料有摩卡Mocha(巧克力味),咖啡Coffee,冰水IceWater,当然,如果卖得好的话我们还打算引进新的饮料(Orange、Yoghurt等等)以及新的配料(Salt。。玩笑)

    Milk本身有价格,并且会在节假日打折,各种不同的配料价格也不同,当然,我也可以点一杯加双份IceWater的Milk。。

    -------

    最容易想到的解决方案是:

    定义一个Milk类,包含很多属性,例如hasMocha, hasCoffee, hasIceWater(用来表示已添加的配料),还需要discount属性(用来表示折扣信息)、cost属性(用来表示价格)

    这就好了吗?不,除此之外我们还需要MochaNum, CoffeeNum, IceWaterNum(用来表示配料的份数,重口味顾客需要双份或者更多的配料。。)

    问题解决了,可是这样做真的好吗?

    考虑以下这些情况:

    1.引进一种新的饮料Orange(我们需要定义一个Orange,几乎没有复用的部分,从零开始。。或者,我们可以定义一个Beverage基类,把饮料共有的部分放进去)

    2.引进一种新的配料Salt(我们必须修改Milk类,添加hasSalt, SaltNum属性以满足加盐Milk需求。。)

    。。。

    现在看来我们的解决方案很差,不能适应任何变化,要扩展功能就可能必须修改已有的封装好的代码,而且还存在一个性能上的问题:计算饮料价格部分需要大量的if...else...结构,使得我们的代码很臃肿,且难以复用(不同饮料配料可能不同,计算价格的方法也不同)

    -------

    是时候尝试装饰者模式了

    首先,因为被装饰者与装饰者必须要具有相同的超类(暂不解释为什么),所以,我们定义下面的Beverage基类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package DecoratorPattern;
     
    /**
     * @author ayqy
     * 定义Beverage超类,所有具体Beverage和Ingredient都必须扩展自此类
     *
     */
    public abstract class Beverage {
        String desc = "Unknown Beverage";//定义饮料相关描述信息
        float cost;//定义饮料的价格
         
        public abstract float getCost();//定义cost方法返回该饮料的价格,子类必须实现此方法
         
        public String getDesc(){
            return desc;
        }
    }

    有了Beverage就可以开始定义被装饰者——Milk:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package DecoratorPattern;
     
    /**
     * @author ayqy
     * 定义具体Beverage:Milk类
     *
     */
    public class Milk extends Beverage{
         
        float discount = 1;//定义折扣,节假日Milk可能会打折(默认不打折)
         
        public float getDiscount() {
            return discount;
        }
     
        public void setDiscount(float discount) {
            this.discount = discount;
        }
     
        public Milk(){
            cost = 4.5f;//初始化Milk的价格
            desc = "Milk";//初始化描述信息
        }
     
        @Override
        public float getCost(){
            return discount * cost;//返回打折后的价格
        }
    }

    接下来是装饰者,因为装饰者具有一些不同于Beverage的特性,所以我们对其进行抽象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package DecoratorPattern;
     
    /**
     * @author ayqy
     * 定义Ingredient佐料类,继承自Beverage(在装饰者模式中,装饰者与被装饰者必须具有相同的超类)
     *
     */
    public abstract class Ingredient extends Beverage{
         
        Beverage beverage;//需要添加该佐料的饮料
     
        @Override
        public String getDesc() {
            return "(" + desc + ")" + beverage.getDesc();//佐料的描述应当带上括号,以区别佐料与饮料
        }
     
        @Override
        public float getCost() {
            return cost + beverage.getCost();//配料没有折扣,直接返回其价格 + 饮料价格
        }
         
        //在此添加其它Ingredient不同于Beverage的属性与行为
    }

    注意上面的getDesc与getCost方法,我们把计算价格与生成描述信息的责任完全委托给方法调用机制了,以至于代码是如此的简洁。。

    下面定义具体配料——IceWater,Coffee,Mocha:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package DecoratorPattern;
     
    /**
     * @author ayqy
     * 定义配料IceWater冰水
     *
     */
    public class IceWater extends Ingredient{
         
        public IceWater(Beverage bev)
        {
            cost = 0.5f;
            desc = "IceWater";
            beverage = bev;
        }
    }

    一切准备就绪,我们的Milk小店可以开张了。。

    三.效果示例

    先定义一个测试类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package DecoratorPattern;
     
    public class Test {
        public static void main(String[] args){
            Beverage bev;
             
            System.out.println("做一杯加摩卡加咖啡的Milk。。");
            bev = new Milk();//先做一杯Milk
            bev = new Mocha(bev);//添加Mocha
            bev = new Coffee(bev);//添加Coffee
            System.out.println(bev.getDesc() + " " + bev.getCost() + "¥");
             
            System.out.println("做一杯加双份冰水双份摩卡的咖啡Milk。。");
            bev = new Milk();//重新做一杯Milk
            bev = new IceWater(bev);//添加冰水
            bev = new IceWater(bev);//添加冰水
            bev = new Mocha(bev);//添加Mocha
            bev = new Mocha(bev);//添加Mocha
            bev = new Coffee(bev);//添加Coffee
            System.out.println(bev.getDesc() + " " + bev.getCost() + "¥");
            //当然也可以这样写: Beverage bev = new Coffee(new Mocha(new Milk()));
            //对比我们熟悉的方法链: InputStream in = new BufferedInputStream(new FileInputStream(file));
        }
    }

    结果示例:

    -------

    效果不错吧,再考虑之前的扩展问题:

    1.引进一种新的饮料Orange(我们需要定义一个Orange类继承自Beverage基类,可以复用Beverage基类中已有的部分,如果还不满意,当然也可以抽象出一个“具体饮料类ConcreteBeverage”,让Milk等其它饮料在此基础上扩展)

    2.引进一种新的配料Salt(我们不必对Milk类做任何修改,只需要实现一种Salt配料,继承自Ingredient类就好了)

    。。。

    发现装饰者模式的优点了吗?

    那么是时候泼一盆冷水了。。

    四.装饰者模式的优缺点

    缺点其实显而易见——你见过这么长的代码吗?

    1
    XObject o = new XDecorator(new XXDecorator(new XXXDecorator(new XXXXDecorator())));

    嗯,它只是给被装饰对象做了三次功能扩展而已,当然,还可以更多。。也就意味着可以更长

    而且,我们在使用时创建了很多小对象,就像这样:

    1
    2
    3
    4
    5
    6
    bev = new Milk();//重新做一杯Milk
    bev = new IceWater(bev);//添加冰水
    bev = new IceWater(bev);//添加冰水
    bev = new Mocha(bev);//添加Mocha
    bev = new Mocha(bev);//添加Mocha
    bev = new Coffee(bev);//添加Coffee

    让一个不熟悉装饰者模式的人来读上面的代码,他能很快弄明白吗?

    注意,上面的代码就解释了开篇提到的动词——“包裹”,对吗?

    -------

    优点:

    除了上面提到的动态扩展优点,还有一个更重要的优点就是前面提到的getDesc与getCost方法

    没错,我们可以利用这种调用机制来完成我们的操作(在装饰动作前或者装饰动作后添加我们的自定义操作就好了,例子里其实属于在装饰动作后添加操作),我们很轻易的达到了类似于递归的效果

    这也就解释了“为什么装饰者与被装饰者要具有相同的超类?”,还需要更多一点的解释:

    有一种设计原则是“多用组合,少用继承”,这里我们好像违背了这个原则吧

    其实并没有违背原则,装饰者模式中的继承是为了获得类型的匹配,而不是为了利用继承来扩展类的行为,而“多用组合,少用继承”原则省略掉的前提条件是“(当我们需要扩展类的行为时)多用组合,少用继承”

    <原创>黯羽轻扬 欢迎转载 不必注明原文出处</原创>
    <声明>作者水平有限 错误在所难免 欢迎指正</声明>
    <邮箱>835412398@qq.com 交流方可进步</邮箱>
  • 相关阅读:
    格式化数字,将字符串格式的数字,如:1000000 改为 1 000 000 这种展示方式
    jquery图片裁剪插件
    前端开发采坑之安卓和ios的兼容问题
    页面消息提示,上下滚动
    可以使用css的方式让input不能输入文字吗?
    智慧农村“三网合一”云平台测绘 大数据 农业 信息平台 应急
    三维虚拟城市平台测绘 大数据 规划 三维 信息平台 智慧城市
    农业大数据“一张图”平台测绘 大数据 房产 国土 农业 信息平台
    应急管理管理局安全生产预警平台应急管理系统不动产登记 测绘 大数据 规划 科教 三维 信息平台
    地下综合管廊管理平台测绘 大数据 地下管线 三维 信息平台
  • 原文地址:https://www.cnblogs.com/jiligalaer/p/3963816.html
Copyright © 2020-2023  润新知