Java 设计模式之装饰者模式

2023-05-16

一、了解装饰者模式

1.1 什么是装饰者模式

装饰者模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对象。

所以装饰者可以动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。

1.2 装饰者模式组成结构

  • 抽象构件 (Component):给出抽象接口或抽象类,以规范准备接收附加功能的对象。
  • 具体构件 (ConcreteComponent):定义将要接收附加功能的类。
  • 抽象装饰 (Decorator):装饰者共同要实现的接口,也可以是抽象类。
  • 具体装饰 (ConcreteDecorator):持有一个 Component 对象,负责给构件对象“贴上”附加的功能。

1.3 装饰者模式 UML 图解

 

1.4 装饰者模式应用场景

  • 需要扩展一个类的功能,或给一个类添加附加职责。
  • 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
  • 当不能采用生成子类的方法进行扩充时。可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。

1.5 装饰者模式特点

  • 装饰者对象和具体构件有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
  • 装饰者对象包含一个具体构件的引用(reference)。
  • 装饰者对象接受所有来自客户端的请求。它把这些请求转发给具体构件。
  • 装饰者对象可以在转发这些请求以前或以后动态增加一些功能。

二、装饰者模式具体应用

2.1 问题描述

星巴兹咖啡订单系统:星巴兹店提供了各式各样的咖啡,以及各种咖啡调料。为了适应饮料需求供应,所以让你设计一个更新订单系统。

2.2 使用继承


在购买咖啡时,可以要求在咖啡中添加各种调料,例如:豆浆 (Soy)、摩卡 (Mocha)、奶泡等。由于各种调料的价格不相同,所以订单系统必须要考虑这些因素。于是就有了下面的尝试

2.3 继承类图

 

这还只是列出了一部分的类,这简直是类爆炸,可以看出这样做显然是不行的。所以我们要慎用继承,尽量用组合和委托。

2.4 装饰者模式登场

装饰者模式涉及到的一个重要的设计原则 (当然还涉及到了其他的设计原则,比如多用组合,少用继承等):类应该对扩展开放,对修改关闭。

在设计过程中,我们允许类容易扩展,在不修改原有代码的情况下,就可以扩展新的行为。这样的设计具有弹性可以应对改变,可以接受新的功能来应对新的需求。

将装饰者模式应用到问题中去:假如我们想要摩卡和豆浆深焙咖啡,那么,要做的是:

  1. 拿一个深焙咖啡 (DarkRoast) 对象
  2. 以摩卡 (Mocha) 装饰它
  3. 以豆浆 (Soy) 装饰它
  4. 调用 cost() 方法,并依赖委托将调料的价钱加上去

 

(1) 装饰者模式设计图

(2)代码实现

饮料 Beverage 抽象类 (抽象构件)

package com.jas.decorator;

public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

浓咖啡 Espresso 类 (具体构件)

​
package com.jas.decorator;

public class Espresso extends Beverage {
    public Espresso(){
        description = "Espresso ";
    }
    
    @Override
    public double cost() {
        return 1.99;
    }
}

黑咖啡 HouseBlend 类 (具体构件)

​
package com.jas.decorator;

public class HouseBlend extends Beverage {
    public HouseBlend(){
        description = "House Blend Coffee ";
    }
    
    @Override
    public double cost() {
        return 0.80;
    }
}

调料 CondimentDecorator 抽象类 (抽象装饰构件)

package com.jas.decorator;

public abstract class CondimentDecorator extends Beverage{
    @Override
    public abstract String getDescription();
}

摩卡 Mocha 类 (具体装饰构件)

package com.jas.decorator;

public class Mocha extends CondimentDecorator {
    private Beverage beverage = null;    //用一个实例变量来记录饮料,也就是被装饰者
    
    public Mocha(Beverage beverage){
        this.beverage = beverage;    //通过构造函数将被装饰者实例化
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha ";    //用来加上调料,一起描述饮料
    }

    @Override
    public double cost() {
        return 0.2 + beverage.cost();    //计算摩卡饮料的价钱,为摩卡价钱 + 饮料价钱
    }
}

豆浆 Soy 类 (具体装饰构件)

package com.jas.decorator;

public class Soy extends CondimentDecorator {
    private Beverage beverage = null;
    
    public Soy(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Soy ";
    }

    @Override
    public double cost() {
        return 0.1 + beverage.cost();
    }
}

测试代码 StarbuzzCoffee 类

package com.jas.decorator;

