java设计模式[3]之结构性型模式
文章目录
- 一 代理模式
- 1.1 静态代理
- 1.1.1 静态代理的结构
- 1.1.2 静态代理的特点
- 1.1.3 静态代理的应用场景
- 1.1.4 静态代理的案例代码
- 1.2 JDK动态代理
- 1.2.1 JDK动态代理概述
- 1.2.2 JDK动态代理案例代码
- 1.2.3 JDK动态代理的应用场景
- 1.2.4 JDK动态代理的特点
- 1.2.5 与创建型模式的区别
- 1.3 CGLIB动态代理
- 1.3.1 CGLIB动态代理概述
- 1.3.2 CHLIB动态代理代码案例
- 1.4 CGLIB vs JDK 动态代理对比
- 1.5 代理模式的经典应用
- 1.6 代理模式的经典应用
- 二 装饰器模式
- 2.1 装饰器模式的核心组成
- 2.2 装饰器模式的代码案例
- 2.3 装饰器模式的优点
- 2.4 装饰器模式的适用场景
- 三 适配器模式
- 3.1 适配器模式的主要角色
- 3.2 适配器模式的两种实现方式
- 3.3 适配器模式的代码案例
- 3.3.1 目标接口 `Target`
- 3.3.2 被适配者 `Adaptee`
- 3.3.3 对象适配器 `ObjectAdapter`
- 3.3.4 类适配器 `ClassAdapter`(如果使用继承)
- 3.3.5 客户端代码
- 3.4 适配器模式的应用场景
- 3.5 适配器模式的优缺点
- 四 组合模式
- 4.1 组合模式的主要角色
- 4.2 组合模式的UML 结构图
- 4.3 组合模式的代码案例
- 4.3.1 定义 `Component` 接口
- 4.3.2 实现 `Leaf` 类
- 4.3.3 实现 `Composite` 类
- 4.3.4 客户端使用
- 4.4 组合模式的适用场景
- 4.5 组合模式的优缺点
- 五 门面模式
- 5.1 门面模式的核心思想
- 5.2 门面模式的主要角色
- 5.3 门面模式的代码案例
- 5.4 门面模式的应用场景
- 5.5 门面模式的优缺点
- 六 桥接模式
- 6.1 桥接模式的主要角色
- 6.2 桥接模式的应用场景
- 6.3 桥接模式的代码案例
- 6.4 桥接模式的优缺点
- 6.5 桥接模式的经典应用
- 七 享元模式
- 7.1 享元模式的主要角色
- 7.2享元模式的代码实例
- 7.2.1 抽象享元类
- 7.2.2 具体享元类
- 7.2.3 享元工厂类
- 7.2.4 客户端调用
- 7.3享元模式的优缺点
- 7.4 享元模式的适用场景
- 7.5 享元模式的经典应用

