装饰者模式是一种结构型设计模式,它允许你在运行时动态地修改对象的行为。在这种模式中,你可以通过将对象包装在一个装饰器对象中来添加新的行为,而不是修改原始对象的代码。
一、实现思路
在Java中,装饰者模式可以通过继承和组合来实现。通过创建一个包装器对象来实现的,该对象包装了原始对象,并在其上添加了新的行为或功能。步骤如下:
- 定义一个抽象组件(Component)类,它是被装饰者和装饰者的公共接口,可以是一个抽象类或接口。
- 定义一个具体组件(ConcreteComponent)类,它是抽象组件的实现类。
- 定义一个抽象装饰者(Decorator)类,它也是抽象组件的子类,它包含一个抽象组件类型的成员变量,以及一个构造方法,用于初始化该成员变量。
- 定义一个具体装饰者(ConcreteDecorator)类,它是抽象装饰者的实现类,它重写了抽象装饰者的方法,并在其中调用了被装饰者的方法,同时还可以添加自己的行为。
二、简单示例
下面是一个简单的示例,演示如何使用装饰者模式来动态地添加新的行为。
首先,我们定义一个接口Component,它是所有组件的基类:
public interface Component {
void operation();
}
然后,我们定义一个具体的组件ConcreteComponent,它实现了Component接口:
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
现在,我们可以定义一个装饰器Decorator,它也实现了Component接口,并且包含一个Component类型的成员变量,用于存储被装饰的对象:
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
注意,Decorator是一个抽象类,它的operation()方法调用了被装饰对象的operation()方法。
接着,我们可以定义具体的装饰器ConcreteDecorator,它继承自Decorator,并且可以添加新的行为:
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("ConcreteDecorator operation");
}
}
最后,我们可以使用装饰器模式来创建一个具有新行为的对象:
Component component = new ConcreteComponent();
component.operation(); // 输出 "ConcreteComponent operation"
Component decoratedComponent = new ConcreteDecorator(component);
decoratedComponent.operation(); // 输出 "ConcreteComponent operation" 和 "ConcreteDecorator operation"
在这个示例中,我们首先创建了一个ConcreteComponent对象,然后使用ConcreteDecorator来装饰它,从而添加了新的行为。当我们调用decoratedComponent的operation()方法时,它会先调用被装饰对象的operation()方法,然后再添加新的行为。
三、咖啡例子
一个经典的例子是咖啡店的咖啡。咖啡店通常会提供多种咖啡,例如浓缩咖啡、拿铁咖啡、卡布奇诺等。每种咖啡都有其基本配方,但客户可以根据自己的口味添加额外的配料,例如牛奶、糖浆、奶泡等。
在这种情况下,咖啡可以看作是一个基本对象,而添加的配料可以看作是装饰器。客户可以根据自己的喜好选择不同的装饰器来定制自己的咖啡。
下面是一个简单的Java代码示例,演示如何使用装饰者模式来制作咖啡。
首先,我们定义一个基本的咖啡接口,它包含了获取咖啡名称和价格的方法:
public interface Coffee {
String getName();
double getPrice();
}
然后,我们实现一个基本的浓缩咖啡类,它实现了Coffee接口:
public class Espresso implements Coffee {
@Override
public String getName() {
return "Espresso";
}
@Override
public double getPrice() {
return 1.99;
}
}
接下来,我们定义一个装饰器接口,它也实现了Coffee接口,但是它还包含了一个setCoffee方法,用于设置要装饰的咖啡对象:
public interface CoffeeDecorator extends Coffee {
void setCoffee(Coffee coffee);
}
再然后,我们实现一个牛奶装饰器,它将牛奶添加到咖啡中:
public class MilkDecorator implements CoffeeDecorator {
private Coffee coffee;
@Override
public void setCoffee(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getName() {
return coffee.getName() + ", Milk";
}
@Override
public double getPrice() {
return coffee.getPrice() + 0.5;
}
}
最后,我们可以使用这些类来制作不同的咖啡:
public class CoffeeShop {
public static void main(String[] args) {
// 制作一杯浓缩咖啡
Coffee espresso = new Espresso();
System.out.println(espresso.getName() + ": $" + espresso.getPrice());
// 制作一杯加牛奶的浓缩咖啡
Coffee milkEspresso = new MilkDecorator();
milkEspresso.setCoffee(new Espresso());
System.out.println(milkEspresso.getName() + ": $" + milkEspresso.getPrice());
// 制作一杯加牛奶和糖浆的浓缩咖啡
Coffee syrupMilkEspresso = new SyrupDecorator();
syrupMilkEspresso.setCoffee(new MilkDecorator());
syrupMilkEspresso.getCoffee().setCoffee(new Espresso());
System.out.println(syrupMilkEspresso.getName() + ": $" + syrupMilkEspresso.getPrice());
}
}
输出结果如下:
复制代码Espresso: $1.99
Espresso, Milk: $2.49
Espresso, Milk, Syrup: $3.49
这个例子展示了如何使用装饰者模式来动态地添加行为或功能。客户可以根据自己的喜好选择不同的装饰器来定制自己的咖啡,而不需要修改基本的咖啡类。
四、jdk中的应用
Java的IO库中,InputStream和OutputStream是抽象类,它们的子类实现了不同的输入输出方式,如FileInputStream和FileOutputStream、ByteArrayInputStream和ByteArrayOutputStream等。这些子类都是装饰者模式的具体实现。
下面是一个简单的例子,演示了如何使用装饰者模式来实现文件读写操作:
// 创建一个文件输入流
InputStream inputStream = new FileInputStream("test.txt");
// 使用BufferedInputStream装饰文件输入流,添加缓冲功能
InputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 使用DataInputStream装饰BufferedInputStream,添加读取基本数据类型的功能
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
// 读取文件中的数据
int num = dataInputStream.readInt();
String str = dataInputStream.readUTF();
// 关闭流
dataInputStream.close();
在这个例子中,我们首先创建了一个文件输入流,然后使用BufferedInputStream装饰它,添加了缓冲功能。接着,我们又使用DataInputStream装饰BufferedInputStream,添加了读取基本数据类型的功能。最后,我们通过DataInputStream读取了文件中的数据,并关闭了流。
这个例子中,我们使用了两个装饰者类来装饰文件输入流,它们分别是BufferedInputStream和DataInputStream。这两个类都是InputStream的子类,同时也是装饰者类。它们的作用是在文件输入流的基础上添加额外的功能,例如缓冲、读取基本数据类型等。
网友评论