Java设计模式之java装饰者模式详解

目录
  • 介绍
  • 角色
  • 示例代码
  • 星巴克咖啡的例子
    • 方案一
    • 方案二 :将调料内置到Drink类中
    • 方案三:装饰者模式
      • 代码演示
  • 装饰者模式的简化
  • 透明性的要求
  • 半透明的装饰模式
  • 装饰模式的优点
  • 装饰模式的缺点
  • 装饰模式注意事项
  • 适用场景
  • 设计模式在JAVA I/O库中的应用
  • 透明和半透明的装饰模式的区别
  • 参考文章
  • 总结

介绍

装饰者模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

在装饰者模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的类图如下:

角色

  • Component(抽象构件):给出一个抽象接口,以规范准备接收附加责任的对象。
  • ConcreteComponent(具体构件):定义一个将要接收附加责任的类。
  • Decorator(抽象装饰类):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • ConcreteDecorator(具体装饰类):负责给构件对象“贴上”附加的责任。

由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计。

示例代码

抽象构件角色

public interface Component {
    public void sampleOperation();
}

具体构件角色

public class ConcreteComponent implements Component {
    @Override
    public void sampleOperation() {
        // 写相关的业务代码
    }
}

装饰角色

public class Decorator implements Component{
    private Component component;
    public Decorator(Component component){
        this.component = component;
    }
    @Override
    public void sampleOperation() {
        // 委派给构件
        component.sampleOperation();
    }
}

具体装饰角色

public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
    @Override
    public void sampleOperation() {
     super.sampleOperation();
        // 写相关的业务代码
    }
}
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    @Override
    public void sampleOperation() {
      super.sampleOperation();
        // 写相关的业务代码
    }
}

星巴克咖啡的例子

方案一

加入不同调料的咖啡,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。

方案二 :将调料内置到Drink类中

这种设计虽然满足了现在的需求,但是我们想一下,如果出现下面情况,我们怎么办,

①、调料价钱的改变会使我们更改现有代码。

②、一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。

③、以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。

④、万一顾客想要双倍摩卡咖啡,怎么办?

很明显,上面的设计并不能够从根本上解决我们所碰到的问题。并且这种设计违反了 开放关闭原则(类应该对扩展开放,对修改关闭。)。

那我们怎么办呢?好啦,装饰者可以非常完美的解决以上的所有问题,让我们有一个设计非常nice的咖啡馆。

方案三:装饰者模式

这里的Coffee是一个缓冲层,负责将抽取出所有具体咖啡的共同点

代码演示

饮料抽象类:

public abstract class Drink
{
   protected String decription="";//描述
   public String getDecription() {
      return decription;
   }
   public abstract Integer cost();//返回饮料的价格
}

缓冲层:抽取出所有咖啡类的共同特征,即计算价钱

//缓冲层----所有种类咖啡的共同点抽取出来
public abstract class Coffee extends Drink
{
    //共同特点:计算价格
    @Override
    public Integer cost() {
        //价格从0累加
        return 0;
    }
}

具体的咖啡类:

public class LongBlack extends Coffee
{
    LongBlack()
    {
        decription="美式咖啡";
    }
    @Override
    public Integer cost() {
        return 15;
    }
}

public class ChinaBlack extends Coffee
{
    ChinaBlack()
    {
        decription="中式咖啡";
    }
    @Override
    public Integer cost() {
        return 10;
    }
}

public class Espresso extends Coffee
{
    //设置描述信息
    Espresso()
    {
        decription="意大利咖啡";
    }
    @Override
    public Integer cost() {
        //意大利咖啡20元
        return 20;
    }
}

抽象装饰者

//装饰者
public abstract class Decorator extends Drink
{
    @Override
    public abstract String getDecription();
}

具体装饰者—即调料

