1. 享元模式
1.1 简介
享元(FlyWeight)模式顾名思义,既是轻量级的。享元即是共享元素,或者说是共享对象。如何共享对象呢?就是在检测对象产生的时候,如果产生的是同一个对象,那么直接使用已经产生的,听起来很像是单例模式,其实享元模式的内部实现就是很类似与单例模式的懒汉模式。享元的好处就是,在某些场景下可以节省内存,从而使得程序的性能得到提升。
flyweight设计模式意图是:
使用共享可以有效地支持大量细粒度对象
Flyweight设计模式是一种结构设计模式,当需要创建一个类的很多对象时,可以使用Flyweight设计模式。应用Flyweight模式主要考虑一下几个因素:
- 应用程序要创建的对象数量应该很大。
- 对象创建在内存上很重要,也可能很耗时。
- 对象属性可以分为内在属性和外在属性,对象的外在属性应该由客户端程序定义。
应用flyweight模式,我们需要将Object属性划分为内部(intrinsic)属性和外部(extrinsic)属性。内在属性使对象唯一,而外在属性由客户端代码设置并用于执行不同的操作。
Flyweight模式一般会和Factory模式搭配使用,一般会创建一个管理共享对象的Flyweight工厂,这样实现对象的创建、保存、返回等。
2. FlyWeight示例
我们模拟一个搜索过程,在搜索中可以使用google,baidu,bing进行搜索,因为相同搜索引擎只需要创建一个,不需要创建多个,同时相同的搜索结果(网址)只需要返回同一个即可,所以都用到了享元模式。
搜索结果类:
public class Website {
private String title;
private String url;
private long publishTimestamp;
public Website(String title, String url, long publishTimestamp) {
super();
this.title = title;
this.url = url;
this.publishTimestamp = publishTimestamp;
}
public String getTitle() {
return title;
}
public String getUrl() {
return url;
}
public long getPublishTimestamp() {
return publishTimestamp;
}
public String toString() {
return String.format("[title:%s, url:%s, time:%d]", title, url, publishTimestamp);
}
}
搜索结果享元类:
public class WebsiteRepository {
private static WebsiteRepository repo = new WebsiteRepository();
public Map<String, Website> websites = new HashMap<String, Website>();
public static WebsiteRepository getInstance() {
return repo;
}
public Website getWebsite(String name, String url, long timestamp) {
if (!websites.containsKey(url)) {
Website newsite = new Website(name, url, timestamp);
websites.put(url, newsite);
return newsite;
}
return websites.get(url);
}
public int howmanyWebsite() {
return websites.size();
}
}
搜索引擎抽象类:
public abstract class SearchEngine {
private long timeoutInMs;
private String name;
public SearchEngine (Long timeoutInMs, String name) {
this.timeoutInMs = timeoutInMs;
this.name = name;
}
public List<Website> search(String keyword) {
System.out.println(name + " search engine with timeout set to " + timeoutInMs + "start to search");
return doSearch(keyword);
}
protected abstract List<Website> doSearch(String keyword);
}
** Google:**
public class Google extends SearchEngine {
public Google(Long timeoutInMs, String name) {
super(timeoutInMs, name);
}
@Override
public List<Website> doSearch(String keyword) {
List<Website> website = new ArrayList<Website>();
website.add(WebsiteRepository.getInstance().getWebsite("title1", "url1", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title2", "url2", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title3", "url3", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title4", "url4", 1l));
return website;
}
}
** Bing:**
public class Bing extends SearchEngine {
public Bing(Long timeoutInMs, String name) {
super(timeoutInMs, name);
}
@Override
public List<Website> doSearch(String keyword) {
List<Website> website = new ArrayList<Website>();
website.add(WebsiteRepository.getInstance().getWebsite("title1", "url1", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title2", "url2", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title4", "url4", 1l));
return website;
}
}
BaiDu:
public class Baidu extends SearchEngine {
public Baidu(Long timeoutInMs, String name) {
super(timeoutInMs, name);
}
@Override
public List<Website> doSearch(String keyword) {
List<Website> website = new ArrayList<Website>();
website.add(WebsiteRepository.getInstance().getWebsite("莆田医院", "莆田医院", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title1", "url1", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title2", "url2", 1l));
website.add(WebsiteRepository.getInstance().getWebsite("title3", "url3", 1l));
return website;
}
}
搜索引擎享元类:
public class SearchEngineFlyweightFactory {
private static final Map<String, SearchEngine> engines = new ConcurrentHashMap<>();
public static SearchEngine getEngine(String name, long timeoutInMs, Class<? extends SearchEngine> clazz) {
String key = name + timeoutInMs;
SearchEngine engine = null;
if (engines.get(key) == null) {
try {
Constructor<? extends SearchEngine> cons = clazz.getConstructor(Long.class, String.class);
engine = cons.newInstance(timeoutInMs, name);
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
SearchEngine existing = engines.put(key, engine);
if (existing != null) {
engine = existing;
}
} else {
engine = engines.get(key);
}
return engine;
}
public static int howManyEngine() {
return engines.size();
}
}
客户端调用:
public class FlyWeightPatternClient {
public static void main(String[] args) throws SecurityException, ClassNotFoundException {
search("baidu", 1000L, Baidu.class, "keyword");
search("google", 1000L, Google.class, "keyword");
search("bing", 1000L, Bing.class, "keyword");
search("baidu", 1000L, Baidu.class, "keyword");
}
private static void search(String name, long timeoutInms, Class<? extends SearchEngine> clazz, String keyword) {
SearchEngine engine = SearchEngineFlyweightFactory.getEngine(name, timeoutInms, clazz);
List<Website> websites = engine.search(keyword);
renderWebsite(websites);
System.out.println(SearchEngineFlyweightFactory.howManyEngine());
System.out.println(WebsiteRepository.getInstance().howmanyWebsite());
}
public static void renderWebsite(List<Website> websites) {
System.out.println("start to render " );
for (Website website : websites) {
System.out.println(website.toString());
}
}
}
在WebsiteRepository类中,使用了HashMap来将Website对象保存起来,这样就形成了一个DAC(有向无环图),只要websites变量不被释放,我们使用的共享单元是不会被释放的。这样就保证了Website对象数组不被释放,在使用享元模式的时候一定要特别注意这种情况,因为垃圾回收器(GC)在内存占用过多的时候被唤醒,然后清理那些被再被使用的内存,采用的方式就是DAC。
3. FlyWeight应用
FlyWeight应用:
- jvm中的字符串常量池。相同的字符串会共享一个字符串对象;
- 在HystrixCommandKey.Factory类会存储所有线程group相同的HystrixCommandKey
- HystrixPropertiesFactory会存储线程熔断配置,这些都是享元模式的使用。
FlyWeight模式和Factory模式异同:
- 二者都提供了对象工厂的功能,生成了类的实例供外部调用。
- 但Flyweight模式的目的是使某些相似对象共用类的同一个实例以达到节省内存空间的目的。
- 工厂模式则不强制这一点,它只是负责生成类的实例,另外,工厂模式还通过工厂的继承来生成具有继承关系的不同类的实例,而Flyweight模式不强调这一点。
网友评论