设计模式の装饰者&组合&外观模式

文章目录


前言

本篇是关于设计模式中装饰者模式、组合模式、以及外观模式的学习笔记。


一、装饰者模式

装饰者模式是一种结构型设计模式,在不改变对象接口的情况下,动态地添加额外的功能到对象中。通过创建一个装饰类来包裹原始类的实例,并在保持原有接口不变的基础上,扩展其行为。

通常包含了以下角色:

  1. 组件接口:定义一个对象的接口,可以是抽象类或接口,通常是被装饰者和具体装饰器实现的基础。
  2. 具体组件:实现了组件接口的具体类,是被装饰的对象,提供了核心功能。
  3. 装饰器类:实现了组件接口并持有一个组件对象的引用(通常是组件接口类型)。装饰器类的职责是扩展修改核心对象的功能。
  4. 具体装饰器:继承自装饰器类,添加具体的功能或行为。

举一个在生活中的案例,假设咖啡有不同的品种,比如美式,拿铁等,每个品种又有不同的做法,可以加冰块,糖,牛奶等。如果需要下订单,则组合的方式非常多,无法一一列举。

利用装饰者模式,即可以创建一个组件接口,然后编写具体的实现:

java 复制代码
/**
 * 组件接口
 */
public interface Coffee {

    String getDescription();

    double cost();

}
java 复制代码
/**
 * 某个品种的咖啡
 */
class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple Coffee";
    }

    @Override
    public double cost() {
        return 5.0; // 咖啡的价格
    }
}

如果这些咖啡有不同的做法,则可以编写一个装饰器类:

java 复制代码
/**
 * 装饰器,持有咖啡的对象,也实现咖啡接口
 */
public class CoffeeDecorator implements Coffee{

    protected Coffee coffee;

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

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public double cost() {
        return coffee.cost();
    }

具体的做法,实现装饰器类:

java 复制代码
public class IceDecorator extends CoffeeDecorator{

    public IceDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + "冰块";
    }

    @Override
    public double cost() {
        return coffee.cost() + 1.0; // 咖啡费用 + 冰块费用
    }
}
java 复制代码
public  class MilkDecorator extends CoffeeDecorator {

    public MilkDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }

    @Override
    public double cost() {
        return coffee.cost() + 1.5; // 咖啡费用 + 牛奶的附加费用
    }
}

用户在下单时即可进行组合:

java 复制代码
public class Client {
    public static void main(String[] args) {
        Coffee coffee = new SimpleCoffee();

        System.out.println("coffee.getDescription() = " + coffee.getDescription());
        System.out.println("coffee.cost() = " + coffee.cost());

        coffee = new IceDecorator(coffee);//相当于为coffee包装了一种做法

        System.out.println("coffee.getDescription() = " + coffee.getDescription());
        System.out.println("coffee.cost() = " + coffee.cost());
    }
}

coffee.getDescription() = Simple Coffee

coffee.cost() = 5.0

coffee.getDescription() = Simple Coffee冰块

coffee.cost() = 6.0

虽然装饰器模式和桥接模式都将某个维度抽象的接口组合进了另一个维度的类中,但是还是有区别的,装饰者模式着重于扩展功能,通过包装已有对象来增强功能。而桥接模式着重于解耦抽象与实现,通过引入桥接类来避免抽象和实现的紧耦合。

二、组合模式

组合模式是一种结构型设计模式,将对象组合成树形结构以表示"部分-整体"的层次结构,它的主要角色有:

  1. 组件接口:定义了叶子对象和容器对象的共同接口。通常是一个抽象类或接口,声明所有子类必须实现的方法,例如 add()、remove() 和 getChild() 等。
  2. 叶子对象:是树的最基本单元,不再包含其他子对象。叶子对象实现了组件接口,但没有子节点。
  3. 容器对象:继承了组件接口,并可以包含多个子组件(可以是叶子对象或其他容器对象)。容器对象实现了对子组件的操作。

例如用一个案例表示操作系统中的文件体系。一个盘中可以有多个文件夹,一个文件夹中有多个文件,总体是一个树形结构。定义一个组件接口,其中showDetails作为抽象方法强制所有子类重写,而最底层的文件是没有增加或删除功能的,所以可以不用强制重写。