public class Milk extends Decorator{
    Drink drink;
    Milk(Drink drink)
    {
        this.drink=drink;
    }
    @Override
    public String getDecription()
    {
        return "加了牛奶的"+this.drink.getDecription();
    }
    @Override
    public Integer cost()
    {
        return this.drink.cost()+3;
    }
}

public class Chocolate extends Decorator{
     //用一个实例变量记录饮料,也就是被装饰者
    Drink drink;
    Chocolate(Drink drink) {
       this.drink=drink;
    }
    @Override
    public String getDecription() {
        return "加了巧克力的"+drink.getDecription();
    }
    @Override
    public Integer cost() {
        //在原有饮料价格的基础上加上调料味的价格
        return 5+drink.cost();
    }
}

测试

public class test
{
    @Test
    public void test()
    {
        //模拟下单
        //首先点一个美式咖啡,不加任何调料
        Drink drink=new LongBlack();
        System.out.println("购买了"+drink.getDecription()+"  花了"+drink.cost());
        //给美式咖啡加一个巧克力
        drink=new Chocolate(drink);
        System.out.println("购买了"+drink.getDecription()+"  花了"+drink.cost());
        //给美式咖啡再加一个牛奶
        drink=new Milk(drink);
        System.out.println("购买了"+drink.getDecription()+"  花了"+drink.cost());
        //再把牛奶和巧克力加一次
        drink=new Chocolate(drink);
        System.out.println("购买了"+drink.getDecription()+"  花了"+drink.cost());
        drink=new Milk(drink);
        System.out.println("购买了"+drink.getDecription()+"  花了"+drink.cost());
        System.out.println("====================================================");
        //简化写法
        Drink d=new Chocolate(new Milk(new ChinaBlack()));
        System.out.println("购买了"+d.getDecription()+"  花了"+d.cost());
    }
}

装饰者模式的简化

大多数情况下,装饰模式的实现都要比上面给出的示意性例子要简单。

如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类。如下图所示:

如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做。如下图所示:

透明性的要求

装饰模式对客户端的透明性要求程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量。

用顶层抽象父类指向具体子类,以多态的形式实现透明性要求

应该像下面这样写:

Drink drink=new LongBlack();
//给美式咖啡加一个巧克力
drink=new Chocolate(drink);

而不是这样写

Drink drink=new LongBlack();
//给美式咖啡加一个巧克力
Chocolate drink=new Chocolate(drink);

半透明的装饰模式

然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。

在增强性能的时候,往往需要建立新的公开的方法。

比如巧克力可以单独售卖,即售卖巧克力棒,那么这里巧克力类里面需要新增加一个sell方法,用于单独售卖

这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:

Drink drink=new LongBlack();
//给美式咖啡加一个巧克力
Chocolate drink=new Chocolate(drink);
//售卖巧克力棒
drink.sell();

半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰、半适配器模式。

装饰模式的优点

  • 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
  • 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合 “开闭原则”。

装饰模式的缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
  • 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

装饰模式注意事项

(1) 尽量保持装饰类的接口与被装饰类的接口相同,这样,对于客户端而言,无论是装饰之前的对象还是装饰之后的对象都可以一致对待。这也就是说,在可能的情况下,我们应该尽量使用透明装饰模式。

(2) 尽量保持具体构件类是一个“轻”类,也就是说不要把太多的行为放在具体构件类中,我们可以通过装饰类对其进行扩展。

(3) 如果只有一个具体构件类,那么抽象装饰类可以作为该具体构件类的直接子类。

适用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java语言中的final类)

设计模式在JAVA I/O库中的应用

装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了

由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式。

Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。

下面是使用I/O流读取文件内容的简单操作示例。

public class IOTest {
    public static void main(String[] args) throws IOException {
        // 流式读取文件
        DataInputStream dis = null;
        try{
            dis = new DataInputStream(
                    new BufferedInputStream(
                            new FileInputStream("test.txt")
                    )
            );
            //读取文件内容
            byte[] bs = new byte[dis.available()];
            dis.read(bs);
            String content = new String(bs);
            System.out.println(content);
        }finally{
            dis.close();
        }
    }
}

