设计模式-装饰者模式

图片 1

图片 2

 

定义

  装饰模式的英文原话是:Attach additional responsibilities to an
object dynamically keeping the same interface.Decorators provide a
flexible alternative to subclass for extending
functionality.意思是:动态的给一个对象添加一些额外的职责。就增强功能来说,装饰模式相比于代理模式生成子类更为灵活。

  装饰模式有四种角色:

  抽象构建(Component)角色:该角色用于规范需要装饰的对象(原始对象)。

  具体构建(Concrete
Component)角色:该角色实现抽象构件接口,定义一个需要装饰的原始类。

  装饰角色(Decorator)角色:该角色持有一个构建对象的实例,并定义一个与抽象构建接口一致的接口。

  具体装饰模式(Concrete
Decorator)角色:该角色负责对构建对象进行装饰。

/**
 * 抽象构建
 * 原始对象接口
 */
public interface Component {
    public void operation();
}

/**
 * 具体构建
 * 原始对象类
 */
public class ConcreteComponent implements Component{
    @Override
    public void operation() {
        //业务代码
        System.out.println("原始对象业务代码");
    }
}

/**
 * 装饰类角色
 * 抽象类
 */
public abstract class Decorator implements Component {
    private Component component = null;
    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        this.component.operation();
    }
}

/**
 * 具体修饰
 */
public class ConcreteDecorator extends Decorator {
    public ConcreteDecorator(Component component) {
        super(component);
    }

    //装饰类添加的方法
    private void addmethod(){
        System.out.println("Decorator添加的方法");
    }
    //重写operation方法
    public void operation(){
        this.addmethod();
        super.operation();
    }
}

//调用
public class DecoratorDemo {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component.operation();
        System.out.println("经过装饰----------");
        component = new ConcreteDecorator(component);
        component.operation();
    }
}

源码

概述

一般情况下,当我们想给一个类或对象添加功能的时候,有两种常用的方式:

  1. 继承:通过使用继承,我们可以使子类既能拥有父类的功能,也能实现本身的功能。
  2. 组合:而组合的方式,是在某一个类中包装另一个类的对象,然后通过这个对象引用来决定是否拥有该类的某些功能,我们把这个包装的对象可以称为装饰器
    (Decorator);

由于继承是一种静态的行为,而组合则可以实现动态的往一个类中添加的新的行为。并且就功能而言,组合相比继承更加灵活,这样可以给某个对象而不是整个类添加一些功能。而装饰者模式就是基于组合来实现的:

装饰者模式是一种动态地往一个类中添加新的行为的设计模式。

原文地址:LoveDev

前言

设计模式遍布我们编码的每一个角落,例如JAVA刚入门那会做的第一个窗口程序用的Swing中的ActionsListener类就是观察者模式实现的;以及我们文件操作接触比较多的java.io包内的类有许多就是装饰者模式设计的。如果深入研究过Spring源码的人都会有这个感慨:Spring就是一些设计模式狂魔的作品

设计模式有一个非常重要的面向对象原则:针对接口编程,不针对实现编程
在我们debug排查故障或在写程序的时候多多少少都会有看过Spring MVC中的一些源码,大多数人都会发现Spring的代码是比较难看懂的。那是因为我们大多都在用面向实现编程的思维去思考代码。这样去看一群设计模式狂魔整出来的东西当然是有相当大的难度的。我们需要看懂读懂Spring源码,或者其他开源代码必备的一个良好的功底那就是要熟悉常见的设计模式。

定义:

装饰模式的优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合,即Component类无须知道Decorator类,Decorator类从外部扩展Component类的功能,而Decorator不用知道具体的构建。
  • 装饰模式是继承关系的一个替代方案。装饰类Decorator不管装饰多少层,返回的对象还是Component。
  • 装饰模式可以动态地扩展一个实现类的功能。
装饰者模式简介

  也就是说,通过使用装饰者模式,可以在运行时扩充一个类的功能。大概原理是:增加一个装饰类包装原来的类,包装的方式一般是通过在将原来的对象作为装饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。
  修饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。
  当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味着要为每一种组合创建一个新类。相反,装饰者模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。

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

装饰者模式

