面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况。面向对象所带来的成本必须谨慎处理。
典型的模式:单例模式,享元模式。
假设我们要设计一个字体系统,即一个字有多种不同的字体。现在写了几十万个字,如果要为每一个字都创建字体对象,那么这种开销是很大的。实际上一般的文章最多也就五六种字体,如果对所有的字都共享一个或多个字体对象,那么就大大节约了内存资源。
在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行代价----主要指内存需求方面的代价。享元模式就是解决这样的问题。
运用共享技术有效地支持大量细粒度的对象。
————《设计模式》GoF
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作呢?
关键代码如下
package com.zlfan.flyweight;
public class Font {
//唯一表示字体的名字
private String fontname;
//其他状态
Font(String fontname){
this.fontname = fontname;
}
}
package com.zlfan.flyweight;
import java.util.Map;
public class FontFactory {
//维护字体对象池
private Map<String,Font> fontPool;
//共享的方式,如有就返回,没有就新建
public Font getFont(String fontname){
Font font = null;
for (Map.Entry<String, Font> entry : fontPool.entrySet()) {
if(entry.getKey().equals(fontname))
font = entry.getValue();
}
if(font != null){
return font;
}else{
font = new Font(fontname);
fontPool.put(fontname, font);
return font;
}
}
public void clear(){
//...
}
}
重要的是理解它的思想。当客户端要求生成一个对象时,工厂会检测是否存在此对象的实例,如果存在那么直接返回此对象实例,如果不存在就创建一个并保存起来。
要点总结
- 面向对象很好地解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。
- Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。
- 对象的数量太大从而导致对象内存开销加大——什么样的数量才算大?这需要我们仔细的根据具体应用情况评估,而不能凭空臆断。
适用性
- 一个应用程序使用了大量对象,造成很大内存开销。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象外部状态,那么可以用相对较少的共享对象取代很多组对象。
- 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,所以对于概念上明显有别的对象,标识测试将返回真值。
在java中,数据库连接池,线程池等即是用享元模式的应用。