享元模式

《大话设计模式》第26章读书笔记,介绍享元模式

Posted by GrayWind on November 22, 2018

享元模式(Flyweight)运用共享技术有效地支持大量细粒度的对象。

flyweight

Flyweight类,所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态

public interface Flyweight {
	public void operation(int extrinsicstate);
}

ConcreteFlyweight继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间

public class ConcreteFlyweight implements Flyweight {

	@Override
	public void operation(int extrinsicstate) {
		System.out.println("具体Flyweight:" + String.valueOf(extrinsicstate));
	}

}

UnsharedConcreteFlyweight指那些不需要共享的Flyweight子类。因为Flyweight接口共享成为可能,但它并不强制共享

public class UnsharedConcreteFlyweight implements Flyweight {

	@Override
	public void operation(int extrinsicstate) {
		System.out.println("不共享的具体Flyweight:" + String.valueOf(extrinsicstate));
	}

}

FlyweightFactory一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。这里初始化生成对象也可以在发现Map中不存在时再进行生成。

public class FlyweightFactory {
	private Map<String, Flyweight> flyweights = new HashMap<>();

	public FlyweightFactory() {
		flyweights.put("X", new ConcreteFlyweight());
		flyweights.put("Y", new ConcreteFlyweight());
		flyweights.put("Z", new ConcreteFlyweight());

	}

	public Flyweight getFlyweight(String key) {
		return (flyweights.get(key));
	}
}

客户端代码

public class FlyweightTest {
	public static void main(String[] args) {
		//外部状态
		int extrinsicstate = 22;

        FlyweightFactory f = new FlyweightFactory();

        Flyweight fx = f.getFlyweight("X");
        fx.operation(--extrinsicstate);//具体Flyweight:21

        Flyweight fy = f.getFlyweight("Y");
        fy.operation(--extrinsicstate);//具体Flyweight:20

        Flyweight fz = f.getFlyweight("Z");
        fz.operation(--extrinsicstate);//具体Flyweight:19

        UnsharedConcreteFlyweight uf = new UnsharedConcreteFlyweight();

        uf.operation(--extrinsicstate);//不共享的具体Flyweight:18
	}
}

在享元对象内部并且不会随着环境改变而改变的共享部分,可以成为享元的内部状态,而随着环境改变而改变的、不可以共享的状态就是外部状态了。享元模式可以避免大量非常相似的开销。在程序设计中,有时需要生产大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式Flyweight执行时所需的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。

如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用享元模式。还有就是对象的大多数状态可以使用外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以用享元模式。

以下方的网站共享实例为例,需要开发大量类似的网站,每个用户请求不同类型的网站,此时如果对每个用户的每类网站都生成一个实例就会浪费大量的空间。所以,可以将网站作为享元对象,用一个工厂来存储它,如果用户请求已有类型则直接返回,否则生成一个新的对象。

用户类,用于网站的客户账号,是网站类的外部状态

public class User {
	private String name;

	public User(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

}

网站抽象类,相当于Flyweight享元对象,“使用”方法需要传递用户对象

public interface Website {
	public void use(User user);
}

具体网站类

public class ConcreteWebSite implements Website {
	private String name;

	public ConcreteWebSite(String name) {
		this.name = name;
	}

	@Override
	public void use(User user) {
		System.out.println("网站分类:" + name + " 用户:" + user.getName());
	}

}

网站工厂类,当请求不存在的网站实例时,初始化新的实例并保存,已存在则直接返回

public class WebSiteFactory {
	private Map<String, Website> flyweights = new HashMap<>();

	// 获得网站分类
	public Website getWebSiteCategory(String key) {
		if (!flyweights.containsKey(key))
			flyweights.put(key, new ConcreteWebSite(key));
		return (flyweights.get(key));
	}

	// 获得网站分类总数
	public int getWebSiteCount() {
		return flyweights.size();
	}
}

客户端代码

public class Test {
	public static void main(String[] args) {
		WebSiteFactory f = new WebSiteFactory();

		Website fx = f.getWebSiteCategory("产品展示");
		fx.use(new User("小菜"));

		Website fy = f.getWebSiteCategory("产品展示");
		fy.use(new User("大鸟"));

		Website fz = f.getWebSiteCategory("产品展示");
		fz.use(new User("娇娇"));

		Website fl = f.getWebSiteCategory("博客");
		fl.use(new User("老顽童"));

		Website fm = f.getWebSiteCategory("博客");
		fm.use(new User("桃谷六仙"));

		Website fn = f.getWebSiteCategory("博客");
		fn.use(new User("南海鳄神"));

		System.out.println("得到网站分类总数为" + String.valueOf(f.getWebSiteCount()));
	}
}

运行结果

网站分类:产品展示 用户:小菜
网站分类:产品展示 用户:大鸟
网站分类:产品展示 用户:娇娇
网站分类:博客 用户:老顽童
网站分类:博客 用户:桃谷六仙
网站分类:博客 用户:南海鳄神
得到网站分类总数为2