包装器模式

包装器模式:用于封装对象,提供统一的接口,隐藏底层复杂性。

包装器模式(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. 属性访问和操作

包装器模式可以在属性访问中提供一种统一的方式,隐藏底层的差异性。例如:

  • 操作 JavaBeanMap 和集合等不同类型的对象。
  • 动态代理属性访问,如通过反射或其他动态机制实现。

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 或集合),选择不同的包装策略。
      • 通过策略模式和包装器模式结合实现。

2. Java IO 类库

Java 的 IO 流类库中广泛使用包装器模式来扩展功能。

  • 输入流和输出流

示例:

ini 复制代码
FileInputStream fileInputStream = new FileInputStream("example.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
    • BufferedReaderReader 进行包装,提供了缓冲功能和高级方法(如 readLine())。
    • DataInputStreamInputStream 进行包装,提供了读取 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 复制代码
数据尚未加载
加载数据中...
获取数据: 实际数据
再次获取数据: 实际数据

总结

  1. 属性访问和操作 :通过包装提供统一的接口访问不同类型的对象属性。
  2. 增强功能 :在不改变原始类的基础上,为其增加功能(如日志、监控)。
  3. 适配和解耦 :在不修改现有代码的情况下,适配不兼容的接口。
  4. 延迟加载 :通过按需加载减少性能开销,提升资源利用效率。

包装器模式在这些场景下,通过解耦、功能增强和延迟初始化,使代码更易维护和扩展。

包装器模式的使用在源码中非常普遍,主要思想是通过对现有对象的封装,增强功能或简化访问。在现代框架(如 MyBatis、Spring、Hibernate)以及核心类库(如 Java IO、Guava Cache)中,包装器模式都有广泛的应用。它解决了以下问题:

  1. 统一接口:隐藏底层实现细节,提供一致的操作方式。
  2. 增强功能:通过包装扩展原始对象的功能。
  3. 适配差异:适配不同的对象类型或接口。

这种模式的灵活性和解耦特性使其成为开发中不可或缺的设计模式之一。

相关推荐
raoxiaoya1 分钟前
golang中的值传递与引用传递,如何理解结构体的方法?为什么 T 和 *T 有不同的方法集?
开发语言·后端·golang
一丝晨光18 分钟前
苹果电脑可以安装windows操作系统吗?Mac OS X/OS X/macOS傻傻分不清?macOS系统的Java支持?什么是macOS的五大API法王?
java·windows·macos·objective-c·cocoa·posix·x11
王中阳Go27 分钟前
又遇百度,能否 hold 住?
后端·go
GreedySnaker30 分钟前
Qt-chart 画折线图(文字x轴)
java·数据库·qt
唐梓航-求职中33 分钟前
leetcode-146.LRU缓存(易理解)
java·leetcode·缓存
顽疲40 分钟前
从零用java实现 小红书 springboot vue uniapp (3)详情页优化
java·vue.js·spring boot·uniapp
野蛮的大西瓜43 分钟前
大模型呼出机器人如何赋能呼叫中心?(转)
java·人工智能·语言模型·自然语言处理·机器人·开源·信息与通信
忘却的纪念1 小时前
基于SpringBoot的嗨玩旅游网站:一站式旅游信息服务平台的设计与实现
java·开发语言·spring boot·后端·毕业设计·旅游
野蛮的大西瓜1 小时前
大模型呼出机器人详解
java·人工智能·语言模型·自然语言处理·机器人·信息与通信
java1234_小锋1 小时前
MyBatis与JPA有哪些不同?
java·开发语言·mybatis