设计模式⑨ :避免浪费

文章目录

  • 一、前言
  • [二、Flyweight 模式](#二、Flyweight 模式)
    • [1. 介绍](#1. 介绍)
    • 2.应用
    • [3. 总结](#3. 总结)
  • [三、Proxy 模式](#三、Proxy 模式)
    • [1. 介绍](#1. 介绍)
    • 2.应用
    • [3. 总结](#3. 总结)

一、前言

有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。

二、Flyweight 模式

Flyweight 模式:共享对象,避免浪费

1. 介绍

Flyweight 即轻量级的意思,如通过尽量共享实例来避免 new 出实例,占用更少的内存的思想。


Flyweight 模式 登场的角色

  • Flyweight (轻量级) : 表示那些可以被共享的类。
  • FlyweightFactory (轻量级工厂) :FlyweightFactory 表示生成 Flyweight 角色的工厂,在 FlyweightFactory 中生成的 Flyweight 角色可以共享。
  • Client (请求者) : Client 使用 FlyweightFactory 来生成 Flyweight 角色。

类图如下:


2.应用

  • Integer 在类加载的时候会创建缓存对象,范围从 -128 到 127,当我们通过 Integer#valueOf 方法创建一个 Integer 对象时,Integer 会首先判断当前要创建的 Integer 是否在缓存中,如果在则直接从缓存中获取对象。

    java 复制代码
        public static Integer valueOf(int i) {
        	// 判断是否在缓存范围内,如果在则直接返回缓存的对象。
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
  • Dubbo SPI 在加载各个 SPI 接口时是懒加载的,即使用到才进行加载。并且Dubbo会为每一个SPI 接口生成一个适配器 ExtensionLoader,但这个适配器并不是写好的,而是动态编译生成的,因此为了避免每次获取 SPI 接口时都需要重新创建 ExtensionLoader 对象,便对 ExtensionLoader 进行了缓存以供复用,如下:

    java 复制代码
    	// 在 Dubbo 中,每个扩展接口对应自己的ExtensionLoader,key为扩展接口的Class 对象,value为对应的ExtensionLoader
        private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    
        public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
            if (type == null) {
                throw new IllegalArgumentException("Extension type == null");
            }
            if (!type.isInterface()) {
                throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
            }
            // 没有 SPI 注解修饰抛出异常
            if (!withExtensionAnnotation(type)) {
                throw new IllegalArgumentException("Extension type(" + type +
                        ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
            }
    		// 获取 type 对应的 ExtensionLoader对象,如果没有,则创建一个。
            ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            if (loader == null) {
                EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
                loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            }
            return loader;
        }


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在下面 Proxy 模式的举例中的 PROXY_CACHE 即是缓存了对象并复用。

3. 总结

扩展思路:

  • 如果 Flyweight 对象被共享,那么就需要注意当改变被共享的对象时会对多个地方都产生影响。因此对于共享的对象需要进行慎重选择。
  • 需要区分可以共享的信息和不可共享的信息。
  • 不要让被共享的实力被垃圾回收器回收了。
  • 共享对象可以节省的不仅仅是内存资源,也可以减少创建新对象时消耗的时间资源。

相关模式:

  • Proxy 模式: 如果生成实例的处理花费时间较长,则可以通过 Flyweight 提高个程序的处理速度。而 Proxy 则是通过设置代理提高程序的处理速度。
  • Composite 模式:有时候可以使用 Flyweight 模式共享 Composite 模式中的 Leaf 对象。
  • Singleton 模式:在 FlyweightFactory 中的角色有时会使用 Singleton 模式

一时的小想法,仅仅个人理解,无需在意 :

  • Redis 中基于引用计数属性还实现了对象共享功能:Redis 会在初始化服务器时,创建一万个字符串对象, 从 0-9999,当服务器需要使用0-9999的字符串对象时,服务器就会使用这些共享对象, 而不是新创建的对象。

三、Proxy 模式

Proxy 模式:只在必要时生成实例。

1. 介绍

Proxy 即 代理人,指的是代替别人进行工作的人。


Proxy 模式登场的角色:

  • Subject(主体):Subject 角色定义了使 Proxy 角色和 RealSubject 角色之间具有一致性的接口。由于存在 Subject 角色,可以 Client 角色可以不用在意他使用的就就是 Proxy 还是 RealSubject 角色。
  • Proxy(代理人) : Proxy 角色会进来处理来自 Client 角色的请求。当自己无法处理时会交由RealSubject 角色。Proxy 角色只有在必要的时候才生成 RealSubject,Proxy 角色实现了在 Subject 角色中定义的接口 。
  • RealSubject (实际的主体) :RealSubject 会在 Proxy 无法胜任工作的时候出场。它与 Proxy一样,也实现了Subject 角色中定义的接口。
  • Client (请求者) :使用 Proxy 模式的角色。

类图如下:


Demo 如下: 对于超级明星 XycSuperStar ,对金钱没有执着,多少出场费都可以,而此时通过 Broker 经纪人来对一些功能进行扩展,如限制出场费太低不同意出场,而 Broker 无法代替 SuperStar 唱歌,所以需要 SuperStar 自己亲自唱歌。

java 复制代码
// 超级明星:可以签约唱歌
public interface SuperStar {
    /**
     * 签约
     */
    boolean signContract(int pay);

    /**
     * 唱歌
     */
    void sing();
}

// 某一位超级明星
public class XycSuperStar implements SuperStar {
    @Override
    public boolean signContract(int pay) {
        System.out.println("我夏义春同意签约, 出场费: " + pay + "元");
        return true;
    }

    @Override
    public void sing() {
        System.out.println("唱一首名为《夏义春》的歌曲");
    }
}

// 明星经纪人
public class Broker implements SuperStar {

    /**
     * 超级明星
     */
    private SuperStar superStar;

    public Broker(SuperStar superStar) {
        this.superStar = superStar;
    }

    @Override
    public boolean signContract(int pay) {
        // 经纪人代理, 出场费比较少时不签约
        if (pay < 100000) {
            System.out.println("出场费 " + pay + " 太少, 对不起身价");
            return false;
        }
        return superStar.signContract(pay);

    }

    @Override
    public void sing() {
        // 经纪人无法完成, 交由明星自己来唱
        superStar.sing();
    }
}

```java
public class ProxyDemoMain {
    public static void main(String[] args) {
        XycSuperStar xycSuperStar = new XycSuperStar();
        Broker broker = new Broker(xycSuperStar);

        int pay = 100;
        while (!broker.signContract(pay)) {
            pay *= 10;
        }
        broker.sing();
    }
}

输出如下:

2.应用

  • Spring 中的 AOP、事务注解等功能都是用了 Proxy 模式对原始内容进行了代理。以及上面提到的 Dubbo SPI 也使用到了代理。Spring Aop 的代理对象创建在 CglibAopProxy#getProxy(java.lang.ClassLoader),这里不再赘述,如有需要,详参:Spring源码分析二十四 : cglib 的代理过程


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 项目A中在开始的时候安排了两个人开发,一个人负责订单基本操作开发了OrderService,另一个人负责订单扩展操作开发了OtherService。而订单类型的种类有十几种,也就是说每个 OrderService 和 OtherService 都是一一对应的并且存在十几组,因此想将这两个 Service 合并。如下:通过 AggregateFactory 对 OrderService 和 OtherService 进行代理,生成代理对象 AggregateService 并缓存到 PROXY_CACHE 中,外层调用时无需考虑方法是 OrderService 还是 OtherService 实现,直接调用 AggregateService 对象即可。

    java 复制代码
    public interface OrderService {
    
        /**
         * 做一些订单操作
         * @return
         */
        String doSomeOrder();
    }
    
    public interface OtherService {
        /**
         * 做一些其他操作
         * @return
         */
        String doSomeOther();
    }
    
    // 聚合服务
    public interface AggregateService extends OrderService, OtherService {
    }
    
    public class AggregateFactory {
        /**
         * 聚合缓存
         */
        private static final Map<String, AggregateService> PROXY_CACHE = Maps.newConcurrentMap();
    
        /**
         * 获取聚合服务
         *
         * @param scheme
         * @return
         */
        public AggregateService getAggregateService(String scheme) {
            final OrderService orderService = getOrderService(scheme);
            final OtherService otherService = getOtherService(scheme);
            return PROXY_CACHE.computeIfAbsent(scheme, new Function<String, AggregateService>() {
                @Override
                public AggregateService apply(String scheme) {
                	// 不存在则创建代理
                    return (AggregateService) Proxy.newProxyInstance(this.getClass().getClassLoader(),
                            new Class[]{AggregateService.class}, (proxy, method, args) -> {
                                if (method.getDeclaringClass().isAssignableFrom(OrderService.class)) {
                                    return method.invoke(orderService, args);
                                } else if (method.getDeclaringClass().isAssignableFrom(OtherService.class)) {
                                    return method.invoke(otherService, args);
                                } else {
                                    throw new RuntimeException();
                                }
                            });
                }
            });
        }
    
        /**
         * 获取当前场景的 OtherService
         *
         * @param scheme
         * @return
         */
        public OrderService getOrderService(String scheme) {
            // TODO : 从缓存中获取不同场景的实现类
            return null;
        }
    
        /**
         * 获取当前场景的 OtherService
         *
         * @param scheme
         * @return
         */
        public OtherService getOtherService(String scheme) {
            // TODO : 从缓存中获取不同场景的实现类
            return null;
        }
    }

3. 总结

扩展思路:

  • 使用代理人来提升处理速度 : 即 Bean 懒加载,在 proxy 需要使用 Subject 时才初始化。
  • 划分代理人和被人的意义 :使得 Proxy 和 Subject 解耦,成为两个组件。
  • 代理和委托:代理只代理他能解决的问题。当遇到不能解决的问题时,还是会转交(委托)给本人去解决。
  • 透明性:由于 Proxy 和 RealSubject 都实现了 Subject 的接口,所以对于 Client 无需考虑调用的究竟是 Proxy 还是 RealSubject。

各种 Proxy 模式 :

  1. Virtual Proxy(虚拟代理):即本文提到的 Proxy 模式,最常见的本地代理模式。
  2. Remote Proxy(远程代理):对于 RealSubject 对象,有时其具体实现在远程网络上,通过 Remote Proxy 代理后可以透明的调用他的方法。Java 的 RMI (远程方法调用)就相当于 Remote Proxy。
  3. Access Proxy :用于在调用 RealSubject 角色的功能时设置访问限制。如对某些接口进行权限或次数限制等。

相关设计模式:

  • Adapter 模式 :Adapter 模式适配了两种具有不同接口的对象,使得它们可以一同工作,而在 Proxy 模式中,Proxy角色与RealSubject 角色的接口是相同的。
  • Decorator 模式 :Decorator 模式与 Proxy 模式在实现上很相似,但是目的不同:Decorator 模式的目的在于增加新的功能。而在 Proxy 中,与增加新功能想必,它更注重通过设置代理人的方式来减轻本人的工作负担。
相关推荐
c++之路14 分钟前
C++20概述
java·开发语言·c++20
Championship.23.2418 分钟前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮33 分钟前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken39 分钟前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
冷雨夜中漫步1 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
直奔標竿1 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring
one_love_zfl2 小时前
java面试-微服务组件篇
java·微服务·面试
一只大袋鼠2 小时前
Java进阶:CGLIB动态代理解析
java·开发语言
环流_2 小时前
HTTP 协议的基本格式
java·网络协议·http
爱滑雪的码农2 小时前
Java基础十三:Java中的继承、重写(Override)与重载(Overload)详解
java·开发语言