public class StarbuzzCoffee {
    public static void main(String[] args) {
        
        //简单要一杯浓咖啡
        Beverage beverage1 = new Espresso();
        System.out.println(beverage1.getDescription() + "$" +  beverage1.cost());
        
        //两份摩卡加一份豆浆的浓咖啡
        Beverage beverage2 = new Espresso();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Soy(beverage2);
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
        
        //一份摩卡加一份豆浆的黑咖啡
        Beverage beverage3 = new HouseBlend();
        beverage3 = new Mocha(beverage3);
        beverage3 = new Soy(beverage3);
        System.out.println(beverage3.getDescription() + "$" +  beverage3.cost());
    }
}

    /**
     * 输出
     * Espresso $1.99
     * Espresso , Mocha , Mocha , Soy $2.49
     * House Blend Coffee , Mocha , Soy $1.1
     */

2.5 装饰者模式问题总结

  • 装饰者与被装饰对象有相同的超类型 (DarkRoast 与装饰类 Mocha 和 Soy 都继承自 Beverage(饮料))。
  • 可以使用一个或多个装饰对象包装一个对象。
  • 因为装饰者与被装饰者具有相同的超类型,所以在任何需要原始对象的情况下,都可以用装饰过的对象去代替它。
  • 装饰者可以在所委托被装饰者的行为之前与之后,加上自己的行为,以达到特定的目的。
  • 对象可以在任何时候被装饰,所以在运行时动态地、不限量地用你喜欢的装饰者去装饰对象。

三、真实世界的装饰者 Java I/O

3.1 了解 Java I/O 装饰者模式

在了解了装饰者模式之后,I/O 相关的类对你来说就更有意义了,因为这其中很多类都是装饰者。比如下面相关的类

 

装饰 I/O 类

 

3.2 自定义 Java I/O 装饰者

问题描述:读取文件,把输入流内的所有大写字符转为小写。

装饰者 LowerCaseInputStream 类

package com.jas.decorator;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 扩展 FilterInputStream,这是所有 InputStream 的抽象装饰者
 */
public class LowerCaseInputStream extends FilterInputStream {
    
    public LowerCaseInputStream(InputStream inputStream){
        super(inputStream);
    }
    
    @Override
    public int read() throws IOException{
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }
    
    @Override
    public int read(byte[] bytes, int offset, int len) throws IOException{
        int result = super.read(bytes, offset, len);
        for (int i = offset; i < offset + result; i++) {
            bytes[i] = (byte) Character.toLowerCase((char)bytes[i]);
        }
        return result;
    }
}

测试 InputTest 类

package com.jas.decorator;

import java.io.*;

