【设计模式】结构型设计模式总结之代理模式、装饰模式、外观模式、享元模式

文章目录

代理模式

代理模式(Proxy Pattern) 是一种结构型设计模式,它提供了一个代理对象,控制对目标对象的访问。代理对象通常在客户端与目标对象之间起到中介的作用,用于扩展目标对象的功能。

定义:为其他对象提供一种代理以控制对这个对象的访问

示例

定义接口

java 复制代码
// 公共接口
public interface IShop {
    void buy();
}

实现真实类

java 复制代码
// 真实对象
public class Buyer implements IShop{
    @Override
    public void buy() {
        System.out.println("购买");
    }
}

定义代理类

java 复制代码
// 代理类
public class Buying implements IShop{
    private IShop iShop;

    public Buying(IShop iShop) {
        this.iShop = iShop;
    }

    @Override
    public void buy() {
        iShop.buy();
    }
}

测试代码

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        IShop person1 = new Buyer();
        // 创建代理对象
        IShop proxy = new Buying(person1);
        // 使用代理对象
        proxy.buy();
    }
}

结构

Subject(抽象主题):定义目标对象和代理对象的公共接口。

RealSubject(真实主题):实现了Subject接口,定义了具体的业务逻辑。

Proxy(代理):代理对象,包含对真实主题的引用,并且可以在对真实主题的调用前后添加额外的功能。

分类

  1. 静态代理

静态代理是指在编译时就已经确定代理类的实现,通常是手动编写代理类。静态代理的关键特点是代理类与目标类之间有一一对应的关系。

  1. 动态代理

动态代理是在运行时动态生成代理对象,而不是在编译时就明确写好的。Java提供了通过反射机制动态的生成代理对象的机制

动态代理

java提供了java.lang.reflect.InvocationHandler,一个便捷的动态代理接口,实现它要重写其调用方法invoke

java 复制代码
// 目标接口
public interface Subject {
    void request();
}

// 目标类
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("真实主题:处理请求。");
    }
}

// 动态代理的处理器
public class DynamicProxyHandler implements InvocationHandler {
    private Object target;

    public DynamicProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理:在真实主题处理请求之前。");
        Object result = method.invoke(target, args);  // 调用目标方法
        System.out.println("代理:在真实主题处理请求之后。");
        return result;
    }
}

// 测试
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 创建一个真实的目标对象
        RealSubject realSubject = new RealSubject();

        // 使用Proxy创建代理对象
        Subject proxy = (Subject) Proxy.newProxyInstance(
            // 目标类的类加载器
            realSubject.getClass().getClassLoader(),
            
            // 目标类实现的接口
            new Class[] {Subject.class},  
            
            // 代理的处理器(即动态代理的逻辑)
            new DynamicProxyHandler(realSubject)
        );
        
        // 调用代理对象的方法
        proxy.request();
    }
}

装饰模式

定义

动态地向对象添加额外的职责,而不改变其结构。

使用场景

需要透明且动态地扩展类的功能时

示例

java 复制代码
// 抽象组件类
public abstract class Component {
    // 抽象操作方法,由子类实现
    public abstract void operate();
}
java 复制代码
// 具体组件类
public class ConcreteComponent extends Component {
    @Override
    public void operate() {
        System.out.println("执行基本操作");
    }
}
java 复制代码
// 装饰者基类
public abstract class Decorator extends Component {
    protected Component component; // 持有组件对象的引用

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        component.operate(); // 调用组件的操作
    }
}
java 复制代码
// 具体装饰者实现类
public class ConcreteDecoratorA extends Decorator {
    public LoggingDecorator(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        System.out.println("开始");
        super.operate(); // 调用原始操作
        System.out.println("结束");
    }
}
java 复制代码
public class DecoratorPatternDemo {
    public static void main(String[] args) {
        // 创建具体组件
        Component component = new ConcreteComponent();

        // 根据组件对象构造装饰者componentA并调用
        Component componentA = new ConcreteDecoratorA(component);
        componentA.operate();
    }
}

