一. 世间万物皆为对象 从大学校园中拦住一个软件工程专业的学生,问他,什么是面向对象。他会告 诉你,世间万物皆是对象。 世界之大,何止万物。上至宇宙星辰,下至细菌病毒。皆为对象。 女孩,吐气如兰,仍留淡淡余香。 男孩,闭眼陶醉,不亦乐乎。 此乃共享之妙也! 二. 对象爆炸 呼吸之间,分子无数。 每个分子皆为一对象,恐万台服务器之矩阵亦无可容。 奈何乎? GOF 曰: 享元模式! 三. 何为享元模式 Flyweight : 次最轻量级的拳击选手。即粒度最小。 因此,享元模式的目的是采用共享技术解决大量细粒度对象的爆炸问题。 图: |
四. 享元模式应用之QQ聊天 我们不妨假设QQ是在服务器端将每次的对话都抽象出来形成了一个类。于是代 码如下: class People { private string name; private int age; public string Name { get { return name; } } public int Age { get { return age; } set { age = value; } } public People(string name, int age) { this.name = name; this.age = age; } }
class Chat { private People boy; private People girl; private string chatContent; public Chat(People p1, People p2) { this.boy = p1; this.girl = p2; } public string ChatContent { get { return chatContent; } set { chatContent = value; } } public People Boy { get { return boy; } } public People Girl { get { return girl; } } } 若每次二者聊天时均将Chat实例化为一个对象,如下: class Program { static void Main(string[] args) { People boy=new People("PrettyBoy",20); People girl=new People("BeautifulGirl",18); Chat chat = new Chat(boy, girl); chat.ChatContent = "I love you"; ChatServer.Send(chat); } } 若如此,服务器就需要每次都去初始化一个对象,而当chatServer将此次聊天的记录发 送给客户机之后,这个对象便成了垃圾对象。这样,每小时几百万的聊天次数,便有了几百万的对象垃 圾。垃圾回收器GC便需要不停地去工作,回收对象。 这就对效率产生了极大的影响。于是,我们想办法,使用享元模式来解决这个 问题。 两者聊天,他们的聊天方是不变的,因此,我们可以在服务器端去维护一个这样的Chat 对象集合,如果该聊天对象已经存在,那么我们便重复去利用这个聊天对象。这样既减少了内存垃圾, 又节省了创建对象的时间。 代码如下: class FlyweightFactory { private IDictionary<string, Chat> cache = new Dictionary 于是,从客户端访问该FlyweightFactory即可。 这样,便有效控制了对象的数量。 五. 享元模式的.NET Framework典型应用——String (在这里麻烦请教一下各位,我想在Reflector中,看一下String赋值的具体代 码,怎么找到呢?比如说string s=”111”;这一步的代码) 好,步入正题,让我们来看看享元模式在.NET Framework中的应用。 String 无论在.NET 还是 Java中,都是一个特殊的引用对象。 我们可以试想,出现了这样一段代码: String s=”Hello world”; String s1=”Hello world”; 那么是不是每次都要重新的去申请一块内存,然后去保存这个字符串呢?那么这样的话 是不是会效率很低呢?因为我们知道,字符串在实际使用中往往都是非常短暂的。他们通常是被读出来 之后,便直接展示给了客户。然后这个字符串的生命结束,变成垃圾。是不是很像我们刚才那个QQ聊天 对象呢? 于是在.NET 和 Java中,String都被以不变模式来进行设计。 我们来简单的分析一下String的驻留机制:在CLR被加载之后,就会在SystemDomain的托 管堆中去建立一个HashTable来维护String。 于是模拟代码如下:(伪代码) Hashtable table; if (!table.Contains("Hello world")) { table.Add("Hello world", &(new String("Hello world"))); } return *(table["Hello world"]);
代码写的有些乱,我来解释一下。 也就是说,我是在模拟一个string s=”Hello world”的过程。过程是,首先,他先去找Hashtable中目前是否存有Key为”Hello world”的项。如果不存在,那么就分配一块堆内存,存储这字符串,然后将地址作为Value,存储在 Hashtable中。如果存在的话,那么便直接找到该字符串所对应的地址,然后取出地址中的值。 用一个Hashtable来控制String对象的数量。这次您明白了么? 六 . 享元模式的扩展——对象池的应用 我们之前说,无论是字符串还是Object对象,使用享元模式都是去检查该对象是否存在 ,只要存在,那么便去重复使用。 那么是否有这样一种情况呢? 在峰期时,大量的客户端去访问同一个服务器,这个时候,如果只有一个对象的话,会 引起一定的并发问题。我的语言表述有些不大清楚。简单的说,就是每当一个对象被访问的时候,他必 须将自身锁定,并且防止其他客户去引用至该对象。 如果这个时候,我们依然去只维护一个对象的话,便会让大量的客户端处于等待队列中 。因此,我们需要靠维护一个对象池,允许在对象池中,维护一个类的多个对象。从而来实现一个服务 器空间与客户端等待时间的均衡问题。 因此,曾经,我们是在Dictionary中去维护一个Value为Object的缓存。而如今,我们便 需要在Dictionary中去维护一个Value为List<Object>的缓存。而这个List应当是限定数量的,能 保存同一类型Object的数组。 代码如下:(参考蜡笔小王的<设计模式——基于C#的工程化实现及扩展 >) class ObjectCache { private static IDictionary<Type, Object> cache; static ObjectCache() { cache = new Dictionary<Type, Object>(); } public bool TryToGetObejct<T>(out T item, out bool increasable) where T : class,IPoolable, new() { TryToAddObject<T>(); return (cache[typeof(T)] as SizeRestrictedList<T>). public bool Acquire(out T item, out bool increasable) { increasable = cache.Count >= configuration.Max ? false : true; item = null; if (cache.Count <= 0) { return false; } foreach (T cacheItem in cache) { if (cacheItem != null && cacheItem.Unoccupied) { item = cacheItem; return true; } } return false; } 七. 从微观到宏观——究竟多小才算Flyweight 我们上文说过,Flyweight是来解决细粒度对象的重用问题。那么我们去想想 ,究竟多小才算细粒度呢? 在上文中,我们一直在解决的都是对象的重用问题。那么我们向宏观方向去想 一想。 爱因斯坦的相对论:世间万物都是相对的。没有什么是绝对大的,只有相对的 小。那么我们来这样想。 我们是否可以重用一个模块,或者一个子系统呢? 八. 举一而反三—— 从享元到单例 其实,在一定意义上,我个人认为单例模式和享元模式的初衷是一样的。他们都是一个 基于空间和性能的模式。他们都是要控制对象的数量,而且实现方式本质上有着一些类似,就是首先查 询这个对象是否存在,然后返回这个对象。 那么从享元模式上的引申,我们就一样可以用到单例模式上了: 1. 我们可以不局限于单例,而是可以控制为多例。比如说:类似我前面对象 池的目的 2. 单例只是对象么?我们一样可以把子系统和模块单例! 看看他们的不同: 应该说享元模式是单例模式的一个延伸。享元模式通过享元工厂来控制多个对象的单例 化。而单例化解决的只是本身的单例问题! 九. 不要为模式而模式——何时才用享元 我一直觉得,模式不要乱用,乱用模式是学习的阶段,但是一旦在工作中,我们去乱用 模式,那么可能会造成很惨的后果。 那么究竟何时应该用享元模式呢? 1. 系统中要有大量的对象,这才值得用享元模式。否则你去维护一张对象表 ,就不值得了。 2. 对象的创建是会消耗大量时间的过程,并且对象占用较大内存。如果不是 ,那就让系统去创建吧。 3. 在B/S的系统中,个人感觉享元的应用相对较少,Web的无状态,加之我们完全在客户端进行一系列的复 杂逻辑,然后将之统一传递给Web服务器端,而不需要享元。享元主要应用还是在C/S及Winform的本地程 序上较多。 其余的,比如,关于外蕴状态和内蕴状态究竟何种应该使用享元的问题,如果不满足情 况,您也根本没有办法去使用享元。因此,我就不在这说那些蹩嘴的定义了。 十 . 享元总结 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。 (本文由控件中国网转载) |