结构型模式
适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,旨在将一个类的接口转换成客户端所期望的另一种接口。适配器模式使得原本由于接口不兼容而无法一起工作的类可以协同工作。
主要组成部分
目标接口:客户端所期望的接口。
适配者类:需要被适配的类,通常是一个已有的类。
适配器类:实现目标接口,并持有适配者类的实例,将客户端的请求转发给适配者。
以下为一个简化的适配器模式代码
//定义一个目标接口 Shape:
interface Shape {
void draw();
}
//定义适配者类 Circle、Rectangle 和 Triangle:
class Circle {
public void drawCircle() {
System.out.println("Drawing Circle");
}
}
class Rectangle {
public void drawRectangle() {
System.out.println("Drawing Rectangle");
}
}
class Triangle {
public void drawTriangle() {
System.out.println("Drawing Triangle");
}
}
//使用一个通用的适配器类来适配不同的图形:
class ShapeAdapter implements Shape {
private Object shape;
public ShapeAdapter(Object shape) {
this.shape = shape;
}
@Override
public void draw() {
if (shape instanceof Circle) {
((Circle) shape).drawCircle();
} else if (shape instanceof Rectangle) {
((Rectangle) shape).drawRectangle();
} else if (shape instanceof Triangle) {
((Triangle) shape).drawTriangle();
}
}
}
//在客户端代码中使用适配器:
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Shape> shapes = Arrays.asList(
new ShapeAdapter(new Circle()),
new ShapeAdapter(new Rectangle()),
new ShapeAdapter(new Triangle())
);
// 绘制所有图形
shapes.forEach(Shape::draw);
}
}
适配器模式是一种结构型设计模式,旨在解决接口不兼容的问题,允许不兼容的类协同工作。其优点包括提高代码的可复用性、增强系统的灵活性、降低耦合度、简化代码以及便于维护。然而,适配器模式也存在一些缺点,如增加系统复杂性、引入性能开销、可能导致过度设计、调试困难、接口不一致以及维护成本增加。因此,在使用适配器模式时,需要权衡其优缺点,以确保系统设计的合理性和高效性。
装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,允许在不改变对象自身的情况下,动态地给对象添加额外的功能。装饰器模式通过将功能封装在装饰器类中,提供了一种灵活的方式来扩展对象的功能。
主要组成部分
组件接口:定义一个接口,表示可以被装饰的对象。
具体组件:实现组件接口的具体类,表示被装饰的对象。
装饰器类:实现组件接口,并持有一个组件对象的引用,用于在其基础上添加新功能。
使用场景
当需要在运行时动态地添加功能时。
当需要为一个类的多个实例添加不同的功能时。
当不想通过子类化来扩展功能时。
实现代码:
//组件接口
interface Coffee {
String getDescription();
double cost();
}
//具体组件
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 2.0;
}
}
// 装饰器类
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
//具体装饰器
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double cost() {
return coffee.cost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double cost() {
return coffee.cost() + 0.2;
}
}
//客户端代码
public class Main {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.cost());
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.cost());
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.cost());
}
}
输出结果
Simple Coffee $2.0
Simple Coffee, Milk $2.5
Simple Coffee, Milk, Sugar $2.7
装饰器模式在实际应用中非常广泛,尤其是在需要动态扩展对象功能的场景。以下是一些常见的实际应用示例:
- 图形界面组件
在图形用户界面(GUI)中,装饰器模式常用于为组件添加功能。例如,可以使用装饰器为按钮、文本框等组件添加边框、滚动条、阴影等效果,而不需要修改原有组件的代码。 - 输入/输出流
在 Java 的 I/O 库中,装饰器模式被广泛应用于输入和输出流。例如,BufferedInputStream 和 DataInputStream 都是对 InputStream 的装饰器,它们在不改变原有流的情况下,提供了缓冲和数据读取的额外功能。 - 咖啡订单系统
如前面示例所示,咖啡订单系统是装饰器模式的经典应用。可以通过装饰器为咖啡添加不同的配料(如牛奶、糖、香料等),每种配料都可以作为一个装饰器,动态组合出不同的咖啡类型。 - 日志记录
在日志记录系统中,可以使用装饰器模式为日志添加不同的功能。例如,可以创建一个基本的日志记录器,然后通过装饰器为其添加时间戳、日志级别、输出到文件等功能,而不需要修改原有的日志记录器代码。 - 文本处理
在文本处理应用中,可以使用装饰器模式为文本添加不同的格式化功能。例如,可以创建一个基本的文本类,然后通过装饰器为其添加粗体、斜体、下划线等样式,而不需要修改原有文本类的实现。 - 网络请求
在网络请求中,可以使用装饰器模式为请求添加不同的功能,例如身份验证、缓存、重试机制等。通过装饰器,可以在不改变原有请求逻辑的情况下,动态地为请求添加这些功能。
优点
遵循开闭原则:可以在不修改原有类的情况下,动态地为对象添加新功能,增强了系统的可扩展性。
灵活性:可以使用多个装饰器对同一个对象进行多次装饰,从而实现不同组合的增强功能。
透明性:装饰后的对象与未装饰的对象在对外接口上保持一致,客户端无需关心对象是否被装饰以及如何装饰,只需面向组件接口编程。
缺点
类层次过深:过度使用装饰器模式可能导致类的层次过深,增加系统的复杂性。
理解难度:对于不熟悉装饰器模式的开发人员来说,可能需要花费更多时间理解装饰器的实现逻辑。
增加小类数量:可能会导致设计中出现大量的小类,增加了代码的复杂性。
总的来说,装饰器模式在提供灵活性和可扩展性方面表现出色,但在使用时需要谨慎,以避免系统复杂性过高。
总结
装饰器模式在许多领域都有实际应用,尤其是在需要动态扩展对象功能的场景中。它提供了一种灵活的方式来组合和扩展功能,避免了通过继承来实现功能扩展的复杂性。
代理模式
在 Java 中,代理模式可以分为两种主要类型:静态代理和动态代理。它们各自有不同的实现方式和使用场景。
1. 静态代理
静态代理是在编译时就确定代理类的类型,通常需要手动创建代理类。代理类和真实类都实现同一个接口,代理类持有真实类的引用,并在调用真实类的方法时添加额外的逻辑。
// 主题接口
interface Subject {
void request();
}
// 真实对象
class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理对象
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
public void request() {
System.out.println("Proxy: Pre-processing request.");
realSubject.request();
System.out.println("Proxy: Post-processing request.");
}
}
// 使用静态代理
public class StaticProxyDemo {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.request();
}
}
//输出结果
Proxy: Pre-processing request.
RealSubject: Handling request.
Proxy: Post-processing request.
2. 动态代理
动态代理是在运行时动态生成代理类,通常使用 Java 的反射机制。Java 提供了 java.lang.reflect.Proxy 类来创建动态代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 主题接口
interface Subject {
void request();
}
// 真实对象
class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 动态代理处理器
class DynamicProxyHandler implements InvocationHandler {
private Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Proxy: Pre-processing request.");
Object result = method.invoke(realSubject, args);
System.out.println("Proxy: Post-processing request.");
return result;
}
}
// 使用动态代理
public class DynamicProxyDemo {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject)
);
proxy.request();
}
}
//输出结果
Proxy: Pre-processing request.
RealSubject: Handling request.
Proxy: Post-processing request.
动态代理与静态代理的区别:
| 特点 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类生成 | 编译时生成 | 运行时生成 |
| 代码复杂性 | 需要手动创建代理类,代码较多 | 使用反射,代码较少 |
| 灵活性 | 不够灵活,代理类固定 | 灵活,可以在运行时决定代理行为 |
| 性能 | 性能较好 | 由于使用反射,性能稍差 |
总结
- 静态代理适用于代理类数量较少且不频繁变化的场景。
- 动态代理适用于代理类数量较多或需要在运行时动态生成代理的场景。
代理模式和装饰器模式虽然在结构上相似,但它们的目的和使用场景却截然不同。代理模式的主要目的是控制对原有对象的访问,通常用于处理权限管理、延迟加载或日志记录等职责,代理对象通过隐藏真实对象的实现来管理访问。而装饰器模式则旨在动态增强原有对象的功能,允许在不修改对象结构的情况下,添加额外的职责,适用于需要灵活组合功能的场景。
以下是代理模式和装饰器模式的区别总结:
| 特点 | 代理模式 | 装饰器模式 |
|---|---|---|
| 目的 | 控制对原有对象的访问 | 动态增强原有对象的功能 |
| 关注点 | 原有对象的行为 | 增强后的行为 |
| 透明性 | 对客户端透明 | 客户端可见装饰效果 |
| 使用场景 | 权限控制、延迟加载、日志记录等 | 动态添加功能、功能组合 |
| 结构 | 代理类持有真实对象的引用 | 装饰器类持有被装饰对象的引用 |
桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,旨在将抽象部分与实现部分分离,使它们可以独立变化。这种模式通过引入一个桥接接口,使得抽象和实现之间的耦合度降低,从而提高系统的灵活性和可扩展性。
原理:抽象类持有实现类接口引用,并定义上层抽象接口。扩展抽象类分别实现对应的上层方法,且持有不同的实现类引用,实现抽象部分与实现部分的分离,灵活切换对应的抽象方法与实现方法。
主要组成部分
- 抽象类(Abstraction):定义高层接口,持有对实现接口的引用。
- 扩展抽象类(Refined Abstraction):实现抽象类的具体实现方法。
- 实现接口(Implementor):定义实现部分的接口。
- 具体实现类(Concrete Implementor):实现接口的具体类。
// 实现接口
interface Color {
String fill();
}
// 具体实现类
class Red implements Color {
@Override
public String fill() {
return "Red color";
}
}
class Blue implements Color {
@Override
public String fill() {
return "Blue color";
}
}
// 抽象类
abstract class Shape {
protected Color color;
protected Shape(Color color) {
this.color = color;
}
abstract void draw();
}
// 具体抽象类
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
void draw() {
System.out.println("Drawing a circle with " + color.fill());
}
}
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
void draw() {
System.out.println("Drawing a square with " + color.fill());
}
}
// 客户端代码
public class BridgePatternDemo {
public static void main(String[] args) {
Color red = new Red();
Color blue = new Blue();
Shape circle = new Circle(red);
Shape square = new Square(blue);
circle.draw(); // 输出: Drawing a circle with Red color
square.draw(); // 输出: Drawing a square with Blue color
}
}
总结
桥接模式通过将抽象与实现分离,提供了更高的灵活性和可扩展性,适用于多种场景,尤其是在需要处理多个变化维度的复杂系统中。
外观模式
外观模式(Facade Pattern)是一种结构型设计模式,其主要目的是为复杂的子系统提供一个统一的高层接口,从而简化客户端与子系统的交互。以下是外观模式的一些关键点:
- 意图:提供一个简单的接口,隐藏系统的复杂性。
- 主要解决的问题:降低子系统与客户端之间的耦合度,提高程序的可维护性。
- 应用场景:适用于需要简化复杂系统的场合,例如大型框架或库的使用。
- 优缺点:
- 优点:简化了接口,降低了系统的复杂性,增强了可维护性。
- 缺点:可能会导致系统的灵活性降低,因为外观模式将多个接口封装在一起。
// 子系统类
class SubsystemA {
public String operationA() {
return "Subsystem A: Operation A";
}
}
class SubsystemB {
public String operationB() {
return "Subsystem B: Operation B";
}
}
class SubsystemC {
public String operationC() {
return "Subsystem C: Operation C";
}
}
// 外观类
class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
private SubsystemC subsystemC;
public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
subsystemC = new SubsystemC();
}
public String simpleOperation() {
StringBuilder result = new StringBuilder();
result.append(subsystemA.operationA()).append("\n");
result.append(subsystemB.operationB()).append("\n");
result.append(subsystemC.operationC()).append("\n");
return result.toString();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
System.out.println(facade.simpleOperation());
}
}
组合模式
组合模式(Composite Pattern)是一种结构型设计模式,旨在将对象组合成树形结构以表示“部分-整体”的层次关系。该模式允许客户端以统一的方式对待单个对象和对象组合,从而简化了客户端的操作。
组合模式用于以树的结构去定义一组对象,其中包含叶子节点的单一对象结构,也包含由持有子对象列表的组合节点对象,通过相互向下持有对象列表引用的方式去存储对象。组合模式的优点在于它通过提供统一的接口,使客户端能够以一致的方式处理单个对象和对象组合,从而简化了代码逻辑。此外,该模式增强了系统的灵活性和可扩展性,允许动态添加或删除对象,同时清晰地表示“部分-整体”的层次关系,便于理解和维护。这种结构化的设计符合开闭原则,使得系统在扩展时无需修改现有代码。
主要组成部分
- 组件接口(Component):定义了叶子和组合对象的共同接口。
- 叶子节点(Leaf):实现组件接口,表示树的叶子节点,通常是最基本的对象。
- 组合节点(Composite):实现组件接口,持有子组件的集合,可以包含叶子节点或其他组合节点。
应用场景
- 图形界面:在图形用户界面中,窗口、按钮、文本框等可以被视为组合对象,允许用户以统一的方式处理这些元素。
- 文件系统:文件和文件夹可以使用组合模式来表示,文件夹可以包含文件或其他文件夹。
- 组织结构:公司组织结构图可以使用组合模式来表示,部门可以包含员工或其他部门。
import java.util.ArrayList;
import java.util.List;
// 组件接口
interface Component {
void operation();
}
// 叶子节点
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Leaf: " + name);
}
}
// 组合节点
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
System.out.println("Composite:");
for (Component child : children) {
child.operation();
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Leaf leaf1 = new Leaf("Leaf 1");
Leaf leaf2 = new Leaf("Leaf 2");
Composite composite = new Composite();
composite.add(leaf1);
composite.add(leaf2);
composite.operation();
}
}
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来减少内存使用和提高性能。该模式特别适用于需要大量相似对象的场景,通过将对象的共享部分(内蕴状态)与独立部分(外蕴状态)分离,从而有效地管理内存。
主要组成部分
- 享元接口(Flyweight):定义了可以共享的对象接口。
- 具体享元(Concrete Flyweight):实现享元接口,包含共享的状态。
- 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保共享相同的实例。
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface Flyweight {
void operation(String extrinsicState);
}
// 具体享元
class ConcreteFlyweight implements Flyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("Intrinsic State: " + intrinsicState + ", Extrinsic State: " + extrinsicState);
}
}
// 享元工厂
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(key));
}
return flyweights.get(key);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("A");
flyweight1.operation("First Call");
Flyweight flyweight2 = factory.getFlyweight("A");
flyweight2.operation("Second Call");
Flyweight flyweight3 = factory.getFlyweight("B");
flyweight3.operation("Third Call");
}
}
享元模式通过相同对象的重复利用达成节省内存的目的。其中具体享元对象实现相同的享元接口,并且对象的对象的共享部分统一;独立部分暴露从而达成统一内存不同实现的效果,减少了对象的创建和销毁次数,提高了系统的性能,尤其是在频繁使用相同对象的情况下。但是过度共享对象可能导致性能下降,尤其是在需要频繁修改对象状态时,多个客户端共享同一个享元对象,可能导致状态不一致,增加了调试的难度。建议慎用,可使用对象缓存池的方式去替代。