设计模式⑨ :避免浪费

一、前言

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

二、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 是否在缓存中,如果在则直接从缓存中获取对象。

        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 进行了缓存以供复用,如下:

    	// 在 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 自己亲自唱歌。

// 超级明星:可以签约唱歌
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 对象即可。

    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 中,与增加新功能想必,它更注重通过设置代理人的方式来减轻本人的工作负担。

最近更新

  1. TCP协议是安全的吗?

    2024-01-26 21:04:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-26 21:04:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-26 21:04:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-26 21:04:01       20 阅读

热门阅读

  1. React进阶 - 13(说一说 React 中的虚拟 DOM)

    2024-01-26 21:04:01       45 阅读
  2. Hive之set参数大全-14

    2024-01-26 21:04:01       29 阅读
  3. SpringBoot实现自定义异常+全局异常统一处理

    2024-01-26 21:04:01       36 阅读
  4. 深入理解高阶函数与函数柯里化在React中的应用

    2024-01-26 21:04:01       39 阅读
  5. MySQL之约束

    2024-01-26 21:04:01       33 阅读
  6. CGAL::Plane_3<K>平面结构

    2024-01-26 21:04:01       36 阅读
  7. webpack常见的loader和plugin

    2024-01-26 21:04:01       37 阅读
  8. Android JNI中设置全局的jbyteArray

    2024-01-26 21:04:01       35 阅读