什么是装饰者模式?我们来看看度娘的解读

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

我们先从我们所熟悉的java.io包内的装饰者模式实现的类入手。我们来看一个熟悉的对象集合

图片 3

BufferedInputStreamLinerNumberInputStream都是扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。

    在不必改变原类文件和原类使用的继承的情况下,动态地扩展一个对象的功能。

装饰模式的缺点

  多层的装饰比较复杂

结构

我们先通过一张图来看下装饰者模式的结构:

图片 4

Decorator.jpg

图片来源:图说设计模式 – 装饰模式 –
结构图解
通过以上结构,我们可以大概了解到装饰者模式的几个角色:

  1. Component,最基础的底层接口,包含基础的功能方法;
  2. ConcreteComponent
    ,底层接口Component原始的实现,我们要装饰的对象就是这部分,这部分也就是被装饰者;
  3. Decorator,装饰角色,一般情况下该对象是一个抽象类,实现自Component,在该类中一般会有一个指向Component接口的对象,指向被装饰的对象,通过组合的形式对其进行包装;
  4. ConcreteDecorator,具体的装饰角色,继承自Decorator,对具体的被装饰者进行包装,并且可以添加新的功能;

我们先通过简单的代码来看一下:

  1. Component接口:

public interface Component {
    /**
     * 底层基础接口
     */
    void operation();
}
  1. ConcreteComponent实现类:

public class ConcreteComponent implements Component {
    /**
     * 基础接口的实现
     */
    @Override
    public void operation() {
        System.out.println("concrete component");
    }
}
  1. Decorator 装饰角色,抽象类,实现Component:

public abstract class Decorator implements Component {
    /** 维护一个被装饰的对象的引用*/
    protected Component component;

    /**
     * 通过构造方法传入被装饰的对象
     * @param component
     */
    public Decorator(Component component) {
        this.component = component;
    }

    /**
     * 调用被装饰的对象的方法
     */
    @Override
    public void operation() {
        component.operation();
    }
}
  1. ConcreteDecorator 具体装饰角色,继承自Decorator抽象类:

public class ConcreteDecorator extends Decorator {
    /**
     * 自定义自己的属性
     */
    private String addState = "test";

    public ConcreteDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        component.operation();
        System.out.println("addState:" + addState);
    }

    /**
     * 自定义新的方法,实现新的功能
     */
    public void addedBehavior() {
        System.out.println("addState:" + addState);
    }
    // 省略掉get,set方法
}
  1. Main,用于测试:

public class Main {
    public static void main(String[] args) {
        Component component = new ConcreteDecorator(new ConcreteComponent());
        component.operation();
    }
}

装饰模式是一种用于替代继承的技术,通过一种无须定义子类的方式给对象动态增加职责,使用对象间的关联关系替代继承关系

装饰java.io类

图片 5

我们从上边的java.io类中分析确认了装饰者的重要用途:动态的将责任附加到对象上。

    它是通过创建一个包装对象,也就是用装饰来包裹真实的对象来实现。

装饰模式使用场景

  • 需要扩展一个类的功能,或给一个类增加附加功能。
  • 需要动态地给一个对象增加功能,这些功能可以动态地撤销。
  • 需要为一批类进行改装或加装功能。

  装饰模式是对继承的有效补充。单纯使用继承时,在某些情况下回增加很多子类,而且灵活性差,也不容易维护。装饰模式可以替代继承,解决类膨胀的问题,如java基础类库中的与输入输出流相关的类大量使用了装饰模式。

优缺点
  1. 我们先来看下优点:

a.
装饰者模式可以用来代替继承关系,可以动态的扩展一个实现类的功能,且不影响到其他对象;
b. 装饰者模式不管最终装饰了多少层,最终返回的对象还是Component;
c. 遵循设计模式的原则:对扩展开放,对修改关闭。

  1. 有优点就免不了会有缺点,我们再来看下缺点,缺点的话其实就比较明显了:

装饰者模式采用组合的形式比继承灵活,但也比继承相对复杂,如果装饰的层数过多,出现问题排查的时候也会相对困难些;因此,尽量减少装饰类的数量,以便降低系统的复杂度;

图片 6装饰模式