结构

  1. 抽象组件(Component): 一个接口或抽象类,被装饰的原始对象
  2. 具体组件(ConcreteComponent): 抽象组件的具体实现,是被装饰的核心对象
  3. 抽象装饰者(Decorator) : 抽象类或接口,持有一个组件对象的引用,并定义与组件一致的接口
  4. 具体装饰者(ConcreteDecorator): 抽象装饰者实现类,对抽象装饰者做出具体的实现

使用场景

  1. 动态扩展一个类的功能。
  2. 替代多层次的继承结构。
  3. 当不能直接修改类或不希望影响其他对象时。

与代理模式区别

装饰模式

  • 目的:装饰模式用于动态地扩展一个对象的功能,且对客户端透明。它是继承关系的替代方案,可以通过包装原对象并为其添加新的功能,而不改变原对象的结构。
  • 使用场景:当你需要扩展对象的功能时,不希望直接修改原有的类时

代理模式

  • 目的:代理模式用于为其他对象提供一个替代品或代理对象,以便通过代理对象来控制对原对象的访问。代理对象本身不增强原对象的功能,而是控制访问、延迟加载、安全检查等。
  • 使用场景:当你希望控制对某个对象的访问(例如,延迟加载、访问权限控制、日志记录等)时,可以使用代理模式。

装饰模式强调对对象功能的增强和扩展。

代理模式关注对对象的访问控制、管理、替代。

核心区别:装饰模式是为了增强原对象的功能,而代理模式是为了控制原对象的访问。

Context

java 复制代码
// 抽象组件
public abstract class Context {
    public abstract void startActivity(@RequiresPermission Intent intent);
    public void startActivity(Intent intent, Bundle options);
}
java 复制代码
// 具体组件实现类
class ContextImpl extends Context{
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }
    
    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
            && (targetSdkVersion < Build.VERSION_CODES.N
                || targetSdkVersion >= Build.VERSION_CODES.P)
            && (options == null
                || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
            throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity"
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity) null, intent, -1, options);
    }
}
java 复制代码
// 装饰者,持有ContextImpl的对象
public class ContextWrapper extends Context {
    Context mBase;

    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }

    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        mBase.startActivity(intent, options);
    }
}
java 复制代码
Activity extends ContextThemeWrapper
ContextThemeWrapper extends ContextWrapper

Activity就是具体装饰者

ContextImpl的创建和ContextWrapperContextImpl引用在ActivityThreadmain函数中进行

外观模式

外观模式(Facade Pattern)是一种结构型设计模式,它为复杂的子系统提供一个简单的接口。

目的是简化系统的使用方式,使得调用者可以通过一个统一的入口来访问系统中的多个子系统,而不需要关心子系统的内部实现细节。

结构

外观模式通常包含以下几个角色:

  1. Facade(外观类):提供一个简化的接口,委托请求给子系统。

  2. Subsystem(子系统类):各个独立的子系统,完成具体的业务逻辑。

示例

java 复制代码
// 外观类:简化多个功能操作的接口
class SmartphoneFacade {
    private Camera camera = new Camera();
    private MusicPlayer musicPlayer = new MusicPlayer();

    public void takePhotoAndPlayMusic() {
        camera.open();
        camera.takePhoto();
        musicPlayer.play();
    }

    public void stopMusicAndCloseCamera() {
        musicPlayer.stop();
        camera.open(); 
    }
}

// 子系统相机
class Camera {
    public void open() {
        System.out.println("打开相机");
    }

    public void takePhoto() {
        System.out.println("拍照");
    }
}
// 子系统音乐播放器类似

// 客户端
public class FacadePatternExample {
    public static void main(String[] args) {
        SmartphoneFacade smartphone = new SmartphoneFacade();

        smartphone.takePhotoAndPlayMusic();
        smartphone.stopMusicAndCloseCamera();
    }
}

使用手机只需要调用相关方法,而不用去管CameraMusicPlayer的具体实现

使用场景

  • 当系统较为复杂时,使用外观模式可以简化与子系统的交互,提供一个更易于使用的接口。
  • 为多个子系统提供一个统一的接口。
  • 需要解耦子系统与外部代码的依赖关系。

