0.《设计模式》摘录
-
问题:
在一个文档编辑器中可以把文档中的每个字符描述成一个对象:
太消耗内存,一个小文档都可能包含成千上万个字符对象。 -
解决方案:
共享对象,每个字符用一个对象表示,文档中出现的所有这个字符都共享这个对象。
字符全部引用flyweight pool,避免重复
状态信息怎么办?把每个字符的状态信息(字体、颜色等)用一个外部对象表示
这些状态也可以共享,例如整个段落的文字采用的是相同的字体和颜色。
一.针对的问题
不同的客户有相似的需要,比如做网站,最开始做法是给每个客户分配一个空间,复制一份代码。也就是说,多一个网站就多一个类的实例,浪费了很多资源。
解决:共享代码核心和数据库,利用用户ID来区分不同的用户,具体数据和模板可以不同。
二.享元模式(只有共享部分的实现)
2.1 结构图和代码
- 享元模式:运用共享技术有效地支持大量细粒度的对象。
-
Flyweight:所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
abstract class Flyweight { public abstract void Operation (int extrinsicstate); }
-
ConcreteFlyweight:继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间。
class ConcreteFlyweight extends Flyweight { public override void Operation(int extrinsicstate) { Console.WriteLine ("具体 Flyweight:" +extrinsicstate); } }
-
UnsharedConcreteFlyweight:不需要共享的Flyweight子类。因为Flyweight接口共享成为可能,但它并不强制共享。
class UnsharedConcreteFlyweight extends Flyweight { public override void Operation(int extrinsicstate) { Console.Writeine("不共享的具体 Flyweight:"+ extrinsicstate); } }
-
FlyweightFactory:享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight。当用户请求一个Flyweight时,FlyweightFactory对象提供一个己创建的实例或者创建一个
class FlyweightFactory { private Hashtable flyweights = new Hashtable(); //初始化工厂时,先生成三个实例 flyweights.Add("X", new ConcreteFlyweight()); flyweights.Add("Y", new ConcreteFlyweight()); flyweights.Add("Z", new ConcreteFlyweight()); //根据客户端请求,获得已生成的实例 public Flyweight GetFlyweight(String key) { return ((Flyweight)flyweights[key]) } }
-
客户端:client是关联享元工厂FlyweightFactory、ConcreteFlyweight、UnsharedConcreteFlyweight,就是说他知道有那些共享Flyweight和不共享的Flyweight
static void Main(string[] args) { int extrinsicstate = 22; //代码外部状态 FlyweightFactory f = new FlyweightFactory(); Flyweight fx = f.GetFlyweight("x"); fx.Operation(--extrinsicstate); Flyweight fy = f.GetFlyweight("Y"); fy.Operation(--extrinsicstate); Flyweight fz = f.GetFlyweight("z"); fz.Operation(--extrinsicstate); UnsharedConcreteFlyweight uf = new UnsharedConcreteFlyweight(); uf.Operation(--extrinsicstate); Console.Read(); } /* 结果表示 具体 Flyweight: 21 具体 Flyweight: 20 具体 Flyweight: 19 不共享的具体Flyweight: 18 */
-
注意:也可以懒汉式,初始化时什么也不做,用户调用时再去判断对象是否null来决定是否实例化。
2.2. 应用到网站的代码
网站应该有一个抽象类和一个具体网站类,然后通过网站工厂来产生对象。
-
网站抽象类
abstract class WebSite { public abstract void Use(); }
-
具体网站类
class ConcreteWebSite extends WebSite { private string name = ""; public ConcreteWebSite(string name) { this.name = name; @override public void Use() { Console.WriteLine ("网站分类:" + name) } } }
-
网站工厂类
class WebSiteFactory { private Hashtable flyweights = new Hashtable(); //获得网站分类 public WebSite GetWebSiteCategory(string key) { if(!flyweights.ContainsKey(key)) flyweights.Add(key,new ConcreteWebSite(key)); return ((WebSite)flyweights[key]); } //获得网站分类总数 public int GetWebSiteCount() { return flyweights.Count; } }
-
客户端代码
static void Main(string[] args) { WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory ("产品展示”); fx.Use(); WebSite fy = f.GetWebSiteCategory("产品展示”); fy.Use(); WebSite fz = f.GetWebSiteCategory("产品展示"); fz.Use(); WebSite fl = f.GetWebSiteCategory("博客"); fl.Use(); WebSite fm = f.GetWebSiteCategory("博客") ; fm.Use(); WebSite fn = f.GetWebSiteCategory("博客"); fn.Use(); Console.WriteLine ("网站分类总数为{0}",f.GetWebSiteCount()); Console.Read(); }
结果:
网站分类:产品展示网站分类:产品展示
网站分类:产品展示
网站分类:博客
网站分类:博客
网站分类:博客
网站分类总数为 2
实际上这样写没有体现对象间的不同,只体现了它们共享的部分。
三.内部状态与外部状态
-
享元对象的内部状态:在享元对象内部并且不会随环境改变而改变的共享部分
外部状态:而随环境改变而改变的、不可以共享的状态
适用场景:有时需要生成大量细粒度的类实例来表示数据。如果发现这些实例除了几个参数外基本上都是相同的,有时候能减少需要实例化的类的数量:把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享减少单个实例的数目。 内部状态存储于ConcreteFlyweight对象之中,而外部对象由客户端对象存储或计算,调用Flyweight对象的操作时传递给它。也就是说,客户账号就是外部状态,应该由专门的对象处理
-
代码结构图:
-
用户类:用于网站的客户账号,是“网站”类的外部状态
public class User { private string name; public User(string name) { this.name = name; } public string get() { return name; } }
-
网站抽象类
abstract class WebSite { public abstract void Use(User user); }
-
具体网站类
class ConcreteWebSite extends WebSite { private string name = ""; public ConcreteWebSite(String name) { this.name = name; } @override public void Use(User user)//具体实现Use,传入了参数User来获得外部状态 { Console.WriteLine("网站分类:" + name + "用户:" + user.Name); } }
-
网站工厂类
class WebSiteFactory { private Hashtable flyweights = new Hashtable(); //获得网站分类 public WebSite GetWebSiteCategory(String key) { if (!flyweights.ContainsKey(key)) flyweights.Add(key, new ConcreteWebSite(key)); return ((WebSite)flyweights[key]); } //获得网站分类总数 public int GetWebSiteCount() { return flyweights.Count; } }
-
客户端代码
static void Main(string[] args) { WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("产品展示"); fx=f.Use(new User("小菜")); WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("产品展示"); fx=f.Use(new User("大鸟")); WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("产品展示"); fx=f.Use(new User("娇娇")); WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("博客"); fx=f.Use(new User("桃谷六仙")); WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("博客"); fx=f.Use(new User("南海鳄神")); WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("博客"); fx=f.Use(new User("老顽童")); Console.WriteLine ("得到网站分类总数为{0}",f.GetWebSiteCount()); Console.Read(); }
结果显示:尽管给六个不同用户使用网站,但实际上只有两个网站实例。
网站分类:产品展示 用户:小菜网站分类:产品展示 用户:大鸟
网站分类:产品展示 用户:娇娇
网站分类:博客用户:南海鳄神
网站分类:博客 用户:桃谷六仙
网站分类:博客 用户:老顽童
得到网站分类总数为2
-
四.一些问题
-
为什么要有UnsharedConcreteFlyweight的存在呢?
因为尽管我们大部分时间都需要共享对象来降低内存的损耗,但个别时候也有可能不需要共享的,它可以解决那些不需要共享对象的问题 -
什么时候用享元模式?
如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;
还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态, 那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式 -
缺点:
使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,
另外享元模式使得系统更加复杂。
为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
因此,应当在有足够多的对象实例可供共享时 才值得使用享元模式 -
应用:
-
String A=“a”;
String B =“a”;
两个地址是一样的,只有一个实例。
-
围棋不变的状态是两种颜色——内部状态,变的是方位——外部状态,所以只需要两个实例就可以了
-