为什么要使用装饰者模式

我们从设计一个简单饮料店说起,饮料店中有原味奶茶,珍珠奶茶,绿茶,抹茶等;调料有糖,牛奶,奶泡等。我们会在第一时间在脑中进行一次类图分析。

图片 7

这种设计类似硬编码形式在后期并不能做很好的扩展,并违背了一个非常重要的面向对象设计原则:类应该对扩展开放,对修改关闭

我们可以看出这种通过继承设计的饮料店是有很大的缺陷:调料价钱出现改变是会使我们更改现有的代码;一但出现新的调料或开发出新的饮料都需对现有代码作比较大的改动。这样的代码可维护性是非常糟糕的。

接着我们用装饰者模式做一次的重新设计

图片 8

当我们组件与装饰者模式组合时,就是再加入新的行为。例如红豆+珍珠=红豆珍珠奶茶,新的茶饮出来了,无需在添加一个新的类。

 

适用场景

装饰者模式的适用场景可以根据它的优点来进行选择:

  1. 当需要在不影响其他对象的情况下,动态的为一个类扩展功能的时候;
  2. 当不能使用继承或者不适合采用继承的方式进行类的扩展和维护时(比如类定义为final类型),可以采用装饰者模式;
  • Component:具体组件类和抽象装饰类共同父类,声明了具体组件中需要实现的方法,它的引入可以使调用者以一致的方式处理未装饰对象和装饰对象,实现调用者的透明操作
  • ConcreteComponent:实现抽象组件类的声明的方法
  • Decorator:用于增加具体组件的职责,它的子类实现具体职责,它持有一个具体组件类的引用,通过该引用可以调用未装饰前的方法,并通过子类扩展该方法
  • ConcreteDecorator:给具体组件类增加新方法

代码实现

饮料抽象类

public abstract class Berverage {//饮料店抽象基类
    public String description;
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public abstract int Cost();
}

调料抽象类

public abstract class CondimentDecorator  extends  Berverage{
    public abstract String getDescription();
}

饮料类

public class GreenTea extends Berverage {

    public GreenTea () {
        description = "this is a cup of green tea ";
    }

    public int Cost() {
        return 5;
    }
}
public class MilkTea extends  Berverage {
    public MilkTea() {
        description = "this is a cup of milk tea";
    }
    public int Cost() {
        return 6;
    }
}

public class PearlsMilkTea extends Berverage {
    public PearlsMilkTea() {
        description = "this is a cup of pearls milk tea  ";
    }
    public int Cost() {
        return 2;
    }
}

调料类

public class Milk extends CondimentDecorator {//牛奶
    Berverage berverage;
    public Milk( Berverage berverage) {
        this.berverage = berverage;
    }

    public String getDescription() {
        return berverage.getDescription()+"+milk";
    }

    public int Cost() {
        return berverage.Cost()+3;
    }
}

测试代码

public class Application {

    public static void main(String[] args) {

        //珍珠奶茶
        Berverage berverage1 = new PearlsMilkTea();
        System.out.println(berverage1.getDescription() + " cost = " + berverage1.Cost());

        //绿茶加牛奶
        Berverage berverage = new GreenTea();
        berverage = new Milk(berverage);
        System.out.println(berverage.getDescription()+"cost = "+berverage.Cost());
    }
}

测试结果

图片 9

角色:

在JAVA I/O流中的应用

  而装饰者模式在Java中应用最广泛最出名的恐怕就是Java中的I/O的API了。如果我们在学习I/O体系前,没有了解过装饰者模式的话,那么由于I/O体系本身复杂的结构,学习的时候或许会有一种很头疼的感觉。我们还是先来看下I/O体系大致结构:

图片 10

I/O体系结构.jpg

注:没有完全展示所有I/O相关的接口。 图片来源:google 图片
我们可以大致把上图分为几层:

  1. Reader、Writer、InputStream、OutputStream,对应装饰者角色中最基础的接口:Component,这里面包含了基础的操作;
  2. FileInputStream,FileOutputStream等第二层没有其他实现类的类,对应于装饰者中被装饰的角色:ConcreteComponent;
  3. FilterReader,FilterOutputStream等,对应于装饰者中的抽象装饰角色:Decorator;
  4. BufferedOutputStream,DataOutputStream等,对应于装饰者中的具体装饰角色:ConcreteDecorator;