优点

  • 简化接口:隐藏了系统的复杂性,提供了更简单的接口。
  • 降低耦合:客户端与子系统之间的耦合度降低,修改子系统的实现不会影响到客户端。

Context

Context封装了很多重要的操作,如 startActivitysendBroadcastbindService 等。因此,Context对开发者来说是最重要的高层接口。Context只是一个定义了很多接口的抽象类,这些接口的功能实现并不是在Context及其子类中,而是通过其他子系统来完成。

Context只是一个抽象类,它的真正实现在Contextlmpl类中,Contextlmpl就是外观类。

  1. startActivity()startActivity() 方法启动一个新的 Activity,但实际的启动过程是通过 ActivityManagerService 来完成的。
  2. sendBroadcast() :底层的实现则是通过 BroadcastManager 来处理。
  3. bindService()bindService() 方法允许应用与服务建立连接,实际操作是通过 ServiceManager 来管理服务的绑定。

享元模式

享元模式用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,来缓存可共享的对象,达到对象共享、避免创建过多对象的效果,可以提升性能、避免内存移除等。

核心思想是++复用已经存在的对象,而不是每次都创建新对象。++
享元对象中的部分状态是可以共享,可以共享的状态成为内部状态,内部状态不会随着环境变化;不可共享的状态则称为外部状态,它们会随着环境的改变而改变。

在享元模式中会建立一个对象容器,在经典的享元模式中该容器为一个 Map,它的键是享元对象的内部状态,它的值就是享元对象本身。客户端程序通过这个内部状态从享元工厂中获取享元对象,如果有缓存则使用缓存对象,否则创建一个享元对象并且存入容器中,这样一来就避免了创建过多对象的问题。

结构

  1. 抽象享元(Flyweight):定义享元对象的基类或接口
  2. 具体享元(ConcreteFlyweight):实现抽象享元对象
  3. 享元工厂(FlyweightFactory):用于管理享元对象的创建和共享,确保享元对象的复用。

示例

在火车票预订系统中,有很多用户会购买相同类型、相同时间、相同座位的火车票。每次有用户购买相同的火车票时,我们不需要为每个用户创建一个新的火车票对象,而是可以共享相同的火车票对象。享元模式可以帮助我们避免为每个用户创建重复的对象,从而节省内存。

享元对象(Flyweight)

TrainTicket 类是享元对象,表示火车票的固定信息,例如车次、出发时间、座位类型等。

java 复制代码
// 享元类:火车票
public class TrainTicket {
    private String trainNumber; // 车次
    private String departureTime; // 出发时间

    // 构造方法,初始化火车票的固定信息
    public TrainTicket(String trainNumber, String departureTime) {
        this.trainNumber = trainNumber;
        this.departureTime = departureTime;
    }

    // 购票
    public void serve(String passengerName) {
        System.out.println("乘车人:" + passengerName + " ,车次 " + trainNumber +
                           ",发车时间: " + departureTime);
    }
}

享元工厂(FlyweightFactory)

TrainTicketFactory 类是享元工厂,用于管理和共享火车票对象。

java 复制代码
public class TrainTicketFactory {
    private Map<String, TrainTicket> ticketMap = new HashMap<>();

    public TrainTicket getTrainTicket(String trainNumber, String departureTime) {
        String key = trainNumber + departureTime;
        if (!ticketMap.containsKey(key)) {
            ticketMap.put(key, new TrainTicket(trainNumber, departureTime));
            System.out.println("购票成功,乘车人:" + trainNumber + ", 车次: " + departureTime);
        }
        return ticketMap.get(key); // 返回共享的火车票对象
    }
}

客户端

