美文网首页
设计模式学习笔记08-Flyweight模式

设计模式学习笔记08-Flyweight模式

作者: 百恼神烦 | 来源:发表于2018-07-08 11:21 被阅读0次

    本文主要是看了《设计模式》做的笔记和思考,在此分享仅代表个人观点,如有不对的地方欢迎批评和指正。

    应用场景

    如果你打算做一个文字编辑器,类似Word那种,你可能想着给每个字都做成一个对象,但这样一来,一篇长文章中的对象数量增长会非常的快,对内存的消耗也是大问题,不过,这些字总有些相同的,如果是英文,256个(参考ASCII)能凑合着用了。那么,当同样的字符使用同一个对象,并且在字符对象外部对其进行修饰,就能够在满足使用需求的情况下大幅节省内存。

    基础

    Flyweight模式中文名叫享元模式,该模式的目的是在于分享最小规格的对象,来达到节省内存的目的。至于各个对象在不同位置的不同性质,由它的外部决定,UML如下。就继续拿文字编辑器的例子来说,被分享的是每个字符,但你在屏幕上看到的,是那些字符打印出来的效果,即相同的字符由同一字符对象打印

    Flyweight的UML.png

    Flyweight的外部

    由于屏幕上的每个文字都可能有样式的不同,如果个每个字符添加样式控制的对象,那么使用Flyweight模式的意义就不大了。正确的做法是使用一个外部类,这个类能够通过使用特定的数据结构来存储这些信息,使得相同的信息只有一份储存。

    class GlyphContext{
        public:
            GlyphContext();
            virtual ~GlyphContext();
            virtual void Next(int step = 1);
            virtual void Insert(int quantity = 1);
    
            virtual Font* GetFont();
            virtual void SetFont(Font*, int span = 1);
        private:
            int _index;
            BTree* _fonts;  // 使用二叉树保存样式信息
    }
    

    这就是一个外部类,它的引用以参数的形式进入到字符对象中,当字符添加、修改样式时它将实时更新内部信息。重点它保存字符样式的方法,是使用一个二叉树,二叉树可以抽象成下图(图来源于书),此处每个数字代表字符数量,叶子节点代表字符样式,顺着这个树向下遍历,便能得到某个字符串的样式。如果此处换做给每个字符都保存一个样式信息,那么该模式的使用意义并不大。


    二叉树.png

    上图可以这么理解:字符对象储存的是字符信息(它是哪个字符),GlyphContext类则储存了排版信息,当界面开始绘制时,字符对象就询问GlyphContext类:“我该画成什么样子”,GlyphContext就开始查表(当前位置我们可以通过光标传入,而不需要由字符对象保存),告诉字符对象你该怎样怎样。

    因此,这里可以总结出来:如果希望通过享元模式节省内存,那么应该尽量整合外部的信息,最好外部信息可以通过计算得到,以时间换空间

    管理享元对象

    创建

    为了更好地管理并且确保享元对象是真的被共享,你需要通过工厂模式来创建享元对象。重点:如果标识相同,就返回相同的对象,不存在就新建一个。

    class FlyweightFactory{
        ...
        public MyChar getCharacter(char c){
            if(!map.containsKey(c)){
                newOne = new MyChar(c);
                map.put(c,newOne);
            }
            return map.get(c);
        }
    
        private HashMap<char c,MyChar mc> map;
    }
    

    那么,如何防止用户绕过工厂去创建Flyweight对象呢?在Java中,你可以将工厂和Flyweight放在同一个包,并且将Flyweight的权限设置为default,工厂的设置为public。

    回收

    这个我在很多博客中都没有看到,也请读者不要忽略这个问题。设想一下,如果享元过多,是否本身就是一种对内存的消耗(尽管成为享元前更多),比如这里要存的不是英文,而是中文,那享元的数量就很可观了。然而哪怕存的是中文,也不是每个中文字符都会被用上,因此当某个生僻字终于被用户删除时,我们应该立马释放它的内存(毕竟在内存中,它与常用字占用着同样的资源)。

    class MyChar{
        private char character;
        private int useQuantity;
    
        public MyChar(char c){
            character = c;
            useQuantity = 1;
        }
    }
    
    class FlyweightFactory{
        ...
        public boolean removeChar(char c){
            if(map.containsKey(c)){
                if(map.get(c).useQuantity == 0){
                    map.remove(c);
                    return true;
                }
            }
            return false;
        }
    }
    
    

    这里我设计了一个useQuantity的计数器,在外部可以检测其数值,一旦发现其使用数为0,可以调用工厂内的removeChar方法来释放内存。

    当然,如果你的享元数量较少,而且本身占用内存很少时,你可以永久保留。

    相关文章

      网友评论

          本文标题:设计模式学习笔记08-Flyweight模式

          本文链接:https://www.haomeiwen.com/subject/pafouftx.html