我们接下来可以通过一个代码简单看下实现:

public class Main {
    public static void main(String[] args) throws Exception {
        // 1. 定义输入流
        InputStream inputStream = null;
        try {
            // 2. 实例 被装饰者 : 文件读取
            inputStream = new FileInputStream("E://test.txt");
            // 3. 具体装饰角色:缓冲流
            inputStream = new BufferedInputStream(inputStream);
            // 4. 进行操作:开始读文件
            byte[] text = new byte[inputStream.available()];
            inputStream.read(text);
            System.out.println(new String(text));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }
    }
}

当然,装饰者模式在Java中应用的地方还有许多地方,比如最近学习的Mybatis中Executor体系结构:Executor,
BaseExecutor, CachingExecutor等。

图片 11

点题

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

篇幅中讲到了两个重要的面向对象设计原则

  • 针对接口编程,不针对实现编程
  • 类应该对扩展开放,对修改关闭

    抽象构件角色(Project):给出一个接口,以规范准备接收附加责任的对象。

总结

学习了装饰者模式之后,我们来简单回顾总结下:

  1. 装饰者模式可以动态的扩展类的功能,就这点来说,装饰者模式要比使用继承更为灵活,按照GoF设计模式的划分,装饰者模式属于结构型模式的一种。
  2. 装饰者模式遵循了对扩展开放,对修改关闭的设计原则,在不修改原有代码的基础上,通过组合的形式扩展新的功能。
  3. 装饰者模式虽然比继承的形式要灵活,但由于其本身的繁琐性,所以也会相对继承复杂些,如果装饰的层数过多,一旦出现问题,将会提高我们排查问题的难度,所以要尽量减少装饰的层数,以便降低系统的复杂度。

本文参考自:
装饰器模式–继承的另一个选择
维基百科-装饰模式

平时生活中,有很多需要送礼物的时候,一个好礼物更需要好包装来衬托,但是有时候买的礼物只有一个很丑陋的盒子,以装饰模式实现包装礼物的需求:

The last

三人行,必有我师。在给大家分享干货的同时,才疏学浅还望大家大刀予以斧正。也欢迎关注我的掘金或简书,名称为柴码

    具体构件角色(Employe):定义一个将要接收附加责任的类。

Component 类:

    装饰角色(Manager):持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口。

public abstract class Gift { /** * 礼物包装 */ public abstract void packaging();}

    具体装饰角色(ManagerA、ManagerB):负责给构件对象“贴上”附加的责任。

ConcreteComponent 类:

 

public class BirthdayGift extends Gift { @Override public void packaging() { LogUtils.i; }}

示例:

Decorator 类:

公共接口:

public abstract class GiftPackaging extends Gift { private Gift gift; public GiftPackaging(Gift gift) { this.gift = gift; } public void packaging() { gift.packaging(); }}

 

ConcreteDecorator 类:

public interface Person {  
    void eat();  
}
// 简易包装public class SimplePackaging extends GiftPackaging { public SimplePackaging(Gift gift) { super; } @Override public void packaging() { super.packaging(); addColorSheet(); } private void addColorSheet() { LogUtils.i; }}// 奢华包装public class LuxuryPackaging extends GiftPackaging { public LuxuryPackaging(Gift gift) { super; } @Override public void packaging() { super.packaging(); addColorSheet(); addRibbon(); addCard(); addGiftBox(); } private void addColorSheet() { LogUtils.i; } private void addRibbon() { LogUtils.i; } private void addCard() { LogUtils.i; } private void addGiftBox() { LogUtils.i; }}

被装饰对象:

Client 类:

 

// 简易包装的礼物Gift gift = new BirthdayGift();Gift giftPackaging = new SimplePackaging;giftPackaging.packaging();// 奢华包装的礼物Gift gift = new BirthdayGift();Gift giftPackaging = new LuxuryPackaging;giftPackaging.packaging();
public class OldPerson implements Person {  
    @Override  
    public void eat() {  
        System.out.println("吃饭");  
    }  
} 