观察上面的代码,会发现最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理,再把处理后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。

透明和半透明的装饰模式的区别

理想的装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。

而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。

装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。

透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。

相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式也是可以接受的,称为“半透明”的装饰模式,如下图所示

在适配器模式里面,适配器类的接口通常会与目标类的接口重叠,但往往并不完全相同。换言之,适配器类的接口会比被装饰的目标类接口宽。

显然,半透明的装饰模式实际上就是处于适配器模式与装饰模式之间的灰色地带。如果将装饰模式与适配器模式合并成为一个“包装模式”的话,那么半透明的装饰模式倒可以成为这种合并后的“包装模式”的代表。

参考文章

设计模式 | 装饰者模式及典型应用

《JAVA与模式》之装饰模式

设计模式之装饰者模式

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 举例讲解Java设计模式编程中Decorator装饰者模式的运用

    概念 装饰者模式动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案. 装饰者和被装饰对象有相同的超类型. 你可以用一个或多个装饰者包装一个对象. 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合 ,可以用装饰过的对象代替它. 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的. 对象可以在任何时候被装饰,所以可以在运行时动态地.不限量地用你喜欢的装饰者来装饰 对象. 在Java中,io包下的很多类就是典型的装饰

  • Java设计模式之装饰者模式详解和代码实例

    装饰者模式可以给已经存在的对象动态的添加能力.下面,我将会用一个简单的例子来演示一下如何在程序当中使用装饰者模式. 1.装饰者模式 让我们来假设一下,你正在寻找一个女朋友.有很多来自不同国家的女孩,比如:美国,中国,日本,法国等等,他们每个人都有不一样的个性和兴趣爱好,如果需要在程序当中模拟这么一种情况的话,假设每一个女孩就是一个Java类的话,那么就会有成千上万的类,这样子就会造成类的膨胀,而且这样的设计的可扩展性会比较差.因为如果我们需要一个新的女孩,就需要创建一个新的Java类,这实际上也

  • 23种设计模式(6) java装饰者模式

    23种设计模式第六篇:java装饰者模式 定义: 在不必改变原类文件和原类使用的继承的情况下,动态地扩展一个对象的功能.     它是通过创建一个包装对象,也就是用装饰来包裹真实的对象来实现. 角色: 抽象构件角色(Project):给出一个接口,以规范准备接收附加责任的对象.     具体构件角色(Employe):定义一个将要接收附加责任的类.     装饰角色(Manager):持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口.     具体装饰角色(ManagerA.Manag

  • java设计模式系列之装饰者模式

    何为装饰者模式 (Decorator)? 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 一.结构 Component : 定义一个对象接口,可以给这些对象动态地添加职责. interface Component { public void operation(); } ConcreteComponent : 实现 Component 定义的接口. class ConcreteComponent implements Component { @

  • java设计模式-装饰者模式详解

    目录 引例 一般解法 装饰者模式 装饰者解法 代码: 抽象类 装饰者 被装饰者 客户端测试 总结: 引例 需求:设现在有单品咖啡:Espresso(意大利浓咖啡)和LongBlack(美式咖啡),调料有Milk(牛奶)和sugar(糖),客户可以点单品咖啡或单品咖啡+调料的组合,计算相应费用.要求在扩展新的咖啡种类时,具有良好的扩展性.改动维护方便. 抛砖引玉,我们先看看两种一般解法. 一般解法 方案一. 枚举创建每一种组合可能,Drink抽象类表示饮料,cost()方法计算价格,子类如Long

  • Java设计模式中的装饰者模式

    目录 模式介绍 UML类图 装饰者模式案例 装饰者模式优点 装饰者模式缺点 模式介绍 23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式.装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. UML类图 类图解析: Componet:主体 ConcreateComponent:主体具体实现类 Decorator:装饰者 ConcreteDecorate:装饰者具体实现类 装饰者模式案例 背景介绍

  • Java躲不过设计模式的坑之代理模式详解

    目录 前言 使用场景 代码分析 总结 前言 设计模式在我看来更像是一种设计思维或设计思想,它就像<孙子兵法>一样,为你的项目工程提供方向,让你的项目工程更加健壮.灵活,延续生命力.本文即将分享的是设计模式的其中一种:代理模式. 代理模式 通用官方定义:代理模式(Proxy Pattern) 是一种结构型设计模式,通过代理对象控制对原对象的访问,并允许在访问前或访问后做一些处理. 简单理解就是给一个对象找了一个替代品,这个替代品得到原对象授权,可以拦截一些无效或低效的访问,从而使得原对象可以释放

  • java 设计模式(DAO)的实例详解

    java 设计模式(DAO)的实例详解 应用场景:在Java程序中,经常需要把数据持久化,也需要获取持久化的数据,但是在进行数据持久化的过程中面临诸多问题(如:数据源不同.存储类型不同.供应商不同.访问方式不同等等),请问如何能以统一的接口进行数据持久化的操作? 其实这个我没学号(≧ ﹏ ≦).我的理解就是一个产品面向的用户不是单一的,所以我们要兼容许多情况如前面提到的数据源不同.存储类型不同.供应商不同.访问方式不同等等. ★ 解决方案 DAO的理解: 1.DAO其实是利用组合工厂模式来解决问

  • java 设计模式之单例的实例详解

    java 设计模式之单例的实例详解 设计模式思想 什么是设计模式:我作为初学者,今天第一次正式学习设计模式,我觉得对与理解什么是设计模式很重要,那么什么是设计模式呢? 设计模式:解决问题的一种行之有效的思想. 设计模式:用于解决特定环境下.重复出现的特定问题的解决方案 我的理解是前人在软件设计的时候碰到了一类问题,他们总结出了一套行之有效,并且经过验证的解决方案. 设计模式的优点: 1.设计模式都是一些相对优秀的解决方案,很多问题都是典型的.有代表性的问题,学习设计模式,我们就不用自己从头来解决

  • Java 设计模式原则之迪米特法则详解

    定义 一个对象应该对其他对象保持最少的了解. 问题由来 类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大. 解决方案 尽量降低类与类之间的耦合. 自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚.无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率.低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的. 迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern Un

  • Javascript设计模式之装饰者模式详解篇

    一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点: 1. 在不改变原对象的原本结构的情况下进行功能添加. 2. 装饰对象和原对象具有相同的接口,可以使客户以与原对象相同的方式使用装饰对象. 3. 装饰对象中包含原对象的引用,即装饰对象是真正的原对象经过包装后的对象. 二.Javascript装饰者模式详解: 描述: 装饰者模式中,可以在运行时动态添加附加功能到对

  • Java设计模式之java装饰者模式详解

    目录 介绍 角色 示例代码 星巴克咖啡的例子 方案一 方案二 :将调料内置到Drink类中 方案三:装饰者模式 代码演示 装饰者模式的简化 透明性的要求 半透明的装饰模式 装饰模式的优点 装饰模式的缺点 装饰模式注意事项 适用场景 设计模式在JAVA I/O库中的应用 透明和半透明的装饰模式的区别 参考文章 总结 介绍 装饰者模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,增加对象功能来说,装饰模式比生成子类实现更为灵活.装饰模式是一种对象结构型模式. 在装饰者模

  • Java设计模式之装饰者模式详解

    目录 具体代码: Person: Student: Doctor: DecoratePerson: ShoeDecorate: DressDecorate: 总结 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 以一个Person对象为例.Person作为一个接口,Student(学生)和Doctor(医生)为Person接口的两个具体类,DecoratorPerson为Pers

随机推荐