从未这么明白的设计模式(二):装饰器模式

栏目: 后端 · 发布时间: 5年前

内容简介:本文原创地址:jsbintask的博客(食用效果最佳),转载请注明出处!装饰器模式是为了运行时动态的扩展一个类的功能。它谨遵

从未这么明白的设计模式(二):装饰器模式

本文原创地址:jsbintask的博客(食用效果最佳),转载请注明出处!

前言

装饰器模式是为了运行时动态的扩展一个类的功能。它谨遵 开闭原则 ,它实现的关键在于 继承和组合的结合使用 ,解耦对象之间的关系。

各种 设计模式 学习地址: https://github.com/jsbintask22/design-pattern-learning

栗子

首先我们列举一个案例,并且按照面向对象的思想来对应实体之间的关系。

有一个咖啡店,销售各种各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不同的咖啡配上不同的调料就会卖出不同的价格。

从未这么明白的设计模式(二):装饰器模式

V1

针对上面的栗子,我们很容易就抽象出对应的实现,如上图。接着,我们就要编写对应的类来实现对应的功能。在这个例子中,主题当然就是 咖啡 ,并且它有一个属性是 名字 ,一个行为 价格 ,出于“面向对象”的思想,我们自然会设计出抽象类 Coffee :

从未这么明白的设计模式(二):装饰器模式
public abstract class Coffee {
    /**
     * 获取咖啡得名字
     */
    public abstract String getName();

    /**
     * 获取咖啡的价格
     */
    public abstract double getPrice();
}

接着,按照继承的思想,我们要开始设计出具体的实现类,因为拿铁,卡布奇洛,蓝山搭配上不同的调料(上面三种)会有不同的价格,名字,所以我们至少得设计出 3 X 3 = 9 个类来分别对应它们的名字和价格:

从未这么明白的设计模式(二):装饰器模式

嗯!我想不用说这样设计得缺陷也很明显了! 由于不同的咖啡和不同的调料得各种任意组合,使得出现了 类爆炸 的现象。既然有这么明显的缺陷,那我们当然得改! 我们可以考虑把各种调料当作属性加入到Coffee这个抽象类中,接着在实现类中计算价格和名字时,分别判断是否加入了各种调料包,得到不同的名字和价格!

按照上面的思想,我们的Coffee类现在变成了这样:

public abstract class Coffee {
    // 是否加了牛奶
    protected boolean addedMilk;
    // 是否加了糖
    protected boolean addedSugar;
    // 是否加了薄荷
    protected boolean addedMint;

    /**
     * 获取咖啡得名字
     */
    public abstract String getName();

    /**
     * 获取咖啡的价格
     */
    public abstract double getPrice();
}

接着,我们实现一种咖啡,蓝山咖啡:

public class BuleCoffee extends Coffee {
    @Override
    public String getName() {
        StringBuilder name = new StringBuilder();
        name.append("蓝山");
        if (addedMilk) {
            name.append("牛奶");
        }
        if (addedMilk) {
            name.append("薄荷");
        }
        if (addedSugar) {
            name.append("加糖");
        }
        return name.toString();
    }

    @Override
    public double getPrice() {
        double price = 10;
        if (addedMilk) {
            price += 1.1;
        }
        if (addedMilk) {
            price += 3.2;
        }
        if (addedSugar) {
            price += 2.7;
        }

        return price;
    }
}

嗯!现在似乎比上面愉快多了。其实不然!我们仔细分析这种设计,会发现它似乎不太符合”封装的思想“,比如说针对拿铁,对于加薄荷而言,对他总是多余的! 而对于蓝山而言,牛奶又显得很多余! 所以这种设计也并不合理。 另外,我们假设coffee,拿铁等实体类来自第三方类库,我们并不能改动这些类的实现, 又要怎么得到名字和价格呢?

这个时候,我们就得使用 装饰器 模式来动态的扩展类行为! 所以我们设计出V3版本。

V3

开闭原则

首先,我们需要了解一个面向对象的一个基本设计原则: 开闭原则 ,它指的是 类应该对修改关闭,对扩展开放

