文章目录
- 一、前言
- [二、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 是否在缓存中,如果在则直接从缓存中获取对象。
javapublic 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 对象即可。
javapublic 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 模式 :
- Virtual Proxy(虚拟代理):即本文提到的 Proxy 模式,最常见的本地代理模式。
- Remote Proxy(远程代理):对于 RealSubject 对象,有时其具体实现在远程网络上,通过 Remote Proxy 代理后可以透明的调用他的方法。Java 的 RMI (远程方法调用)就相当于 Remote Proxy。
- Access Proxy :用于在调用 RealSubject 角色的功能时设置访问限制。如对某些接口进行权限或次数限制等。
相关设计模式:
- Adapter 模式 :Adapter 模式适配了两种具有不同接口的对象,使得它们可以一同工作,而在 Proxy 模式中,Proxy角色与RealSubject 角色的接口是相同的。
- Decorator 模式 :Decorator 模式与 Proxy 模式在实现上很相似,但是目的不同:Decorator 模式的目的在于增加新的功能。而在 Proxy 中,与增加新功能想必,它更注重通过设置代理人的方式来减轻本人的工作负担。