zoukankan      html  css  js  c++  java
  • 享元模式(Flyweight)

    一. 世间万物皆为对象

    从大学校园中拦住一个软件工程专业的学生,问他,什么是面向对象。他会告诉你,世间万物皆是对象。

    世界之大,何止万物。上至宇宙星辰,下至细菌病毒。皆为对象。

    女孩,吐气如兰,仍留淡淡余香。

    男孩,闭眼陶醉,不亦乐乎。

    此乃共享之妙也!

    二. 对象爆炸

    呼吸之间,分子无数。

    每个分子皆为一对象,恐万台服务器之矩阵亦无可容。

    奈何乎?

    GOF 曰: 享元模式!

    三. 何为享元模式

    Flyweight : 次最轻量级的拳击选手。即粒度最小。

    因此,享元模式的目的是采用共享技术解决大量细粒度对象的爆炸问题。

    图:

    www.exciton.cs.rice.edu__image0

    四. 享元模式应用之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<string, Chat>();
    
        private void Add(Chat c)
        {
            cache.Add(c.Boy.Name + "_" + c.Girl.Name, c);
        }
    
        public Chat GetChat(People boy , People girl)
        {
            if (!cache.ContainsKey(boy.Name + "_" + girl.Name))
            {
                cache.Add(boy.Name + "_" + girl.Name, new Chat(boy, girl));
            }
            return cache[boy.Name + "_" + girl.Name];
        }
    }

    于是,从客户端访问该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>).Acquire(out item, out increasable);
        }
        private void TryToAddObject<T>() where T:class,IPoolable,new()
        {
            if (!cache.ContainsKey(typeof(T)))
            {
                cache.Add(typeof(T), new 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):运用共享技术有效地支持大量细粒度的对象。

  • 相关阅读:
    Spark学习之路 (五)Spark伪分布式安装
    Spark学习之路 (四)Spark的广播变量和累加器
    Spark学习之路 (三)Spark之RDD
    Spark学习之路 (二)Spark2.3 HA集群的分布式安装
    Spark学习之路 (一)Spark初识
    通俗理解梯度下降
    通俗理解线性回归(二)
    通俗理解线性回归(一)
    python机器学习手写算法系列——线性回归
    CSS中display对布局的影响以及元素display的默认值
  • 原文地址:https://www.cnblogs.com/heartstage/p/3437773.html
Copyright © 2011-2022 走看看