java 复制代码
public class TrainStation {
    public static void main(String[] args) {
        TrainTicketFactory ticketFactory = new TrainTicketFactory();

        TrainTicket ticket1 = ticketFactory.getTrainTicket("G101", "10:00");
        TrainTicket ticket2 = ticketFactory.getTrainTicket("G101", "10:00");
        TrainTicket ticket3 = ticketFactory.getTrainTicket("D202", "14:00");

        ticket1.serve("Alice");
        ticket2.serve("Bob");
        ticket3.serve("Charlie");
    }
}
  • TrainTicket 类是享元对象,火车票的固定信息,相同的,可以被多个乘客共享。
  • TrainTicketFactory 类是享元工厂,维护了一个火车票对象池,确保每种车次、出发时间和座位类型的火车票只创建一次。如果有相同的请求,返回已有的火车票对象。
  • TrainStation 类模拟了多个乘客购买相同车次、相同时间的火车票。由于使用了享元模式,虽然有多个乘客,但共享了相同的火车票对象。

使用场景

  1. 对象创建代价高,且每个对象的内部状态差别不大。
  2. 需要优化程序性能,减少内存消耗。

Message

在Handler中,使用对象池 来管理 Message 对象,能够有效避免频繁的对象创建,减少内存占用和GC频率。

java 复制代码
public final class Message implements Parcelable {
    Message next;
    // 同步锁的对象
    public static final Object sPoolSync = new Object();
    // 对象池的头部,具体实现是链表
    private static Message sPool;
    // 当前池中存储的 Message 对象数量
    private static int sPoolSize = 0;
    // 对象池的最大容量
    private static final int MAX_POOL_SIZE = 50;

    public static Message obtain() {
        // 线程安全
        synchronized (sPoolSync) {
            if (sPool != null) {
                // 从池中取出一个 Message 对象
                Message m = sPool;
                // 更新池的头部为下一个对象
                sPool = m.next;
                // 清空当前 Message 对象的链表连接,避免不必要的引用
                m.next = null;
                // 清除消息的标志位,表示该对象已被重用
                m.flags = 0;
                // 更新池中存储的对象数量
                sPoolSize--;
                return m;
            }
        }
        // 如果池中没有可复用的对象,创建一个新的 Message 对象并返回
        return new Message();
    }
    
   /**
     * 回收该 Message 对象,准备将其放入对象池中。
     * 该方法会检查 Message 是否还在使用中,若仍在使用则抛出异常。
     */
    public void recycle() {
        if (isInUse()) {  
            if (gCheckRecycle) {  
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;  
        }
        recycleUnchecked();
    }

    /**
     * 实际进行回收操作,将该对象状态清空,并加入对象池中以供复用。
     */
    void recycleUnchecked() {
        // 清除对象的各个字段,确保该对象回收后不再持有任何引用
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {  
            if (sPoolSize < MAX_POOL_SIZE) {  
                next = sPool;  
                sPool = this;  
                sPoolSize++;  
            }
        }
    }
}

Message 类承担了三个职责:

  1. Flyweight 抽象角色:提供了统一的接口来处理对象的共享和复用。
  2. ConcreteFlyweight 具体享元角色:实际存储对象的状态并处理业务逻辑。
  3. FlyweightFactory 工厂角色 :管理对象池,复用和回收 Message 对象。


参考:

  1. 《设计模式之禅》
  2. 《Android进阶之光》
  3. 《Android源码设计模式解析与实战》
相关推荐
O(1)的boot36 分钟前
微服务的问题
java·数据库·微服务
一个略懂代码的程序员39 分钟前
Redis01
java·redis
IT界的奇葩43 分钟前
基于springboot使用Caffeine
java·spring boot·后端·caffeine
西埃斯迪恩-霖1 小时前
Idea导入SpringBoot3.2.x源码
java·ide·intellij-idea
m0_748251722 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
潘多编程2 小时前
Spring Boot性能提升:实战案例分析
java·spring boot·后端
m0_748256142 小时前
Spring Boot 整合 Keycloak
java·spring boot·后端
#HakunaMatata2 小时前
Java 中 List 接口的学习笔记
java·学习·list
Ase5gqe2 小时前
Spring Boot中实现JPA多数据源配置指南
java
AI人H哥会Java2 小时前
【JAVA】Java高级:多数据源管理与Sharding:在Spring Boot应用中实现多数据源的管理
java·开发语言