设计模式-装饰模式

509 阅读6分钟

# 装饰模式

1.简介

装饰模式是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

当你需要更改一个对象的行为的时候,第一个想法可能就是去扩展它的类。但是继承可能会导致一些问题:

  • 继承是静态的,你无法在运行的时候去更改已有对象的行为,只能用当前继承的子类创建的对象来替代父类对象。
  • 子类只有一个父类,在大多数编程语言不允许一个类去继承多个类。

所以通常使用的方式是聚合(组合),而不是继承。它们的工作方式很相似,一个对象包含了另一个对象的引用,并将部分工作委派给引用对象。这种方式其实是很多设计模式的关键。

在装饰器模式中,其主要核心是使用一个 封装器 对象, 运用组合的方式和目标对象相连接,包含和目标对象一样的方法,将一些方法委派给目标对象,但特别的是它可以在这前后进行处理,从而达到改变最终结果的目的。

直白的描述可能会比较抽象,下面我们看一下UML图。

2.UML图

装饰模式UML.png

  1. 部件 (Component) 声明封装器和被封装对象的公用接口。
  2. 具体部件 (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。
  3. 基础装饰 (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。
  4. 具体装饰类 (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。
  5. 客户端 (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。

3. 代码实现

eg:假设我们有一个类原本只负责读取和写入纯文本数据,现在需要在其中添加新的行为,加密解密,压缩解压。

首先定义一个读取和写入操作的通用数据接口:

package com.gs.designmodel.decoratemodel.filedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-17 13:56
 * 装饰模式:
 *  定义了读取和写入操作的通用数据接口
 **/
public interface DataSource{

    /**
     * 写数据
     * @param data
     */
    void writeData(String data);

    /**
     * 读数据
     * @return
     */
    String readData();
}

再定义一个简单的数据读写器(通用数据接口的具体实现):

package com.gs.designmodel.decoratemodel.filedemo;

import java.io.*;
import java.nio.file.Files;
import java.util.Objects;

/**
 * @author: Gaos
 * @Date: 2023-01-17 14:04
 *
 * 简单的数据读写器(通用数据接口的具体实现)
 **/
public class FileDataSource implements DataSource{

    private String name;

    public FileDataSource(String name) {
        this.name = name;
    }

    @Override
    public void writeData(String data) {
        File file = new File(name);
        try (OutputStream fos = Files.newOutputStream(file.toPath())) {
            fos.write(data.getBytes(), 0, data.length());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    @Override
    public String readData() {
        char[] buffer = null;
        File file = new File(name);
        try (FileReader reader = new FileReader(file)) {
            buffer = new char[(int) file.length()];
            reader.read(buffer);
        } catch (IOException e) {
            System.out.println(e.getMessage());;
        }
        return new String(Objects.requireNonNull(buffer));
    }
}

定义抽象基础装饰,这里的关键是实现通用数据接口,使其具有相同的规范,同时持有其基类的对象引用,方便后面具体装饰类调用其方法。

package com.gs.designmodel.decoratemodel.filedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-17 14:18
 *
 * 抽象基础装饰(实现通用数据接口,使其具有相同的规则)
 **/
public class DataSourceDecorator implements DataSource{

    /**
     * 持有其引用
     */
    private DataSource wrappee;

    public DataSourceDecorator(DataSource wrappee) {
        this.wrappee = wrappee;
    }

    @Override
    public void writeData(String data) {
        wrappee.writeData(data);
    }

    @Override
    public String readData() {
        return wrappee.readData();
    }
}

具体的加密装饰类:

package com.gs.designmodel.decoratemodel.filedemo;

import java.util.Base64;

/**
 * @author: Gaos
 * @Date: 2023-01-17 14:27
 *
 * 加密装饰
 **/
public class EncryptionDecorator extends DataSourceDecorator{

    public EncryptionDecorator(DataSource wrappee) {
        super(wrappee);
    }

    @Override
    public void writeData(String data) {
        super.writeData(encode(data));
    }

    @Override
    public String readData() {
        return decode(super.readData());
    }

    /**
     * 加密方法
     * @param data
     * @return
     */
    private String encode(String data) {
        byte[] result = data.getBytes();
        for (int i = 0; i < result.length; i++) {
            result[i] += (byte) 1;
        }
        return Base64.getEncoder().encodeToString(result);
    }

    private String decode(String data) {
        byte[] result = Base64.getDecoder().decode(data);
        for (int i = 0; i < result.length; i++) {
            result[i] -= (byte) 1;
        }
        return new String(result);
    }
}

具体的压缩装饰类:

package com.gs.designmodel.decoratemodel.filedemo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

/**
 * @author: Gaos
 * @Date: 2023-01-17 14:35
 *
 * 压缩装饰
 **/
public class CompressionDecorator extends DataSourceDecorator{

    private int compLevel = 6;

    public CompressionDecorator(DataSource wrappee) {
        super(wrappee);
    }

    public int getCompLevel() {
        return compLevel;
    }

    public void setCompLevel(int compLevel) {
        this.compLevel = compLevel;
    }

    @Override
    public void writeData(String data) {
        super.writeData(compress(data));
    }

    @Override
    public String readData() {
        return decompress(super.readData());
    }

    private String compress(String stringData) {
        byte[] data = stringData.getBytes();
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
            DeflaterOutputStream dos = new DeflaterOutputStream(bout, new Deflater(compLevel));
            dos.write(data);
            dos.close();
            bout.close();
            return Base64.getEncoder().encodeToString(bout.toByteArray());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String decompress(String stringData) {
        byte[] data = Base64.getDecoder().decode(stringData);
        try {
            InputStream in = new ByteArrayInputStream(data);
            InflaterInputStream iin = new InflaterInputStream(in);
            ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
            int b;
            while ((b = iin.read()) != -1) {
                bout.write(b);
            }
            in.close();
            iin.close();
            bout.close();
            return bout.toString();
        } catch (IOException ex) {
            return null;
        }
    }
}

测试代码:

package com.gs.designmodel.decoratemodel.filedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-17 14:50
 **/
public class DemoTest {
    public static void main(String[] args) {
        String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
        DataSourceDecorator encoded = new CompressionDecorator(
                new EncryptionDecorator(
                        new FileDataSource("/Users/gaosheng/test")));
        encoded.writeData(salaryRecords);
        DataSource plain = new FileDataSource("/Users/gaosheng/test");

        System.out.println("- Input ----------------");
        System.out.println(salaryRecords);
        System.out.println("- Encoded --------------");
        System.out.println(plain.readData());
        System.out.println("- Decoded --------------");
        System.out.println(encoded.readData());
    }
}

测试结果:

- Input ----------------
Name,Salary
John Smith,100000
Steven Jobs,912000
- Encoded --------------
Zkt7e1Q5eU8yUm1Qe0ZsdHJ2VXp6dDBKVnhrUHtUe0sxRUYxQkJIdjVLTVZ0dVI5Q2IwOXFISmVUMU5rcENCQmdxRlByaD4+
- Decoded --------------
Name,Salary
John Smith,100000
Steven Jobs,912000

eg:还有一个例子觉得挺有意思的,这里也一并列出。

设计游戏的装备系统,基本要求,要可以计算出每种装备在镶嵌了各种宝石后的攻击力和描述:

具体需求:

1、武器(攻击力20) 、戒指(攻击力5)、护腕(攻击力5)、鞋子(攻击力5)

2、蓝宝石(攻击力5/颗)、黄宝石(攻击力10/颗)、红宝石(攻击力15/颗)

3、每个装备可以随意镶嵌3颗

首先是装备的基类:

package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:41
 *
 * 装备接口
 **/
public interface IEquip {

    /**
     * 计算攻击力
     * @return 攻击力
     */
    int calculateAttack();


    /**
     * 装备的描述
     * @return 装备描述
     */
    String description();

}

然后分别是相应的装备(武器、戒指、护腕、鞋子):

package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:44
 *
 * 武器--->攻击力20
 **/
public class ArmEquip implements IEquip{

    @Override
    public int calculateAttack() {
        return 20;
    }

    @Override
    public String description() {
        return "屠龙刀";
    }
}
package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:46
 *
 * 戒指---->攻击力5
 **/
public class RingEquip implements IEquip{
    @Override
    public int calculateAttack() {
        return 5;
    }

    @Override
    public String description() {
        return "魔戒";
    }
}
package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:46
 *
 * 护腕---->攻击力5
 **/
public class WristEquip implements IEquip{

    @Override
    public int calculateAttack() {
        return 5;
    }

    @Override
    public String description() {
        return "平凡无奇的护腕";
    }
}
package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:47
 *
 * 鞋子---->攻击力5
 **/
public class ShoeEquip implements IEquip{
    @Override
    public int calculateAttack() {
        return 5;
    }

    @Override
    public String description() {
        return "法穿鞋";
    }
}

接下来是装饰品(宝石),首先是基类:

package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:50
 *
 * 装饰品(宝石)的基类接口
 **/
public class IEquipDecorator implements IEquip{

    private IEquip equip;

    public IEquipDecorator(IEquip equip) {
        this.equip = equip;
    }

    @Override
    public int calculateAttack() {
        return equip.calculateAttack();
    }

    @Override
    public String description() {
        return equip.description();
    }
}

接下来是相应的宝石:

package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:51
 *
 * 蓝色宝石装饰品
 * 攻击力+5
 **/
public class BlueGemDecorator extends IEquipDecorator{

    public BlueGemDecorator(IEquip equip) {
        super(equip);
    }

    @Override
    public int calculateAttack() {
        return 5 + super.calculateAttack();
    }

    @Override
    public String description() {
        return super.description() + "-----蓝宝石";
    }
}
package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:53
 *
 * 黄宝石---->攻击力+10
 **/
public class YellowGemDecorator extends IEquipDecorator {


    public YellowGemDecorator(IEquip equip) {
        super(equip);
    }

    @Override
    public int calculateAttack() {
        return 10 + super.calculateAttack();
    }

    @Override
    public String description() {
        return super.description() + "-----黄宝石";
    }
}
package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:55
 *
 * 红宝石装饰品--->攻击力+15
 **/
public class RedGemDecorator extends IEquipDecorator{
    public RedGemDecorator(IEquip equip) {
        super(equip);
    }
    @Override
    public int calculateAttack() {
        return 15 + super.calculateAttack();
    }

    @Override
    public String description() {
        return super.description() + "-----红宝石";
    }
}

测试类:

package com.gs.designmodel.decoratemodel.gamedemo;

/**
 * @author: Gaos
 * @Date: 2023-01-18 09:56
 **/
public class GameTest {
    public static void main(String[] args) {
        // 镶嵌一个红宝石 一个蓝宝石的武器
        IEquip equipFirst = new RedGemDecorator(new BlueGemDecorator(new ArmEquip()));
        System.out.println("攻击力:" + equipFirst.calculateAttack());
        System.out.println("描述:" + equipFirst.description());
        System.out.println("------------");

        // 一个镶嵌红宝石,一个镶嵌蓝宝石,一个镶嵌黄宝石的护腕
        IEquip equipSecond = new RedGemDecorator(new BlueGemDecorator(new YellowGemDecorator(new WristEquip())));
        System.out.println("攻击力:" + equipSecond.calculateAttack());
        System.out.println("描述:" + equipSecond.description());
        System.out.println("------------");
    }
}

结果:

攻击力:40
描述:屠龙刀-----蓝宝石-----红宝石
------------
攻击力:35
描述:平凡无奇的护腕-----黄宝石-----蓝宝石-----红宝石
------------

4.总结

如果你希望在无需修改代码的情况下即可使用对象,且希望在运行时为对象注入额外的行为,你可以使用此模式。或者当继承来扩展行为的方案难以实现的时候,你可以使用装饰模式。

其主要的核心就是你需要先抽象出一个通用的组件接口,并声明相关的方法(如上面的装备类)。

创建装饰类继承装备类(如上面的宝石基类),实现相关的规范,并存储组件接口的引用方便后续调用。

在具体装备子类中,就可以根据我们的需要,去扩展行为方法。(具体的红、蓝宝石,增加攻击力)

参考文章:

refactoringguru.cn/design-patt…

blog.csdn.net/lmj62356579…