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与模式》之装饰模式

设计模式之装饰者模式

总结

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

时间: 2021-09-13

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

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

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

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

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

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

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

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

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

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

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

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

Java设计模式之静态工厂模式详解

本文实例讲述了Java设计模式之静态工厂模式.分享给大家供大家参考,具体如下: 静态工厂模式(static factory)也叫简单工厂模式. 涉及到3个角色:工厂类角色,抽象产品类角色和具体产品类角色. 抽象产品类可以使用接口或者父类来描述产品对象的行为特征. 具体产品类就是某一具体的对象. 静态工厂类有一个静态的方法,含有判断逻辑,决定要创建哪一种具体的产品对象. 其设计模式如下: 抽象产品类  IProduct package org.test.design.sf; public inte

深入理解JavaScript系列(29):设计模式之装饰者模式详解

介绍 装饰者提供比继承更有弹性的替代方案. 装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数). 装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的. 正文 那么装饰者模式有什么好处呢?前面说了,装饰者是一种实现继承的替代方案.当脚本运行时,在子类中增加行为会影响原有类所有的实例,而装饰者却不然.取而代之的是它能给不同对象各自添加新行为.如下代码所示: 复制代码 代码如下: //需

JAVA设计模式之调停者模式详解

在阎宏博士的<JAVA与模式>一书中开头是这样描述调停者(Mediator)模式的: 调停者模式是对象的行为模式.调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显引用.从而使它们可以较松散地耦合.当这些对象中的某些对象之间的相互作用发生改变时,不会立即影响到其他的一些对象之间的相互作用.从而保证这些相互作用可以彼此独立地变化. 为什么需要调停者 如下图所示,这个示意图中有大量的对象,这些对象既会影响别的对象,又会被别的对象所影响,因此常常叫做同事(Colleague)对象.这

JAVA设计模式之责任链模式详解

在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求.发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任. 从击鼓传花谈起 击鼓传花是一种热闹而又紧张的饮酒游戏.在酒宴上宾客依次坐定位置,由一人击鼓,击鼓

java设计模式之简单工厂模式详解

简单工厂模式:由一个工厂对象决定创建出哪一种类的实例. 1.抽象类 public abstract class People { public abstract void doSth(); } 2.具体类 public class Man extends People{ @Override public void doSth() { System.out.println("I'm a man,I'm coding."); } } 3.具体类 public class Girl exte

浅谈Java设计模式系列-装饰器模式

一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是单方,和代理模式相同,而且目标必须是抽象的. 而实际上,装饰器模式和代理模式的实现方式基本一致,只在目标的存在上有些差别,这个后面我们具体讲述. 二.初步分析 上面提到了两点: 涉及的是单方 目标是抽象的 我们来想一下,所谓单方主要指的是在整个装饰器模式中不存在双方调用,要解决的也不是双方调用的问题,而是解决单方提供对外服务的问题,这个单方在自行对外提供服务时,功能不足,或

Java之dao模式详解及代码示例

什么是dao模式? DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作.在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中.用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法.在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储.DAO模式实

php设计模式之简单工厂模式详解

本文以实例形式较为详细的介绍了PHP设计模式的简单工厂模式,对于进行PHP程序设计来说有很好的借鉴作用.具体如下: 一.概念 简单工厂模式 [静态工厂方法模式](Static Factory Method) 是类的创建模式 工厂模式的几种形态: 1.简单工厂模式(Simple Factory)又叫做 静态工厂方法模式(Static Factory Method) 2.工厂方法模式(Factory Method)又叫做 多态性工厂模式(Polymorphic Factory) 3.抽象工厂模式(A