java 复制代码
public abstract class FileSystemComponent {
  
    abstract void showDetails();

    void add() {
        throw new UnsupportedOperationException();
    }

    void remove() {
        throw new UnsupportedOperationException();
    }

}

再定义一个叶子对象,即文件:

java 复制代码
public class File extends FileSystemComponent{

    protected String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void showDetails() {
        System.out.println(name + "文件");
    }
}

然后是容器对象,即文件夹

java 复制代码
public class Folder extends FileSystemComponent{

    protected List<FileSystemComponent> fileSystemComponents = new ArrayList<>();

    protected String name;

    @Override
    void add(FileSystemComponent fileSystemComponent) {
        fileSystemComponents.add(fileSystemComponent);
    }

    @Override
    void remove(FileSystemComponent fileSystemComponent) {
        fileSystemComponents.remove(fileSystemComponent);
    }

    @Override
    public void showDetails() {
        System.out.println("文件夹"+name+"下有:");
        for (FileSystemComponent fileSystemComponent : fileSystemComponents) {
            System.out.println(fileSystemComponent+"文件");
        }
    }
}

创建测试对象:

java 复制代码
public class Client {
    public static void main(String[] args) {
        //创建文件夹1
        Folder folder = new Folder();
        folder.name = "文件夹1";

        //创建文件夹1中的a文件
        File a = new File();
        a.setName("a.txt");
        //创建文件夹1中的b文件
        File b = new File();
        b.setName("b.txt");
        //将a和b放入文件夹1
        folder.add(a);
        folder.add(b);

        //创建文件夹2
        Folder folder2 = new Folder();
        folder2.name = "文件夹2";
        //创建文件夹2中的c文件
        File c = new File();
        c.setName("c.txt");
        //将c放入文件夹2
        folder2.add(c);

        //还可以将文件夹2放入文件夹1中
        folder.add(folder2);

        folder.showDetails();

    }
}

Folder: 文件夹1

a.txt文件

b.txt文件

Folder: 文件夹2

c.txt文件

通过上面的案例,可以看出,组合模式尤其适用于树形结构的表示,以及对单个对象和对象集合(组合)进行相同的操作。

三、外观模式

外观模式是一种结构型设计模式,目的是通过为复杂系统提供一个统一的接口,来简化客户端的操作和理解,将系统的内部复杂性隐藏在外部接口后面,使客户端与复杂子系统的交互变得简单。

例如页面上有一个下单按钮,点击后用户跳转到支付页面,服务器处理下单请求,参数校验,创建订单等操作对于用户是无感知的,用户只需要点击下单按钮,即可一步完成这些操作。

举一个生活中的案例,假设有一个家庭影院系统,由多个组件组成:音响、投影仪、灯光、DVD播放机等。每个组件开启,以及使用的步骤不尽相同。用户不希望分别操作各个组件,而是希望直接一键启动整个系统并播放电影。

用下面几个类模拟一下音响,投影仪,灯光的操作:

java 复制代码
package com.light.facade;

// 音响系统
public class Amplifier {
    public void on() {
        System.out.println("Amplifier is on");
    }

    public void off() {
        System.out.println("Amplifier is off");
    }

    public void setVolume(int level) {
        System.out.println("Setting amplifier volume to " + level);
    }
}

// DVD 播放机
class DVDPlayer {
    public void on() {
        System.out.println("DVD Player is on");
    }

    public void off() {
        System.out.println("DVD Player is off");
    }

    public void play() {
        System.out.println("DVD Player is playing the movie");
    }

    public void stop() {
        System.out.println("DVD Player has stopped the movie");
    }
}

// 投影仪
class Projector {
    public void on() {
        System.out.println("Projector is on");
    }

    public void off() {
        System.out.println("Projector is off");
    }

    public void wideScreenMode() {
        System.out.println("Projector is in widescreen mode");
    }
}

// 屏幕
class Screen {
    public void down() {
        System.out.println("Screen is lowered");
    }

    public void up() {
        System.out.println("Screen is raised");
    }
}

// 灯光系统
class Lights {
    public void on() {
        System.out.println("Lights are on");
    }

    public void off() {
        System.out.println("Lights are off");
    }

    public void dim(int level) {
        System.out.println("Lights are dimmed to " + level + "%");
    }
}

