Java 装饰者设计模式

装饰者模式

在 java.io 包中,不仅 OutputStream 用到了 Decorator 设计模式,InputStream,Reader,Writer 等都用到了此模式。而作为一个灵活的,可扩展的类库,JDK 中使用了大量的设计模式,比如在 Calendar 的工厂模式,线程池的工厂模式等等。对于 JDK 源码中设计模式的研究不仅能加深对于设计模式的理解,而且还有利于更透彻的了解类库的结构和组成。

模式动机

模式名称:装饰模式 (Decorator)
有时我们希望给某个对象而不是整个类添加一些功能。例如:一个图形用户界面工具箱允许我们对任意一个用户界面组件添加一些新的特性,如增加一个边框;或者增加一些行为,如窗口的滚动。 一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象来添加边框。我们称这个嵌入的对象为装饰器(Decorator)。

这个装饰与它所装饰的组件接口 一致,因此它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作。这种透明性使得我们可以递归嵌套多个装饰,从而可以添加任意多的功能。

模式定义

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

模式结构

image-20210826084706509

  • Component:抽象组件
  • ConcreteComponent:具体组件
  • Decorator:抽象装饰类
  • ConcreteDecoratorA:具体装饰类
  • ConcreteDecoratorB:具体装饰类

案例代码1

定义一个汉堡接口

public interface Hamburger {
    String description();
    double cost();
}

然后,创建一个基础汉堡实现:

public class BasicHamburger implements Hamburger {
    @Override
    public String description() {
        return "Basic hamburger";
    }

    @Override
    public double cost() {
        return 5.0;
    }
}

接下来,创建装饰者抽象类:

public abstract class HamburgerDecorator implements Hamburger {
    protected Hamburger hamburger;

    public HamburgerDecorator(Hamburger hamburger) {
        this.hamburger = hamburger;
    }

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

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

现在,创建一些具体的装饰者来增加额外的特性:

public class CheeseDecorator extends HamburgerDecorator {
    public CheeseDecorator(Hamburger hamburger) {
        super(hamburger);
    }

    @Override
    public String description() {
        return super.description() + ", with cheese";
    }

    @Override
    public double cost() {
        return super.cost() + 1.0;
    }
}

public class LettuceDecorator extends HamburgerDecorator {
    public LettuceDecorator(Hamburger hamburger) {
        super(hamburger);
    }

    @Override
    public String description() {
        return super.description() + ", with lettuce";
    }

    @Override
    public double cost() {
        return super.cost() + 0.5;
    }
}

public class TomatoDecorator extends HamburgerDecorator {
    public TomatoDecorator(Hamburger hamburger) {
        super(hamburger);
    }

    @Override
    public String description() {
        return super.description() + ", with tomato";
    }

    @Override
    public double cost() {
        return super.cost() + 0.5;
    }
}

最后,客户端代码可以组合不同的装饰者来创建具有不同特性的汉堡包:

public class HamburgerTest {
    public static void main(String[] args) {
        Hamburger simpleHamburger = new BasicHamburger();
        System.out.println("Simple hamburger cost: " + simpleHamburger.cost());
        System.out.println("Simple hamburger description: " + simpleHamburger.description());

        Hamburger cheeseHamburger = new CheeseDecorator(simpleHamburger);
        System.out.println("Cheese hamburger cost: " + cheeseHamburger.cost());
        System.out.println("Cheese hamburger description: " + cheeseHamburger.description());

        Hamburger lettuceAndTomatoHamburger = new LettuceDecorator(new TomatoDecorator(cheeseHamburger));
        System.out.println("Lettuce and tomato hamburger cost: " + lettuceAndTomatoHamburger.cost());
        System.out.println("Lettuce and tomato hamburger description: " + lettuceAndTomatoHamburger.description());
    }
}

案例代码2

在实现奶茶的装饰者模式时,可以按照以下步骤进行:

  1. 定义奶茶基类:定义一个奶茶的基类,它可以包含所有奶茶共有的属性和方法,如名称、价格等。

  2. 创建具体奶茶类:基于奶茶基类,创建具体的奶茶类,每个类代表一种特定的奶茶,如珍珠奶茶、草莓奶茶等。

  3. 定义装饰者接口:定义一个装饰者接口,它继承自奶茶基类,并添加一个新的方法,用于添加装饰。