一 代理模式
- 代理模式的作用是通过代理对象来增强目标对象的功能。利用AOP横切的思想。代理模式的实现方式有三种:静态代理,动态代理(JDK动态代理和CGLIB动态代理)。
1.1 静态代理
- 静态代理是 Java 中一种常见的设计模式,属于创建型模式的一种。它主要用于在不修改目标对象的前提下,通过代理对象增强目标对象的功能。
1.1.1 静态代理的结构
- 接口(Subject):定义目标对象和代理对象的公共接口。
- 目标类(RealSubject):实现接口,提供核心功能。
- 代理类(Proxy):也实现接口,内部持有目标对象的引用,可以在调用目标对象的方法前后添加额外操作。
1.1.2 静态代理的特点
-
优点:
- 符合开闭原则:不需要修改目标对象,就可以为其增加新的功能。
- 提高代码解耦性:代理对象和目标对象职责清晰。
-
缺点:
- 每个接口都需要一个代理类,会导致类数量增加。
- 如果接口方法发生变化,代理类也需要同步修改。
1.1.3 静态代理的应用场景
- 日志记录、性能监控。
- 权限控制。
- 远程调用等需要对目标对象进行封装的场景。
1.1.4 静态代理的案例代码
- 定义接口
Subject
public interface Subject {void request();
}
- 实现目标类
RealSubject
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("RealSubject: 处理请求");}
}
- 创建代理类
Proxy
public class Proxy implements Subject {private Subject realSubject;public Proxy(Subject realSubject) {this.realSubject = realSubject;}@Overridepublic void request() {System.out.println("Proxy: 请求前的预处理");realSubject.request(); // 调用真实对象的方法System.out.println("Proxy: 请求后的收尾");}
}
- 使用示例
public class Client {public static void main(String[] args) {Subject realSubject = new RealSubject();Subject proxy = new Proxy(realSubject);proxy.request(); // 通过代理调用目标对象的方法}
}
1.2 JDK动态代理
- 静态代理适用于目标对象较少且接口固定的场景。如果需要动态地为多个对象生成代理,可以使用 JDK 动态代理 或 CGLIB 动态代理。
1.2.1 JDK动态代理概述
- JDK动态代理是一种运行时动态生成代理类的技术,允许在不修改目标类的情况下增强其功能。它主要依赖于
java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
接口。 Proxy
: 提供静态方法用于创建代理实例。InvocationHandler
: 定义了代理对象调用方法时的处理逻辑。
1.2.2 JDK动态代理案例代码
- 创建一个接口(如
MyInterface
)。 - 创建接口的实现类(如
MyInterfaceImpl
)。 - 实现
InvocationHandler
接口,定义拦截逻辑。 - 使用
Proxy.newProxyInstance()
方法生成代理对象。
// 接口类
public interface MyInterface {void doSomething();
}
// 实现类
public class MyInterfaceImpl implements MyInterface{@Overridepublic void doSomething() {System.out.println("Do something");}
}import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//代理类
public class MyInvocationHandler implements InvocationHandler {private final Object target;public MyInvocationHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method call");Object result = method.invoke(target, args);System.out.println("After method call");return result;}}
//测试类
public static void main(String[] args) {// 创建目标对象(真实主题),它是 MyInterface 接口的具体实现类MyInterface target = new MyInterfaceImpl();// 创建一个 InvocationHandler 实例,并将目标对象传入,用于处理代理对象的方法调用InvocationHandler handler = new MyInvocationHandler(target);// 使用 Proxy.newProxyInstance() 方法创建一个代理对象// 参数说明:// 1. target.getClass().getClassLoader():指定类加载器,用来加载动态生成的代理类// 2. new Class<?>[] { MyInterface.class }:指定代理类要实现的接口列表,这里只有一个接口 MyInterface// 3. handler:指定代理对象的方法调用处理器,即当代理对象的方法被调用时,会转交给这个 handler 来处理MyInterface proxy = (MyInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器// 要代理的接口 使用new Class<?>[] { MyInterface.class },或target.getClass().getInterfaces(),target.getClass().getInterfaces(),handler // 方法调用处理器);// 调用代理对象的方法,实际会被 InvocationHandler 的 invoke 方法拦截并处理proxy.doSomething();
}
1.2.3 JDK动态代理的应用场景
- AOP编程:如日志记录、性能监控等。
- 远程调用:如RMI(Remote Method Invocation)。
- 权限控制:在调用目标方法前进行权限验证。
1.2.4 JDK动态代理的特点
- 基于接口:只能对接口进行代理,不能对类进行代理。
- 动态生成字节码:在运行时动态生成代理类的字节码。
- 灵活性高:可以在不修改目标类的情况下增强其功能。
1.2.5 与创建型模式的区别
- 创建型模式(如工厂模式、单例模式、建造者模式等)关注的是对象的创建方式,隐藏对象的构造细节。
- JDK动态代理属于行为型模式,关注的是对象之间的交互和职责分配。
1.3 CGLIB动态代理
1.3.1 CGLIB动态代理概述
- CGLIB(Code Generation Library)是一个强大的字节码生成库,常用于在运行时动态生成类。它在Java中广泛应用于AOP编程、代理模式实现、ORM框架等场景。
- CGLIB动态代理的设计结构与使用方式,属于结构型设计模式的一种变体(非GoF经典23种之一,但基于代理模式的扩展)。
- CGLIB动态代理是基于继承机制实现的代理方式,适用于没有接口的类。它是Spring AOP底层的重要技术支撑之一,具有广泛的工程应用价值。
- CGLIB动态代理的核心概念
-
Enhancer
类:- 是CGLIB中最核心的类,用来创建一个被代理类的子类。
- 通过设置回调(
Callback
),拦截方法调用。
-
MethodInterceptor
接口:- 用户自定义的拦截器,必须实现
intercept()
方法。 - 在该方法中可以加入增强逻辑(如日志、事务、权限控制等)。
- 用户自定义的拦截器,必须实现
-
被代理类(Target Class):
- 不需要实现接口,这是与JDK动态代理的主要区别。
- CGLIB通过继承方式生成子类来实现代理。
1.3.2 CHLIB动态代理代码案例
- 需要引入依赖(Maven):
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
- 业务代码
public class UserService {public void addUser() {System.out.println("添加用户");}
}
- 实现 MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("前置增强逻辑");Object result = proxy.invokeSuper(obj, args); // 调用父类原始方法System.out.println("后置增强逻辑");return result;}
}
- 使用 Enhancer 创建代理对象
import net.sf.cglib.proxy.Enhancer;public class CglibProxyDemo {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class); // 设置父类enhancer.setCallback(new MyMethodInterceptor()); // 设置拦截器UserService proxy = (UserService) enhancer.create(); // 创建代理对象proxy.addUser(); // 调用方法,触发拦截逻辑}
}
- 添加虚拟接参数解决报错
--add-opens java.base/java.lang=ALL-UNNAMED
5. 执行结果
前置增强逻辑
添加用户
后置增强逻辑
1.4 CGLIB vs JDK 动态代理对比
特性 | CGLIB动态代理 | JDK动态代理 |
---|---|---|
原理 | 继承目标类生成子类 | 实现接口生成代理 |
是否需要接口 | 否 | 是 |
性能 | 初次生成较慢,运行快 | 每次反射调用较慢 |
应用场景 | Spring AOP(无接口)、Hibernate懒加载等 | Spring AOP(有接口)、RMI |
1.5 代理模式的经典应用
- Spring中Bean对象的AOP代理。
- MyBatis中Mapper接口代理对象。
- MyBatis中JDBC的日志代理。
- Ribbon或Loadbalancer中的RestTemplate代理对象。
- OpenFeign的接口代理对象。
1.6 代理模式的经典应用
- 代理模式在业务系统中的应用一般都是用来拓展增强业务功能。现在需要统计每个请求到来的处理时长,如果超过500毫秒就给出警告。
//控制层
public class UserController {private UserService userService;public UserController() {this.userService = new UserService(); // 改为构造函数中初始化}public String login() {return userService.login();}
}
// 服务层
public class UserService {public String login(){return "登录成功";}
}
//测试类
public class StartApp {public static void main(String[] args) {UserController userController = new UserController();UserController proxy = new MethodInterceptor() {public UserController createProxy(Class targetClass) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(targetClass);enhancer.setCallback(this);return (UserController) enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {long start = System.currentTimeMillis();Object res = proxy.invoke(userController, args);long end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));if(end-start>500){System.out.println("耗时过长");}return res;}}.createProxy(UserController.class);proxy.login();}
}
二 装饰器模式
- 装饰器模式(Decorator Pattern/包装模式)是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装对象中来为原对象增加功能。这种模式比静态子类化更灵活,能够在运行时动态地给对象添加职责。
- 装饰模式是一种用于代替继承的技术,无需通过继承子类增加子类就能拓展对象的新功能。使用对象的关联关系代替继承关系,更加灵活、同时避免类型体系的快速膨胀。
2.1 装饰器模式的核心组成
- Component(抽象组件):定义对象和装饰器的公共接口。
- ConcreteComponent(具体组件):实现基础功能的对象。
- Decorator(抽象装饰器):继承或实现
Component
,并持有Component
对象的引用。 - ConcreteDecorator(具体装饰器):为对象增加具体的功能。
2.2 装饰器模式的代码案例
- 抽象组件
Coffee
public interface Coffee {double cost();String description();
}
- 具体组件
SimpleCoffee
public class SimpleCoffee implements Coffee {@Overridepublic double cost() {return 2.0;}@Overridepublic String description() {return "Simple Coffee";}
}
- 抽象装饰器
CoffeeDecorator
public abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic double cost() {return decoratedCoffee.cost();}@Overridepublic String description() {return decoratedCoffee.description();}
}
- 具体装饰器
MilkDecorator
public class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 0.5; // 加牛奶的价格}@Overridepublic String description() {return super.description() + ", Milk";}
}
- 具体装饰器
SugarDecorator
public class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 0.2; // 加糖的价格}@Overridepublic String description() {return super.description() + ", Sugar";}
}
- 使用示例
public class Main {public static void main(String[] args) {Coffee coffee = new SimpleCoffee();System.out.println("Cost: $" + coffee.cost() + " | Description: " + coffee.description());Coffee milkCoffee = new MilkDecorator(new SimpleCoffee());System.out.println("Cost: $" + milkCoffee.cost() + " | Description: " + milkCoffee.description());Coffee sugarMilkCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));System.out.println("Cost: $" + sugarMilkCoffee.cost() + " | Description: " + sugarMilkCoffee.description());}
}
- 输出结果
Cost: $2.0 | Description: Simple Coffee
Cost: $2.5 | Description: Simple Coffee, Milk
Cost: $2.7 | Description: Simple Coffee, Milk, Sugar
2.3 装饰器模式的优点
- 灵活性高:相比静态继承方式,装饰器模式更加灵活,可以在运行时动态地组合对象和功能。
- 开闭原则:无需修改原有代码即可扩展功能。
- 组合优于继承:避免了类爆炸的问题,多个装饰器可以自由组合使用。
2.4 装饰器模式的适用场景
- 当需要动态、透明地给对象添加职责时。
- 当子类扩展不切实际或会导致类爆炸时。
- 当希望保持类责任清晰,并避免复杂的继承关系时。
三 适配器模式
- 适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端期望的另一个接口。适配器模式常用于解决两个不兼容接口之间的适配问题。
- 适配器模式的作用是把两个不兼容的对象通过适配器连接起来工作。
- 在 Java 中,
java.util.Arrays.asList()
可以将数组转换为List
,这其实也是一种适配器模式。 - Spring 框架中也广泛使用适配器模式来适配不同的事件监听器、处理器等组件。
3.1 适配器模式的主要角色
- 目标接口(Target):定义客户端使用的接口。
- 被适配者(Adaptee):需要被适配的现有类,其接口与目标接口不兼容。
- 适配器(Adapter):实现目标接口,并持有被适配者的实例,通过组合或继承的方式完成接口转换。
3.2 适配器模式的两种实现方式
- 对象适配器(推荐):使用组合的方式,适配器包含被适配者的实例。
- 类适配器:使用继承的方式,适配器继承自被适配者并实现目标接口。
3.3 适配器模式的代码案例
3.3.1 目标接口 Target
public interface Target {void request();
}
3.3.2 被适配者 Adaptee
public class Adaptee {public void specificRequest() {System.out.println("Adaptee's specific request.");}
}
3.3.3 对象适配器 ObjectAdapter
public class ObjectAdapter implements Target {private Adaptee adaptee;public ObjectAdapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specificRequest();}
}
3.3.4 类适配器 ClassAdapter
(如果使用继承)
public class ClassAdapter extends Adaptee implements Target {@Overridepublic void request() {specificRequest();}
}
3.3.5 客户端代码
public class Client {public static void main(String[] args) {// 使用对象适配器Adaptee adaptee = new Adaptee();Target target = new ObjectAdapter(adaptee);target.request(); // 输出: Adaptee's specific request.// 使用类适配器Target classAdapter = new ClassAdapter();classAdapter.request(); // 输出: Adaptee's specific request.}
}
3.4 适配器模式的应用场景
- 当希望复用已有的类,但其接口不符合当前需求时。
- 当需要在不影响现有代码的情况下扩展功能时。
- 当多个子类有不同的接口,而客户端希望统一调用时。
3.5 适配器模式的优缺点
优点
- 提高了类的复用性,避免对已有代码进行修改。
- 解耦客户端和被适配者,使得两者可以独立变化。
- 符合开闭原则,新增适配器不会影响原有系统。
缺点
- 增加系统的复杂度,引入额外的适配类。
- 如果过度使用,可能导致系统难以理解和维护。
四 组合模式
- 组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次关系。通过组合模式,客户端可以统一地处理单个对象和对象的组合。
- 组合模式的核心在于统一处理叶子节点(Leaf)和组合节点(Composite)。它让客户端无需关心当前操作的是单个对象还是组合对象,从而简化了客户端代码。
- 组合模式非常适合用于构建具有递归结构的对象树,尤其在需要统一处理个体和群体的情况下非常有用。
4.1 组合模式的主要角色
Component
:抽象类或接口,定义叶子节点和组合节点的公共行为。Leaf
:表示叶子节点,没有子节点,实现基础功能。Composite
:表示组合节点,包含子组件(可以是叶子节点或其他组合节点),并实现了管理子组件的方法(如添加、移除等)。
4.2 组合模式的UML 结构图
+-----------+| Component |+-----------+| operation()|+-----------+/ \/ \/ \
+--------+ +-------------+
| Leaf | | Composite |
+----------+ +-------------+
| operation()| | operation() || add(Component)|| remove(Component)|| getChild(int) |+---------------+
4.3 组合模式的代码案例
4.3.1 定义 Component
接口
public interface Component {void operation();
}
4.3.2 实现 Leaf
类
public class Leaf implements Component {private String name;public Leaf(String name) {this.name = name;}@Overridepublic void operation() {System.out.println("Leaf " + name + " is doing operation.");}
}
4.3.3 实现 Composite
类
import java.util.ArrayList;
import java.util.List;public class Composite implements Component {private String name;private List<Component> children = new ArrayList<>();public Composite(String name) {this.name = name;}public void add(Component component) {children.add(component);}public void remove(Component component) {children.remove(component);}@Overridepublic void operation() {System.out.println("Composite " + name + " is doing operation.");for (Component child : children) {child.operation();}}
}
4.3.4 客户端使用
public class Client {public static void main(String[] args) {// 创建叶子节点Component leaf1 = new Leaf("Leaf1");Component leaf2 = new Leaf("Leaf2");// 创建组合节点Composite composite = new Composite("Composite1");composite.add(leaf1);composite.add(leaf2);// 调用组合的操作composite.operation();}
}
- 输出结果
Composite Composite1 is doing operation.
Leaf Leaf1 is doing operation.
Leaf Leaf2 is doing operation.
4.4 组合模式的适用场景
- 表示“部分-整体”的树形结构。
- 希望客户端能够忽略组合对象与单个对象的不同,统一处理它们。
- 需要动态地构建层级结构,并希望灵活地增删结构中的元素。
4.5 组合模式的优缺点
优点
- 符合开闭原则,增加新的组件类不需要修改现有代码。
- 简化客户端代码,使客户端更容易操作复杂结构。
- 提供一致的方式处理单个对象和组合对象。
缺点
- 如果过度使用组合模式,可能会导致系统中出现大量细小的对象,增加调试和维护成本。
- 对于不支持树形结构的操作,需要额外处理。
五 门面模式
- 门面模式(Facade Pattern/外观模式)是 Java 中常用的 结构型设计模式 之一,它的主要目的是为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易被使用。
- 门面模式是一种非常实用的设计模式,特别适用于需要将复杂的子系统简化为一个统一接口的场景。它通过引入一个外观类,将客户端与子系统的复杂性隔离开来,从而提高了系统的可维护性和易用性。
5.1 门面模式的核心思想
- 简化复杂系统的调用方式:通过引入一个外观类(Facade),将复杂的调用流程封装起来,让客户端只需要与外观类交互。
- 降低耦合度:客户端不直接依赖子系统的具体实现,而是通过外观类间接访问,提高了模块化和可维护性。
5.2 门面模式的主要角色
- 子系统类(SubSystem):实现了系统内部的具体功能。
- 外观类(Facade):提供一个统一的接口,封装子系统的调用逻辑。
- 客户端(Client):使用外观类来完成对子系统的操作。
5.3 门面模式的代码案例
// 子系统类 A
class SubSystemA {public void operationA() {System.out.println("SubSystemA: 操作 A");}
}// 子系统类 B
class SubSystemB {public void operationB() {System.out.println("SubSystemB: 操作 B");}
}// 子系统类 C
class SubSystemC {public void operationC() {System.out.println("SubSystemC: 操作 C");}
}// 外观类
class Facade {private SubSystemA subSystemA;private SubSystemB subSystemB;private SubSystemC subSystemC;public Facade() {this.subSystemA = new SubSystemA();this.subSystemB = new SubSystemB();this.subSystemC = new SubSystemC();}// 高层接口public void operation() {subSystemA.operationA();subSystemB.operationB();subSystemC.operationC();}
}// 客户端
public class Client {public static void main(String[] args) {Facade facade = new Facade();facade.operation(); // 调用外观类的方法}
}
- 输出结果
SubSystemA: 操作 A
SubSystemB: 操作 B
SubSystemC: 操作 C
5.4 门面模式的应用场景
- 简化接口:当需要隐藏复杂的子系统时,可以通过门面模式提供一个更简单的接口。
- 分层架构设计:在分层系统中,每一层通过门面暴露其服务给上层。
- 遗留系统封装:当需要集成旧系统时,可以使用门面模式将其封装成一个新接口。
- 解耦:减少客户端与子系统的直接依赖,提高代码的灵活性。
5.5 门面模式的优缺点
优点
- 简化客户端调用逻辑。
- 降低系统间的耦合度。
- 更容易维护和扩展。
缺点
- 不符合开闭原则:如果新增子系统或修改现有子系统,可能需要修改外观类的代码。
六 桥接模式
- 桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与其具体实现部分分离,使它们可以独立变化。它通过组合的方式替代继承,避免类爆炸的问题。
6.1 桥接模式的主要角色
- 桥接模式包含以下核心角色:
-
Abstraction(抽象类)
- 定义抽象类的接口,并持有对
Implementor
的引用。 - 通常是一个高层次的抽象,不直接实现功能,而是委托给
Implementor
。
- 定义抽象类的接口,并持有对
-
RefinedAbstraction(扩展抽象类)
- 扩展
Abstraction
类的功能,增加更复杂的业务逻辑。
- 扩展
-
Implementor(实现接口)
- 定义实现部分的接口,供
Abstraction
调用。 - 通常是不同平台或方式下的统一接口。
- 定义实现部分的接口,供
-
ConcreteImplementorA / ConcreteImplementorB(具体实现类)
- 实现
Implementor
接口的具体行为。
- 实现
6.2 桥接模式的应用场景
- 避免在两个独立维度上使用多重继承导致类数量爆炸。
- 抽象和其实现都要通过子类扩展时,希望解耦两者的继承关系。
- 运行时可以切换实现的情况。
6.3 桥接模式的代码案例
// Implementor
interface Implementor {void operationImpl();
}// ConcreteImplementorA
class ConcreteImplementorA implements Implementor {public void operationImpl() {System.out.println("ConcreteImplementorA operation");}
}// ConcreteImplementorB
class ConcreteImplementorB implements Implementor {public void operationImpl() {System.out.println("ConcreteImplementorB operation");}
}// Abstraction
abstract class Abstraction {protected Implementor implementor;protected Abstraction(Implementor implementor) {this.implementor = implementor;}public abstract void operation();
}// RefinedAbstraction
class RefinedAbstraction extends Abstraction {public RefinedAbstraction(Implementor implementor) {super(implementor);}public void operation() {System.out.print("RefinedAbstraction: ");implementor.operationImpl();}
}//测试类
public class Client {public static void main(String[] args) {Implementor implA = new ConcreteImplementorA();Implementor implB = new ConcreteImplementorB();Abstraction abstrA = new RefinedAbstraction(implA);Abstraction abstrB = new RefinedAbstraction(implB);abstrA.operation(); // 输出:RefinedAbstraction: ConcreteImplementorA operationabstrB.operation(); // 输出:RefinedAbstraction: ConcreteImplementorB operation}
}
6.4 桥接模式的优缺点
优点
- 解耦抽象与实现:允许两者独立变化。
- 提高可扩展性:新增一个维度只需扩展,不需修改。
- 避免类爆炸问题:通过组合代替继承。
缺点
- 增加系统理解难度:需要正确识别两个独立变化的维度。
- 接口设计要求高:必须提前规划好
Implementor
和Abstraction
的接口。
6.5 桥接模式的经典应用
- 不同操作系统上的图形绘制库(图形是抽象,渲染是实现)。
- 多种支付渠道(微信、支付宝)与多种支付方式(扫码、刷脸)的组合。
- 消息通知系统:消息类型(文本、图片)与发送渠道(短信、邮件、站内信)的组合。
- JDBC(java与数据库交互的API)将java应用程序与不同数据库之间的连接进行解耦。JDBC提供标准的接口作为抽象部分,具体的数据库驱动程序作为实现部分。
- SLF4J日志框架提供Logger,Appender和Formatter。他们三个表示不同的维度。Logger表示日志记录所属的类,Appender表示日志的输出,Formatter表示日志记录的格式。三个维度可以有多种不同的实现,利用桥接模式,可以实现三种维度的任意组合。
七 享元模式
- 享元模式(Flyweight Pattern)是 Java 中常用的结构型设计模式之一,主要用于减少创建和管理大量相似对象时的内存开销。
- 享元模式的核心思想:通过共享技术来有效支持大量细粒度的对象的重用。适用于:对象数量巨大;大部分对象的状态可以外部化(即对象之间差异小)
- 享元对象能做到共享的关键是区分了内部状态和外部状态。
- 内部状态:可以共享,不会随环境的改变而变化
- 外部状态:不可以共享,会随环境的改变而变化
- 例如象棋上棋子的颜色,形状,大小是共享属性(内部状态),而位置是不可共享属性(外部状态)。
7.1 享元模式的主要角色
角色 | 描述 |
---|---|
Flyweight (抽象享元类) | 定义公共接口或抽象类,包含一个operation()方法。 |
ConcreteFlyweight (具体享元类) | 实现抽象享元接口,存储内部状态(intrinsic state)。 |
UnsharedConcreteFlyweight (非共享享元类) | 不参与共享的享元类,通常用于组合结构。 |
FlyweightFactory (享元工厂类) | 负责创建和管理享元对象,通常使用 Map 缓存已创建的享元对象。 |
7.2享元模式的代码实例
7.2.1 抽象享元类
public interface Flyweight {void operation(String extrinsicState); // extrinsicState 是外部状态
}
7.2.2 具体享元类
public class ConcreteFlyweight implements Flyweight {private String intrinsicState; // 内部状态,可共享public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}@Overridepublic void operation(String extrinsicState) {System.out.println("内部状态: " + intrinsicState + ", 外部状态: " + extrinsicState);}
}
7.2.3 享元工厂类
import java.util.HashMap;
import java.util.Map;public 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 int getTotalFlyweights() {return flyweights.size();}
}
7.2.4 客户端调用
public class Client {public static void main(String[] args) {FlyweightFactory factory = new FlyweightFactory();Flyweight f1 = factory.getFlyweight("A");Flyweight f2 = factory.getFlyweight("B");Flyweight f3 = factory.getFlyweight("A"); // 共享已有对象f1.operation("X");f2.operation("Y");f3.operation("Z");System.out.println("实际创建的享元对象数量:" + factory.getTotalFlyweights());}
}
- 输出结果
内部状态: A, 外部状态: X
内部状态: B, 外部状态: Y
内部状态: A, 外部状态: Z
实际创建的享元对象数量:2
7.3享元模式的优缺点
优点
- 节省内存:通过共享对象,减少重复创建对象的数量。
- 提高性能:避免频繁的对象创建与销毁。
缺点
- 对JVM回收不友好,因为工厂类一直保持对享元类的引用,造成享元类在没有任何引用的情况下也不会被JVM回收。
7.4 享元模式的适用场景
- 系统中存在大量相似对象,如字符、图形、连接池等。需要将对象的状态分为内部状态和外部状态:
- 内部状态:不变且可共享(如字体名称)
- 外部状态:随环境变化而变化(如字体大小、颜色)
7.5 享元模式的经典应用
- 文本编辑器中对字符的处理(每个字符作为享元对象)
- 游戏开发中的粒子系统,棋类游戏的棋子对象
- 数据库连接池、线程池等资源池化管理