外观类,用于组合对开启,关闭电影系统的各项操作,对外提供统一的方法一键操作。

java 复制代码
/**
 * 外观类
 */
public class HomeTheaterFacade {

    protected Amplifier amplifier;
    protected DVDPlayer dvdPlayer;
    protected Lights lights;
    protected Projector projector;
    protected Screen screen;

    public HomeTheaterFacade() {
    }

    public HomeTheaterFacade(Amplifier amplifier, DVDPlayer dvdPlayer, Lights lights, Projector projector, Screen screen) {
        this.amplifier = amplifier;
        this.dvdPlayer = dvdPlayer;
        this.lights = lights;
        this.projector = projector;
        this.screen = screen;
    }

    /**
     * 一键启动影院系统
     */
    public void oneStepStart(){
        System.out.println("Get ready to watch a movie...");
        lights.dim(10);          // 灯光调暗
        screen.down();          // 拉下屏幕
        projector.on();         // 打开投影仪
        projector.wideScreenMode(); // 设置宽屏模式
        amplifier.on();         // 打开音响
        amplifier.setVolume(5); // 设置音响音量
        dvdPlayer.on();         // 打开DVD播放器
        dvdPlayer.play();       // 播放电影
    }

    /**
     * 一键关闭影院系统
     */
    public void oneStepEnd() {
        System.out.println("Shutting down movie theater...");
        lights.on();            // 打开灯光
        screen.up();            // 升起屏幕
        projector.off();        // 关闭投影仪
        amplifier.off();        // 关闭音响
        dvdPlayer.stop();       // 停止播放
        dvdPlayer.off();        // 关闭DVD播放器
    }
}

用户一键启动,关闭:

java 复制代码
public class Client {
    public static void main(String[] args) {
        HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(
                new Amplifier(),
                new DVDPlayer(),
                new Lights(),
                new Projector(),
                new Screen()
        );

        homeTheaterFacade.oneStepStart();
        System.out.println("*********************");
        homeTheaterFacade.oneStepEnd();
    }
}

Get ready to watch a movie...

Lights are dimmed to 10%

Screen is lowered

Projector is on

Projector is in widescreen mode

Amplifier is on

Setting amplifier volume to 5

DVD Player is on

DVD Player is playing the movie


Shutting down movie theater...

Lights are on

Screen is raised

Projector is off

Amplifier is off

DVD Player has stopped the movie

DVD Player is off

通过上面的案例,可以看出,外观类负责将各个底层组件所需要用到的功能进行组合,对于用户屏蔽了具体的细节,用户无需对每个组件单独进行操作,简化了客户端与多个子系统的交互。

外观模式非常适合以下几种情况:

  • 当系统中的子系统过于复杂,且需要简化客户端与子系统的交互。
  • 当希望将客户端与子系统解耦,使得客户端只与一个简单的接口交互。
  • 当需要协调多个子系统的工作,并提供一个统一的访问点。
  • 当使用第三方库或框架时,需要为其提供一个简化的接口。
  • 当系统中有跨层次、跨模块的功能交互时,需要一个简化的协调接口。

相关推荐
银河麒麟操作系统16 分钟前
【银河麒麟高级服务器操作系统】有关dd及cp测试差异的现象分析详解
java·linux·运维·服务器·前端·网络
机械&编程攻城狮(好哥)16 分钟前
伺服电机控制驱动器选择
python·modbus tcp·驱动器·伺服电机驱动·canable·modbus rtu
Algorithm_Engineer_40 分钟前
机器学习中常用的降维方法-主成分分析法(PCA)
python·机器学习
最爱番茄味41 分钟前
Python之字符串基础篇
python
不安分的小女孩42 分钟前
猫狗识别(PyTorch)
python
C182981825752 小时前
rabbitMq举例
java·rabbitmq·java-rabbitmq
银氨溶液2 小时前
RabbitMQ实现消息发送接收——实战篇(路由模式)
java·开发语言·后端·消息队列·rabbitmq·消息分发
南宫生2 小时前
力扣-图论-13【算法学习day.63】
java·学习·算法·leetcode·图论
爱敲代码的小冰3 小时前
spring boot 过滤器
java·spring boot·后端
CircleMouse3 小时前
IDEA 2024 版本配置热部署
java·ide·intellij-idea