在软件开发中,设计模式提供了一种可复用的解决方案,用于解决常见的设计问题。代理模式、装饰模式和桥接模式是三种常见的结构型设计模式,它们各自有不同的用途和特性。本文将详细探讨这三种模式的区别,并通过Java语言进行示例说明。
一、代理模式(Proxy Pattern)
代理模式为其他对象提供一种代理以控制对该对象的访问。代理对象在客户端和目标对象之间起到中介作用。这种模式主要用于控制对某个对象的访问,比如权限校验、延迟初始化、日志记录等。
1. 代理模式的结构
代理模式主要包含以下几个角色:
- 抽象主题角色(Subject):声明了真实主题和代理主题的共同接口。
- 真实主题角色(RealSubject):实现了抽象主题接口,是代理对象所代表的真实对象。
- 代理主题角色(Proxy):提供了与真实主题相同的接口,内部持有对真实主题的引用,并可以在必要时控制对真实主题的访问。
2. 代理模式的示例
以下是一个简单的代理模式示例,用于控制对图像的访问:
// 抽象主题角色
interface Image {
void display();
}
// 真实主题角色
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
// 代理主题角色
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Image proxyImage = new ProxyImage("test.jpg");
proxyImage.display();
}
}
在这个示例中,ProxyImage
类代理了RealImage
类,实现了延迟加载图像的功能。只有在display
方法被调用时,RealImage
对象才会被创建。
3. 代理模式的适用场景
- 权限控制:在访问对象之前检查权限。
- 延迟加载:在需要时才加载对象,以节省资源。
- 日志记录:在访问对象时记录日志。
- 远程代理:代理远程对象,使得调用远程方法就像调用本地方法一样。
二、装饰模式(Decorator Pattern)
装饰模式动态地将责任附加到对象上。若要扩展功能,装饰模式提供了比继承更有弹性的替代方案。这种模式主要用于在不改变原有对象的基础上,为对象添加额外的职责。
1. 装饰模式的结构
装饰模式主要包含以下几个角色:
- 抽象构件角色(Component):定义了一个接口,用于规范准备接受附加责任的对象。
- 具体构件角色(ConcreteComponent):实现了抽象构件接口,是装饰器装饰的原始对象。
- 装饰器抽象角色(Decorator):持有一个对抽象构件的引用,并实现了抽象构件接口。装饰器抽象角色可以装饰具体构件角色,也可以装饰其他装饰器角色。
- 具体装饰器角色(ConcreteDecorator):扩展了装饰器抽象角色,实现了具体的装饰功能。
2. 装饰模式的示例
以下是一个简单的装饰模式示例,用于为咖啡添加额外的配料:
// 抽象构件角色
interface Coffee {
double getCost();
String getDescription();
}
// 具体构件角色
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 5.0;
}
@Override
public String getDescription() {
return "Simple Coffee";
}
}
// 装饰器抽象角色
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public double getCost() {
return coffee.getCost();
}
@Override
public String getDescription() {
return coffee.getDescription();
}
}
// 具体装饰器角色
class MilkCoffee extends CoffeeDecorator {
public MilkCoffee(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", with milk";
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Coffee simpleCoffee = new SimpleCoffee();
System.out.println(simpleCoffee.getCost());
System.out.println(simpleCoffee.getDescription());
Coffee milkCoffee = new MilkCoffee(simpleCoffee);
System.out.println(milkCoffee.getCost());
System.out.println(milkCoffee.getDescription());
}
}
在这个示例中,MilkCoffee
类装饰了SimpleCoffee
类,为其添加了牛奶的配料和额外的费用。
3. 装饰模式的适用场景
- 动态扩展功能:在不改变原有对象的基础上,动态地为对象添加额外的职责。
- 替代继承:当类的继承层次结构过于复杂时,可以使用装饰模式来替代继承,增加灵活性。
三、桥接模式(Bridge Pattern)
桥接模式将抽象部分与它的实现部分分离,使它们可以独立地变化。这种模式主要用于解决类的功能层次结构与实现层次结构之间的耦合关系,通过分离抽象部分和实现部分,使它们可以独立地变化。
1. 桥接模式的结构
桥接模式主要包含以下几个角色:
- 抽象部分(Abstraction):定义抽象类或接口,维护一个对实现部分的引用,并定义了所需的操作方法。
- 扩充抽象类(Refined Abstraction):扩展抽象部分,实现更细化的功能。
- 实现部分(Implementor):定义实现部分的接口,该接口提供了实现部分的方法。
- 具体实现类(Concrete Implementor):实现实现部分的接口,提供具体的方法实现。
2. 桥接模式的示例
以下是一个简单的桥接模式示例,用于绘制不同颜色和形状的图形:
// 抽象部分
abstract class Shape {
protected DrawingAPI drawingAPI;
public Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
public abstract void draw();
}
// 实现部分
interface DrawingAPI {
void drawCircle(double x, double y, double radius);
}
// 具体实现类A
class RedCircle implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.printf("Drawing red circle at (%.1f, %.1f) with radius %.1f%n", x, y, radius);
}
}
// 具体实现类B
class GreenCircle implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.printf("Drawing green circle at (%.1f, %.1f) with radius %.1f%n", x, y, radius);
}
}
// 扩充抽象类
class Circle extends Shape {
private double x, y, radius;
public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawingAPI.drawCircle(x, y, radius);
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
DrawingAPI redAPI = new RedCircle();
Shape redCircle = new Circle(100, 100, 50, redAPI);
redCircle.draw();
DrawingAPI greenAPI = new GreenCircle();
Shape greenCircle = new Circle(200, 200, 100, greenAPI);
greenCircle.draw();
}
}
在这个示例中,Shape
类作为抽象部分,持有一个对DrawingAPI
的引用。RedCircle
和GreenCircle
类作为具体实现类,实现了不同的绘制逻辑。Circle
类作为扩充抽象类,
3. 桥接模式的适用场景
桥接模式的适用场景
抽象与实现分离:
当需要将抽象部分与它的实现部分分离,使它们可以独立变化时,桥接模式是一个很好的选择。这有助于降低系统的耦合度,提高灵活性。避免静态继承:
桥接模式可以避免在抽象和具体实现之间建立静态的继承关系,而是通过组合的方式在二者之间建立关联,从而增加了系统的可扩展性。多维度变化:
当一个类存在两个或更多独立变化的维度,并且这些维度都需要独立扩展时,桥接模式可以很好地处理这种情况。它允许这些维度各自独立地扩展,而不会影响到其他维度。减少类数量:
桥接模式通过抽象关联取代了传统的多层继承,有效地控制了系统中类的个数,避免了继承层次的指数级爆炸。跨平台或跨格式支持:
在需要支持多个平台或多个格式的情况下,桥接模式可以将平台或格式相关的实现与业务逻辑分离,使得业务逻辑可以独立于平台或格式进行扩展和维护。