  4. 创建具体装饰者类:根据装饰者的类型,创建具体的装饰者类,如加糖、加冰等。每个装饰者类都应该实现装饰者接口,并在其中实现添加装饰的方法。

  5. 组合装饰者和奶茶:在实际使用时,可以将一个或多个装饰者应用到一个具体的奶茶对象上,以生成新的奶茶对象。

以下是使用装饰者模式实现各种款式奶茶的一个简化示例:

// 奶茶基类
abstract class MilkTea {
    protected String name;
    protected double price;

    public MilkTea(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public abstract double calculateCost();
}

// 具体奶茶类
class PearlMilkTea extends MilkTea {
    public PearlMilkTea() {
        super("珍珠奶茶", 5.0);
    }

    @Override
    public double calculateCost() {
        return this.price;
    }
}

// 装饰者接口
interface Decorator extends MilkTea {
    void addDecoration(MilkTea tea);
}

// 具体装饰者类
class SugarDecorator extends Decorator {
    private double sugarCost = 1.0;

    @Override
    public void addDecoration(MilkTea tea) {
        tea.addDecoration(this);
    }

    @Override
    public double calculateCost() {
        return tea.calculateCost() + sugarCost;
    }
}

// 使用装饰者模式
public class Main {
    public static void main(String[] args) {
        MilkTea tea = new PearlMilkTea();
        Decorator sugar = new SugarDecorator(tea);
        System.out.println(sugar.getName() + " with sugar: " + sugar.calculateCost());
    }
}

注意事项

在使用装饰者模式时,需要注意以下几点:

  • 单一职责原则:确保每个装饰者都有明确的责任,避免一个装饰者承担过多的功能。
  • 开放封闭原则:装饰者应该是对扩展开放的,对修改封闭的,这意味着应该容易添加新的装饰者,而不是修改现有的装饰者。
  • 性能考量:由于装饰者可能会增加额外的对象和方法调用,因此在性能敏感的应用中需要仔细考虑装饰者的使用。

通过以上步骤和注意事项,可以有效地利用装饰者模式来扩展奶茶的功能,同时保持代码的可读性和可维护性。

装饰模式的优缺点

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。

装饰模式使用场景举例

Java的 IO 流框架确实使用了装饰者模式来提高其灵活性和扩展性。在Java IO中,装饰者模式的主要作用是允许开发者通过组合不同的流对象来创建新的流对象,以此来扩展或修改流对象的功能。

在Java IO中,InputStream和OutputStream是抽象组件,而像FileInputStream和FileOutputStream这样的具体组件则提供了基本的读写功能。抽象装饰者如FilterInputStream和FilterOutputStream则允许开发者创建新的流对象,这些新对象可以包装其他流对象,并提供额外的功能。例如,BufferedInputStream就是一个装饰者,它包装了一个InputStream,并为它提供了缓冲的功能,这样可以提高读取效率。

代码举例

你需要一个既可以处理二进制数据,又可以过滤数据(如小写字母变大写字母),还可以缓存数据的文件输出流对象。

class MyFilterOutputStream extends FilterOutputStream {
    public MyFilterOutputStream(OutputStream out) {
        super(out);
    }

    @Override
    public void write(int b) throws IOException {
        b =  Character.toUpperCase((char)b);
        super.write(b);
    }
}

public class MyOutputStreamTest {
    public static void main(String[] args) throws IOException {
        OutputStream out = new MyFilterOutputStream(new BufferedOutputStream(new FileOutputStream("d://1.txt")));
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        String str = scanner.nextLine();
        out.write(str.getBytes());
        out.flush();
    }
}

总的来说,Java IO流框架通过使用装饰者模式,提供了一种灵活且强大的方式来扩展和组合流对象,从而满足了各种复杂的IO需求。

装饰模式适合使用场景

在以下情况下适合使用装饰模式:

  • 在不影响其它对象的情况下,以动态、透明的方式给单个对象添加职责。需要动态地给一个对象增加功能,这些功能可以再动态地被撤销。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义不能继承(final类),或类不能用于生成子类。

注意事项

  • 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
  • 尽量保持具体构件类Component作为一个“轻”类,也就是说不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类对其进行扩展。
posted @ 2026-09-29 22:57  Binge-和时间做朋友  阅读(8)  评论(0)    收藏  举报