怎么理解呢? 就比如我们上方说的:假如cofee和它的一众实现拿铁,卡布奇洛,蓝山来自第三方类库,并且这个类库已经很”适合“,”实用“了。 而我们为了得到加入不同调料的咖啡的名字和价格,我们就得修改这些实现,而这样的修改,总是免不了 稳定性 的改变。对原本的系统来说也是一种风险! 所以我们应该 对修改关闭,对扩展开放 ;

继承和组合

遵循开闭原则,那我们就得对外扩展,那怎么对外扩展呢? 这也是装饰器模式实现的关键,利用 继承和组合 的结合; 现在我们可以考虑设计出一个装饰类,它也继承自coffee,并且它内部有一个coffee的实例对象:

从未这么明白的设计模式(二):装饰器模式

现在,我们多了一个 咖啡装饰器 : CoffeeDecorator:

public abstract class CoffeeDecorator implements Coffee {
    private Coffee delegate;

    public CoffeeDecorator(Coffee coffee) {
        this.delegate = coffee;
    }

    @Override
    public String getName() {
        return delegate.getName();
    }

    @Override
    public double getPrice() {
        return delegate.getPrice();
    }
}

接着,我们将牛奶,薄荷作为抽象出一个类,继承自CoffeeDecorator,所以,现在类图就成了这样:

从未这么明白的设计模式(二):装饰器模式

我们实现一个 MilkCoffeeDecorator

public class MilkCoffeeDecorator extends CoffeeDecorator {
    public MilkCoffeeDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getName() {
        return "牛奶, " + super.getName();
    }

    @Override
    public double getPrice() {
        return 1.1 + super.getPrice();
    }
}

按同样的方法可以实现出 MintCoffeeDecoratorSugarCoffeeDecorator 。接着我们写一个测试类:

public class App {
    public static void main(String[] args) {
        // 得到一杯原始的蓝山咖啡
        Coffee blueCoffee = new BlueCoffee();
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());

        // 加入牛奶
        blueCoffee = new MilkCoffeeDecorator(blueCoffee);
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());

        // 再加入薄荷
        blueCoffee = new MintCoffeeDecorator(blueCoffee);
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());

        // 再加入糖
        blueCoffee = new SugarCoffeeDecorator(blueCoffee);
        System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
    }
}

从未这么明白的设计模式(二):装饰器模式 从结果我们可以看出,随着不断加入各种调料,价格,名字都在改变! 这说明我们加入不同的调料,动态的改变了咖啡的名字和价格!

思考

从上面的最后的装饰器模式的实现来看,我们可以得出以下结论:

开闭原则

扩展

到现在,我们已经实现了一个自己的装饰器,我们来看看jdk中用到的装饰器实现.

IO

我们可以查看FilterInputStream:

从未这么明白的设计模式(二):装饰器模式

它的主要是实现者为 BufferedInputStream :

从未这么明白的设计模式(二):装饰器模式

所以我们经常可以使用BufferedInputStream装饰一个InputStream,比如FileInputStream:

new BufferedInputStream(FileInputStream);

这就是装饰器模式的典型应用。

tomcat

在tomcat的HttpServletRequest的内部实现代码中, RequestFacde 继承自HttpServlet,而它内部的实现也是通过代理 Request 对象,而Request对象继承自HttpServlet,Request内部代理了 org.apache.coyote.Request 来实现的。

总结

装饰器模式充分展示了组合的灵活。利用它来实现扩展。它同时也是开闭原则的体现。 如果相对某个类实现运行时功能动态的扩展。 这个时候你就可以考虑使用装饰者模式!

关注我,这里只有干货!

谢谢你支持我分享知识

从未这么明白的设计模式(二):装饰器模式

扫码打赏,心意已收

从未这么明白的设计模式(二):装饰器模式

打开 微信 扫一扫,即可进行扫码打赏哦


以上所述就是小编给大家介绍的《从未这么明白的设计模式(二):装饰器模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Sovereign Individual

The Sovereign Individual

James Dale Davidson、William Rees-Mogg / Free Press / 1999-08-26 / USD 16.00

Two renowned investment advisors and authors of the bestseller The Great Reckoning bring to light both currents of disaster and the potential for prosperity and renewal in the face of radical changes ......一起来看看 《The Sovereign Individual》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具