包装器模式:用于封装对象,提供统一的接口,隐藏底层复杂性。
包装器模式(Wrapper Pattern)
定义
包装器模式通过创建包装类来封装原始对象,提供统一的接口来访问(get方法
)和操作(set方法
)对象的属性,同时隐藏底层复杂逻辑。
应用场景
MetaObject
包装复杂对象,统一访问和操作接口。- 适配对象的不同形式(如 Java Bean、
Map
、集合等)。
示例代码
以下是一个简单的包装器模式示例:
typescript
// 原始对象
class OriginalObject {
private String name;
public OriginalObject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 包装器类
class WrapperObject {
private OriginalObject original;
public WrapperObject(OriginalObject original) {
this.original = original;
}
public String getProperty(String propertyName) {
if ("name".equals(propertyName)) {
return original.getName();
}
throw new IllegalArgumentException("未知属性: " + propertyName);
}
public void setProperty(String propertyName, String value) {
if ("name".equals(propertyName)) {
original.setName(value);
return;
}
throw new IllegalArgumentException("未知属性: " + propertyName);
}
}
// 客户端代码
public class WrapperPatternExample {
public static void main(String[] args) {
OriginalObject obj = new OriginalObject("约翰");
WrapperObject wrapper = new WrapperObject(obj);
System.out.println("原始名称: " + wrapper.getProperty("name"));
wrapper.setProperty("name", "多伊");
System.out.println("更新后的名称: " + wrapper.getProperty("name"));
}
}
包装器模式的使用场景
包装器模式(Wrapper Pattern)常用于对现有对象进行功能增强或接口适配,通过一个封装类隐藏复杂的实现细节,并提供简洁统一的访问方式。
1. 属性访问和操作
包装器模式可以在属性访问中提供一种统一的方式,隐藏底层的差异性。例如:
- 操作
JavaBean
、Map
和集合等不同类型的对象。 - 动态代理属性访问,如通过反射或其他动态机制实现。
2. 增强功能
对原始对象添加新功能而不影响其原有行为。例如:
- 给类添加日志功能。
- 监控类的调用次数或时间。
3. 适配和解耦
将现有的接口转换为客户端需要的接口,隐藏不兼容接口之间的差异。
4. 延迟加载或惰性初始化
通过包装对象来实现资源的按需加载,避免不必要的性能开销。
源码中使用包装器模式的例子
1. MyBatis
MyBatis 使用包装器模式对对象的属性访问和操作进行封装。
- MetaObject
示例:
ini
MetaObject metaObject = SystemMetaObject.forObject(someObject);
metaObject.setValue("propertyName", "value");
Object value = metaObject.getValue("propertyName");
-
MetaObject
是 MyBatis 中对原始对象的包装器。- 提供了一套通用的 API 来访问和操作对象属性,隐藏了底层反射操作的复杂性。
- 实现类:
BeanWrapper
、MapWrapper
、CollectionWrapper
-
-
- 根据对象类型(如 JavaBean、
Map
或集合),选择不同的包装策略。 - 通过策略模式和包装器模式结合实现。
- 根据对象类型(如 JavaBean、
-
2. Java IO 类库
Java 的 IO 流类库中广泛使用包装器模式来扩展功能。
- 输入流和输出流
示例:
ini
FileInputStream fileInputStream = new FileInputStream("example.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
-
BufferedReader
对Reader
进行包装,提供了缓冲功能和高级方法(如readLine()
)。DataInputStream
对InputStream
进行包装,提供了读取 Java 基本数据类型的方法。
- 功能增强的层次化封装
通过链式调用,可以对一个流对象进行多层封装,逐步增强其功能。
3. Spring 框架
Spring 中也大量使用了包装器模式。
- AOP(面向切面编程)
示例:
ini
ProxyFactory factory = new ProxyFactory(new TargetClass());
factory.addAdvice(new LoggingAdvice());
TargetClass proxy = (TargetClass) factory.getProxy();
-
- Spring 的 AOP 使用动态代理对目标对象进行包装。
- 通过代理对象提供增强功能(如日志、事务处理等)。
BeanWrapper
-
BeanWrapper
是 Spring 中用于封装 JavaBean 的类。- 提供对 JavaBean 属性的统一访问方式。
4. Hibernate
Hibernate 使用包装器模式对数据实体进行管理。
- Lazy Initialization Proxy
-
- Hibernate 使用代理模式和包装器模式结合实现延迟加载。
- 在访问数据实体的某些属性时,代理类会拦截调用,延迟加载这些属性。
5. Guava Cache
Guava 的缓存工具类使用包装器模式来封装缓存对象,提供了一种简化和增强的访问方式。
CacheLoader
和LoadingCache
-
CacheLoader
是一个包装器,用于实现延迟加载和数据获取的逻辑。LoadingCache
是对缓存功能的封装,提供了线程安全和高效的缓存操作。
总结 属性访问和操作
示例 1:统一访问 JavaBean 和 Map 属性
使用包装器封装 JavaBean
和 Map
,提供一致的访问接口。
typescript
// 定义一个接口,提供统一的属性访问方法
interface PropertyAccessor {
Object getProperty(String propertyName);
void setProperty(String propertyName, Object value);
}
// JavaBean 的包装器
class BeanPropertyAccessor implements PropertyAccessor {
private Object bean;
public BeanPropertyAccessor(Object bean) {
this.bean = bean;
}
@Override
public Object getProperty(String propertyName) {
try {
var field = bean.getClass().getDeclaredField(propertyName);
field.setAccessible(true);
return field.get(bean);
} catch (Exception e) {
throw new RuntimeException("属性访问失败", e);
}
}
@Override
public void setProperty(String propertyName, Object value) {
try {
var field = bean.getClass().getDeclaredField(propertyName);
field.setAccessible(true);
field.set(bean, value);
} catch (Exception e) {
throw new RuntimeException("属性设置失败", e);
}
}
}
// Map 的包装器
class MapPropertyAccessor implements PropertyAccessor {
private Map<String, Object> map;
public MapPropertyAccessor(Map<String, Object> map) {
this.map = map;
}
@Override
public Object getProperty(String propertyName) {
return map.get(propertyName);
}
@Override
public void setProperty(String propertyName, Object value) {
map.put(propertyName, value);
}
}
// 使用示例
public class PropertyAccessExample {
public static void main(String[] args) {
// 使用 JavaBean 包装器
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Person person = new Person();
PropertyAccessor beanAccessor = new BeanPropertyAccessor(person);
beanAccessor.setProperty("name", "张三");
System.out.println("Bean Name: " + beanAccessor.getProperty("name"));
// 使用 Map 包装器
Map<String, Object> map = new HashMap<>();
PropertyAccessor mapAccessor = new MapPropertyAccessor(map);
mapAccessor.setProperty("name", "李四");
System.out.println("Map Name: " + mapAccessor.getProperty("name"));
}
}
增强功能
示例 2:为类添加日志功能
使用包装器为方法调用添加日志功能。
csharp
// 原始服务接口
interface Service {
void execute();
}
// 原始服务实现
class RealService implements Service {
@Override
public void execute() {
System.out.println("执行原始服务逻辑");
}
}
// 包装器服务
class LoggingServiceWrapper implements Service {
private Service realService;
public LoggingServiceWrapper(Service realService) {
this.realService = realService;
}
@Override
public void execute() {
System.out.println("日志:开始执行服务...");
realService.execute();
System.out.println("日志:服务执行结束");
}
}
// 使用示例
public class LoggingExample {
public static void main(String[] args) {
Service service = new LoggingServiceWrapper(new RealService());
service.execute();
}
}
输出示例:
erlang
日志:开始执行服务...
执行原始服务逻辑
日志:服务执行结束
适配和解耦
示例 3:适配不兼容的接口
将现有接口适配为客户端需要的接口。
csharp
// 客户端期望的接口
interface Target {
void request();
}
// 不兼容的接口
class Adaptee {
public void specificRequest() {
System.out.println("调用适配者的特定请求");
}
}
// 适配器
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 将客户端调用转发给适配者
adaptee.specificRequest();
}
}
// 使用示例
public class AdapterExample {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target adapter = new Adapter(adaptee);
adapter.request();
}
}
输出示例:
调用适配者的特定请求
延迟加载或惰性初始化
示例 4:延迟加载资源
通过包装器实现按需加载资源。
kotlin
// 数据接口
interface Data {
String fetchData();
}
// 实际的数据类
class RealData implements Data {
private String data;
public RealData() {
// 模拟耗时加载过程
System.out.println("加载数据中...");
this.data = "实际数据";
}
@Override
public String fetchData() {
return data;
}
}
// 包装器实现延迟加载
class LazyData implements Data {
private RealData realData;
@Override
public String fetchData() {
if (realData == null) {
realData = new RealData(); // 延迟加载
}
return realData.fetchData();
}
}
// 使用示例
public class LazyLoadingExample {
public static void main(String[] args) {
Data data = new LazyData();
System.out.println("数据尚未加载");
System.out.println("获取数据: " + data.fetchData());
System.out.println("再次获取数据: " + data.fetchData());
}
}
输出示例:
makefile
数据尚未加载
加载数据中...
获取数据: 实际数据
再次获取数据: 实际数据
总结
- 属性访问和操作 :通过包装提供统一的接口访问不同类型的对象属性。
- 增强功能 :在不改变原始类的基础上,为其增加功能(如日志、监控)。
- 适配和解耦 :在不修改现有代码的情况下,适配不兼容的接口。
- 延迟加载 :通过按需加载减少性能开销,提升资源利用效率。
包装器模式在这些场景下,通过解耦、功能增强和延迟初始化,使代码更易维护和扩展。
包装器模式的使用在源码中非常普遍,主要思想是通过对现有对象的封装,增强功能或简化访问。在现代框架(如 MyBatis、Spring、Hibernate)以及核心类库(如 Java IO、Guava Cache)中,包装器模式都有广泛的应用。它解决了以下问题:
- 统一接口:隐藏底层实现细节,提供一致的操作方式。
- 增强功能:通过包装扩展原始对象的功能。
- 适配差异:适配不同的对象类型或接口。
这种模式的灵活性和解耦特性使其成为开发中不可或缺的设计模式之一。