Structural patterns
Structural patterns主要包括Adapter和Decorator两种设计模式。这些模式旨在解决不同类或对象之间接口不兼容或动态添加/覆盖行为的问题,以提高代码的复用性和可维护性。
Adapter模式(适配器模式):
- Intent(意图):将一个类的接口转换成客户端期望的另一个接口,使得原本不兼容的类能够协同工作。
- 适用场景:当需要使用一个已经存在的类,但其接口与需要的接口不匹配时,可以使用适配器模式进行适配。
- 实现方式:适配器模式可以通过类适配器或对象适配器来实现,通过继承或组合的方式将被适配者的接口转换成目标接口。
Decorator模式(装饰器模式):
- Intent(意图):动态地为对象添加额外的行为,而不需要改变其接口。
- 适用场景:当需要在不改变原有对象结构的情况下,动态地添加或覆盖对象的行为时,可以使用装饰器模式。
- 实现方式:装饰器模式通过实现一个共同的接口或继承一个共同的抽象类,然后在运行时动态地添加额外的功能。
Adapter Pattern
适配器模式(Adapter Pattern)是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式允许原本由于接口不兼容而无法一起工作的类能够协同工作。
适配器模式通常涉及以下几个角色:
- 目标接口(Target):客户端期望的接口,适配器模式的目标是将被适配者转换成目标接口。
- 被适配者(Adaptee):需要被适配的类,它定义了客户端不需要的接口。
- 适配器(Adapter):实现了目标接口,并持有一个被适配者的实例,在适配器中调用被适配者的方法来实现目标接口。
适配器模式的主要作用是解决两个不兼容接口之间的适配问题,使得它们可以协同工作。适配器模式通常用于以下情况:
- 当需要使用一个已经存在的类,但其接口与需要的接口不匹配时,可以使用适配器模式进行适配。
- 当需要复用一些现有的类,但是这些类的接口与系统要求的接口不一致时,可以使用适配器模式进行适配。
- 当希望创建一个可复用的类,该类可以与不相关或不可预见的类协同工作时,可以使用适配器模式。
适配器模式的实现方式有两种:类适配器和对象适配器。类适配器使用继承来实现适配,对象适配器使用组合来实现适配。适配器模式可以提高代码的复用性和灵活性,同时也降低了系统的耦合度。
假设有一个LegacyRectangle类,它有一个display()方法,接受"x, y, w, h"参数来显示一个矩形,但是现在客户端希望传入"upper left x and y"和"lower right x and y"来显示矩形。这种接口不兼容的情况可以通过适配器模式来解决。
下面是一个简单的示例代码来说明适配器模式的应用:
// 被适配者:LegacyRectangle类
public class LegacyRectangle {
public void display(int x, int y, int width, int height) {
System.out.println("Displaying rectangle at (" + x + ", " + y + ") with width " + width + " and height " + height);
}
}
// 目标接口:Shape接口
public interface Shape {
void draw();
}
// 适配器:LegacyRectangleAdapter类
public class LegacyRectangleAdapter implements Shape {
private LegacyRectangle legacyRectangle;
public LegacyRectangleAdapter(LegacyRectangle legacyRectangle) {
this.legacyRectangle = legacyRectangle;
}
@Override
public void draw() {
// 将"upper left x and y"和"lower right x and y"转换成"x, y, w, h"参数
int x1 = 10; // upper left x
int y1 = 20; // upper left y
int x2 = 30; // lower right x
int y2 = 40; // lower right y
int width = x2 - x1;
int height = y2 - y1;
legacyRectangle.display(x1, y1, width, height);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
LegacyRectangle legacyRectangle = new LegacyRectangle();
Shape shape = new LegacyRectangleAdapter(legacyRectangle);
shape.draw();
}
}
在上面的示例中,LegacyRectangle类是被适配者,它有一个display()方法用于显示矩形。Shape接口是客户端期望的接口,定义了draw()方法。LegacyRectangleAdapter类是适配器,实现了Shape接口,并持有一个LegacyRectangle实例,在draw()方法中将"upper left x and y"和"lower right x and y"转换成LegacyRectangle类需要的参数形式,然后调用LegacyRectangle的display()方法来显示矩形。
客户端代码中,通过创建LegacyRectangleAdapter实例来适配LegacyRectangle类,使得LegacyRectangle类能够符合Shape接口的要求,实现了两者之间的适配。
Decorator pattern
Decorator模式是一种结构型设计模式,它允许动态地为对象添加额外的功能,而不需要改变其接口。这种模式通过组合和委托的方式,在运行时动态地为对象添加新的行为,从而实现对对象功能的灵活扩展。
在Decorator模式中,通常涉及以下角色:
-
Component(组件):定义一个对象接口,可以动态地为这些对象添加新的功能。通常是一个接口或抽象类,声明了对象的基本行为。
-
ConcreteComponent(具体组件):实现Component接口的具体对象,是被装饰的对象。具体组件是Decorator模式中的核心对象,它定义了被装饰对象的基本行为。
-
Decorator(装饰器):维持一个指向Component对象的引用,并实现与Component接口一致的接口,同时可以动态地添加新的功能。装饰器类通常是抽象类,它持有一个Component对象的引用,并通过构造函数或setter方法注入被装饰对象。
-
ConcreteDecorator(具体装饰器):实现Decorator接口,具体实现对Component对象的装饰功能。具体装饰器是实际添加新功能的类,可以根据需要添加不同的装饰器来扩展对象的功能。
Decorator模式的工作原理如下:
- 客户端通过Component接口操作具体组件对象。
- 装饰器类继承自Component接口,并持有一个Component对象的引用。
- 具体装饰器类继承自装饰器类,可以在其中添加额外的功能。
- 客户端可以动态地组合装饰器对象,实现对对象功能的灵活扩展。
举例说明Decorator模式: 假设有一个Stack接口表示栈数据结构,现在需要为栈添加一些额外的功能,比如UndoStack(支持撤销操作)、SecureStack(需要密码验证)、SynchronizedStack(支持并发访问)。可以使用Decorator模式来实现:
// 定义Stack接口
interface Stack {
void push(Item e);
Item pop();
}
// 实现基本的栈功能的ArrayStack类
public class ArrayStack implements Stack {
// 实现具体的栈功能
public ArrayStack() {
//...
}
public void push(Item e) {
//...
}
public Item pop() {
//...
}
//...
}
// 定义装饰器抽象类StackDecorator
public abstract class StackDecorator implements Stack {
protected final Stack stack;
public StackDecorator(Stack stack) {
this.stack = stack;
}
public void push(Item e) {
stack.push(e);
}
public Item pop() {
return stack.pop();
}
//...
}
// 实现具体的装饰器UndoStack类
public class UndoStack extends StackDecorator implements Stack {
private final UndoLog log = new UndoLog();
public UndoStack(Stack stack) {
super(stack);
}
public void push(Item e) {
log.append(UndoLog.PUSH, e);
super.push(e);
}
public void undo() {
// 实现撤销操作的逻辑
}
//...
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 构造一个普通的栈对象
Stack s = new ArrayStack();
// 构造一个带有撤销功能的栈对象
Stack t = new UndoStack(new ArrayStack());
// 构造一个同时具有安全和同步功能以及撤销功能的栈对象
Stack t = new SecureStack(new SynchronizedStack(new UndoStack(s)));
}
}
在上面的示例中,Stack接口是组件,ArrayStack是具体组件,StackDecorator是装饰器接口,UndoStack、SecureStack、SynchronizedStack是具体装饰器。通过Decorator模式,可以动态地为栈对象添加撤销、密码验证、并发访问等功能,而不需要改变原有的栈接口和实现。
在这段代码中,我们定义了Stack接口和ArrayStack类来实现基本的栈功能。然后我们创建了StackDecorator抽象类作为装饰器的基类,其中包含了一个被装饰的Stack对象。接着我们实现了具体的装饰器UndoStack类,它继承自StackDecorator,并在push方法中添加了记录操作日志的功能。
最后,在客户端代码中,我们展示了如何使用这些类来构造不同类型的栈对象,包括普通栈对象、带有撤销功能的栈对象以及同时具有安全和同步功能以及撤销功能的栈对象。这样可以灵活地组合不同的功能,实现功能的动态扩展和组合。
下面我将展示一个稍微复杂一点的示例,以更好地展示Decorator模式的实际应用。
假设我们有一个Coffee接口表示咖啡,具有描述和价格两个方法。现在我们需要为咖啡添加一些装饰器,比如加牛奶、加糖、加焦糖等功能。我们可以使用Decorator模式来实现这个需求。
// Component: Coffee接口表示咖啡
interface Coffee {
String getDescription();
double cost();
}
// ConcreteComponent: SimpleCoffee是简单的咖啡实现
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 1.0;
}
}
// Decorator: CoffeeDecorator是咖啡装饰器接口
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double cost() {
return decoratedCoffee.cost();
}
}
// ConcreteDecorator: MilkDecorator是加牛奶的装饰器
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with Milk";
}
@Override
public double cost() {
return super.cost() + 0.5;
}
}
// ConcreteDecorator: SugarDecorator是加糖的装饰器
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with Sugar";
}
@Override
public double cost() {
return super.cost() + 0.3;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println("Description: " + coffee.getDescription() + ", Cost: " + coffee.cost());
coffee = new MilkDecorator(coffee);
System.out.println("Description: " + coffee.getDescription() + ", Cost: " + coffee.cost());
coffee = new SugarDecorator(coffee);
System.out.println("Description: " + coffee.getDescription() + ", Cost: " + coffee.cost());
}
}
在这个示例中,Coffee接口是组件,SimpleCoffee是具体组件,CoffeeDecorator是装饰器接口,MilkDecorator和SugarDecorator是具体装饰器。通过Decorator模式,我们可以动态地为咖啡对象添加牛奶、糖等功能,而不需要改变原有的咖啡接口和实现。这样可以实现对咖啡对象功能的灵活扩展。