<h3> 透明装饰模式 </h3>

 

上面实现的装饰模式叫做透明装饰模式,客户端可以完全针对抽象编程,装饰模式的透明性要求客户端不应该将对象类型声明为具体组件类型或者具体装饰类型,需要全部声明为抽象组件类型,对于客户端调用来说,具体组件对象具体装饰对象是一样的,没有任何区别,可以一致处理这些对象,实现透明装饰模式时,要求具体装饰类
operation() 方法覆盖抽象装饰类的 operation()
方法,除了调用具体组件类的 operation() 方法外,还需要调用新增的
addedBehavior() 方法来增加新职责。

装饰对象:

透明模式可以对一个已装饰的对象再进行装饰,获得更复杂,功能更强大的对象。

 

<h3> 半透明装饰模式 </h3>

public class NewPerson implements Person {  
    private OldPerson p;  

    NewPerson(OldPerson p) {  
        this.p = p;  
    }  

    @Override  
    public void eat() {  
        System.out.println("生火");  
        System.out.println("做饭");  
        p.eat();  
        System.out.println("刷碗");   
    }  
}  

有透明装饰模式,就有对应的半透明装饰模式,有时我们需要单独调用新增方法,就不得不把对象声明为具体装饰类型具体组件对象还是可以继续定义为抽象组件类型,这就是半透明装饰模式。

 

还是拿上面的栗子来说,如果包装礼物只想用丝带和彩纸包装或者再加一个礼袋,用半透明模式就会非常方便灵活,直接调用对应的方法就可以了,但是客户端需要区别对待装饰前后的对象

测试:

<h3> 优点 </h3>

 

  • 利用关联关系替代继承关系,更加灵活,不会导致类个数急剧增加
  • 透明装饰模式可以对一个对象进行多次装饰,通过使用不同的具体装饰类的组合,能得到功能更加强大的对象
  • 具体组件类和具体装饰类可以独立变化,根据需求,在不变原来代码得基础上,增加这两个类,很符合“开闭原则”
public class PersonDemo {  
    public static void main(String[] args) {  
        OldPerson old = new OldPerson();  
        //old.eat(); 
        NewPerson np = new NewPerson(old);  
        np.eat();  
    }  
} 

<h3> 缺点 </h3>

        通过例子可以看到,没有改变原来的OldPerson类,同时也没有定义他的子类而实现了Person的扩展,这就是装饰者模式的作用。

  • 既然是更加灵活的解决方法,出错的几率也随之变大,排查错误的困难也跟着变大

 

优点:

        1,使用装饰者模式比使用继承更加灵活,因为它选择通过一种动态的方式来扩展一个对象的功能,在运行时可以选择不同的装饰器,从而实现不同的行为。

        2,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。

        3,具体构件类与具体装饰类可以独立变化,他能是低耦合的。用户可以根据需要来增加新的具体构件类和具体装饰类,在使用时再对其进行各种组合,原有代码无须改变,符合“开闭原则”。

 

缺点:

        1,会产生很多的小对象,增加了系统的复杂性

        2,这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

 

装饰者与适配者模式的区别:

  
1,适配器模式主要用来兼容那些不能在一起工作的类,使他们转化为可以兼容目标接口,虽然也可以实现和装饰者一样的增加新职责,但目的不在此。

        装饰者模式主要是给被装饰者增加新职责的。

  
2,适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。

        装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。

   3,适配器是知道被适配者的详细情况的(就是那个类或那个接口)。

        装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。

 

装饰者和继承的区别:

继承:

  优点:代码结构清晰,而且实现简单

  缺点:对于每一个的需要增强的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。

装饰者:

  优点:内部可以通过多态技术对多个需要增强的类进行增强

      
缺点:需要内部通过多态技术维护需要增强的类的实例。进而使得代码稍微复杂。

 

使用场景:

        1,需要扩展一个类的功能,或给一个类添加附加职责。

        2,需要动态的给一个对象添加功能,这些功能可能不明确或者暂时的,可以随时很方便的动态撤销掉。

        3,需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。

        4.
当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

发表评论

电子邮件地址不会被公开。 必填项已用*标注