public class InputTest {
    public static void main(String[] args) {
        
        int c = 0;
        InputStream in = null;

        try {
            //设置 FileInputStream ,先用 BufferedInputStream 装饰它,再用 LowerCaseInputStream 进行装饰
            in = new LowerCaseInputStream(
                    new BufferedInputStream(
                            new FileInputStream("test.txt")));
            while ((c = in.read()) >= 0){
                System.out.print((char)c);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

     /**在文件中为“HELLO WORLD”
     *
     * 输出
     * hello world
     */

四、装饰者模式总结

4.1 装饰者模式的优缺点

优点

  1. Decorator 模式与继承关系的目的都是要扩展对象的功能,但是 Decorator 可以提供比继承更多的灵活性。
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计者可以创造出很多不同行为的组合。

缺点

  1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
  2. 装饰模式会导致设计中出现许多小类 (I/O 类中就是这样),如果过度使用,会使程序变得很复杂。
  3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 设计模式之装饰者模式 的相关文章

随机推荐

  • MacOs “无法打开***,因为无法验证开发者...”

    在终端执行如下指令 sudo spctl master disable 执行完上面指令后 xff0c 在 安全性与隐私 设置的 允许从以下位置下载的App 中会新增一个任何来源 xff0c 如下面的对比图 xff0c 然后应用在运行中就不会
  • mac编译android源码-创建磁盘映像

    因为mac默认的磁盘环境是不区分大小的 xff0c 而git并不支持此类文件系统 xff0c 所以我们需要创建我们所需要的磁盘映像用来存放下载的源码 首先你需要找在mac上的磁盘工具 xff0c 一般是在应用程序列表 其他文件夹里面 2 如
  • android源码编译-如何在Mac中卸载openjdk15

    说明 之前在mac上使用intellij idea时 xff0c 由于没有在Mac上安装过jdk xff0c 所以就在intellij idea中下载了openjdk15版本 后来觉得想要换一个旧点的版本 xff0c 就想卸载了openjd
  • Mac OS查看和设置JAVA_HOME

    下载java https www java com zh CN download 1 查看JAVA版本 打开Mac电脑 xff0c 查看JAVA版本 xff0c 打开终端Terminal xff0c 通过命令行查看笔者的java版本 xff
  • Android源码编译–jdk版本查询

    2 1 Android源码所需JDK版本 根参考资料 1 的说明 xff0c 在android src build core main mk中对jdk的版本进行查询 xff0c 以确定当前系统是否安装了特定版本的jdk xff0c 因此可以
  • android源码编译 ninja: build stopped: subcommand failed.

    接着编译 make j8 线程加多少个具体看机器配置 xff0c 问题也最可能是这一步骤引起的 xff0c 如果是虚拟机的话 xff0c 建议不要加线程 xff0c 直接使用make执行
  • Ubuntu环境下完美安装python模块numpy,scipy,matplotlib

    不同的ubuntu版本安装过这三个模块几次了 xff0c 然而总是出现各种问题 xff0c 最近一次是在ubuntu 16 04 LTS server版本安装的 xff0c 总的来说安装的比较顺利 先把pip安装好 sudo apt get
  • prebuilts/misc/darwin-x86/bison/bison: Bad CPU type in executable

    方案一 cd external bison touch patch high sierra patch vim patch high sierra patch With format string strictness High Sierr
  • android源码编译 坑

    bash lunch command not found 先调用 build envsetup sh 再执行 lunch Can not find SDK Can not find SDK 10 6 at Developer SDKs Ma
  • 获取当前MacOSX SDK

    xcrun show sdk path 打印出 Library Developer CommandLineTools SDKs MacOSX sdk xcrun show sdk version 打印出 10 15 4 xcode sele
  • Mac OS10.12 编译Android源码8.1

    内容 介绍mac os10 12拉取android源码 xff0c 并且编译后 xff0c 刷入手机的过程 下载的rom是android 8 1 xff0c 手机是pixel 准备工作 硬盘大小 本人Mac磁盘空间只有256GB xff0c
  • android源码 xcode版本,【Android】AOSP源码下载及编译 for mac

    本文记录了AOSP在Mac系统上下载和编译的过程 采用的系统是 macOS 10 13 1 所使用的AOSP分支是 android 8 1 0 r7 系统预留空间 大于200G 一 环境配置 环境配置 xff0c 官网给出了非常全的教程 x
  • (Android 9.0)Activity启动流程源码分析

    前言 熟悉Activity的启动流程和运行原理是一个合格的应用开发人员所应该具备的基本素质 xff0c 其重要程度就不多做描述了 同时 xff0c 知识栈应该不断的更新 xff0c 最新发布的Android 9 0版本相较于之前的几个版本也
  • Lifecycle 源码详解

    Lifecycle 是 Jetpack 整个家族体系内最为基础的内容之一 xff0c 正是因为有了 Lifecycle 的存在 xff0c 使得如今开发者搭建依赖于生命周期变化的业务逻辑变得简单高效了许多 xff0c 使得我们可以用一种统一
  • git常用命令

    1 拉取远程所有分支 git clone xxx git branch r grep v 39 gt 39 while read remote do git branch track 34 remote origin 34 34 remot
  • Android应用启动流程分析

    1 前言 网上看过很多Activity启动过程的源码解析 xff0c 很多文章会贴上一大段代码 xff0c 然后从startActivity 函数开始深究整个源码的调用栈 个人感觉这类文章代码细节太多 xff0c 反而容易迷失在源码调用之中
  • 从一个分支cherry-pick多个commit到其他分支

    在branch1开发 xff0c 进行多个提交 xff0c 这是切换到branch2 xff0c 想把之前branch1分支提交的commit都 复制 过来 xff0c 怎么办 xff1f 单个commit只需要git cherry pic
  • IntWritable详解

    1 Hadoop数据类型如下图 xff1a 由上图的Writable层次结构图可以看到绝大多数的数据类型都实现了Writable WritableComparable接口 xff0c 在此先分析一下这两个接口情况 自顶下下逐步分析 Writ
  • 线程池源码剖析

    线程池 xff08 英语 xff1a thread pool xff09 xff1a 一种线程使用模式 线程过多会带来调度开销 xff0c 进而影响缓存局部性和整体性能 而线程池维护着多个线程 xff0c 等待着监督管理者分配可并发执行的任
  • Java 设计模式之装饰者模式

    一 了解装饰者模式 1 1 什么是装饰者模式 装饰者模式指的是在不必改变原类文件和使用继承的情况下 xff0c 动态地扩展一个对象的功能 它是通过创建一个包装对象 xff0c 也就是装饰者来包裹真实的对象 所以装饰者可以动态地将责任附加到对