大卫的Design Patterns学习笔记10:Flyweight

一、概述
当类的部分属性在整个系统中的多个对象间重复出现时,一个通常的作法是将重复出现的属性从类定义中分离出来,并在多个对象间通过共享来节约系统开销,这种情况在界面相关的应用中尤其常见。如用于浏览目录内容的树,每个节点前面有一个Icon用于表示该节点的类型,如果将该Icon保存在每个节点的数据结构中,无疑是一种巨大的浪费,这时候通过共享(每个节点只需要保存一个所使用Icon的标识即可,在C ++中,可以通过引用、指针或ID标识等来实现)可以提高性能,并且当被共享的次数越多时,这种提高就越明显。
Flyweight(享元)模式采用共享来避免大量拥有相同内容对象的开销,这种开销中最常见、最直观的就是内存的损耗,Flyweight模式以共享的方式高效地支持大量的细粒度对象。

二、结构
存在两种典型的运用Flyweight模式的情形:单纯Flyweight模式和复合Flyweight模式。
单纯Flyweight模式的类图结构如下:

1:单纯Flyweight模式类图示意
在上面的类图中包括以下组成部分:
1
、抽象享元 (Flyweight )角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口,通过这个接口Flyweight可以接受并作用于外部状态(Extrinsic State)。
2
、具体享元 (ConcreteFlyweight )角色:实现抽象享元角色所规定的接口。如果有内蕴状态(Intrinsic State)的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。
3
、享元工厂 (FlyweightFactory )角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
4
、客户端 (Client )角色:本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。

单纯Flyweight模式在收到对象创建请求时检查是否该类型对象已存在,若存在,则直接返回该对象,否则,创建新的对象。单纯Flyweight模式的的结构十分简单,其思想与Singleton模式及Simple Factory Pattern也有几分相似之处,但单纯Flyweight模式注重对多个对象(数量不确定)的共享,希望通过这种共享来达到效率或者空间上的节省,而Singleton模式注重对对象创建数目的控制,Simple Factory Pattern则注重对对象创建细节的屏蔽和分离。

复合Flyweight模式的类图结构如下:

2:复合Flyweight模式类图示意
在上面的类图中包括以下组成部分:
1
)抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
2
)具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3
)复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
4
)享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
5
)客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
复合Flyweight模式通过组合的方式来结合关联的具有相同Extrinsic属性而Intrinsic属性不同的多个单纯Flyweight对象,是Flyweight模式的一种延伸。

三、应用
在以下情况下可以考虑使用Flyweight模式:
1
)系统中有大量的对象,他们使系统的效率降低。
2
)这些对象的状态可以分离出所需要的内外两部分。
GoF的DP一书举出了一个字处理的例子,由于在通常情况下,一篇文档中字符及其字体颜色属性的组合并不会太多(如果是纯文本文件,我认为没有使用Flyweight模式的必要,因为存一个字符跟存一个字符的索引的消耗是相当的),根据GoF的统计,一篇包含 180 , 000个字符(英文)的文档需要分配的Flyweight的数目大约只有 480个。因此,通过保存各字符的索引(通过字符的颜色、大小等信息进行分类,可以对保存的策略进行进一步优化,如DP一书提到采用B -Tree进行存储),而不是实际保存每一个字符以及其大小、颜色信息,可以大大节约实际使用的内存大小。但是,话说回来,虽然我没有实际测试过,但是,个人认为这种存储策略可能在很多情况下并非最优,对于类似的情况,其它一些处理策略,如采用类似位图行程压缩的方式存放属性变化信息,而将文档内容以纯文本形式存放,在很多情况下可能空间使用效率也非常高,只是可能需要涉及比较复杂的逻辑处理。
我不知道是由于Flyweight模式的名字的原因或者其它什么原因,在通常所能看到的关于Flyweight模式的材料中总是假设被共享的对象很小,我并不同意这种观点。实际上,个人认为,Flyweight模式对于大的对象(可能内存消耗大,也可能创建成本高)更有价值,如连接池 /线程池就是共享大的对象的最好的例证,只是由于大的对象往往具有更多的属性,这在一定程度上阻碍了共享的发生。

四、优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:使得系统逻辑变得更加复杂,而且在一定程度上外蕴状态影响了系统的速度。
同时,外蕴状态和内蕴状态的划分,以及两者关系的对应关系也是必须考虑的因素。只有将内外划分妥当才能使内蕴状态发挥它应有的作用;如果划分失误,可能在空间和时间两个方面都得不偿失。

五、举例
上面已经说过,准确划分Intrinsic State和Extrinsic State是应用Flyweight模式的关键,划分时应保证内蕴状态尽可能多,而外蕴状态尽可能少,以充分利用共享减小重复消耗。
作为一个设计良好的程序库,在JDK中存在着一些运用Flyweight模式的例子,如BorderFactory就是一个享元工厂类,下面的例子输出为Yes:

import javax .swing .*;
import javax .swing .border .*;

public class
 BorderTest  {
   public static
 void  main (String [] args ) {
      BorderTest test  =  new BorderTest ();
   }

   public
 BorderTest () {
      Border border1   = BorderFactory .createRaisedBevelBorder ();
      Border border2  = BorderFactory .createRaisedBevelBorder ();

      if
(border1  == border2 )
         System .out .println ( "Yes. Two borders are shared" );
      else

         System .out .println ( "No. Two borders are NOT shared" );
   }
}


此外,Java中的String和JTree、JTable等也通过共享使用部分公共元素,使得性能得以提升。

下面举一个绘图的例子,通常来讲,图形会包含线型、线宽、颜色等信息,在一个包含大量线条(直线或曲线)的绘图系统中,如果在每一个线条对象中均保存这些信息,无疑是一种巨大的浪费。为此,我们将线条对象的属性进行如下划分:
Intrinsic State:
Color
LineWidth
...


Extrinsic State  ( for line )
Start Point
End Point
根据以上划分,相应示例的实现如下(Java Code):

import java .awt .*;
import java .awt .event .*;
import javax .swing .*;
import javax .swing .event .*;
import java .util .ArrayList ;

public class
 FlyweightTest extends JFrame  {
    private static
 final Color colors [] = { Color .red , Color .blue ,
                               Color .yellow , Color .orange ,
                               Color .black , Color .white  };
    private static
 final  int WINDOW_WIDTH  =  400 , WINDOW_HEIGHT  =  400 , NUMBER_OF_LINES  =  100 ;
    private
 ArrayList vLine  =  new ArrayList ();
    
    JButton button  =  new JButton ( "draw lines" );
    final JPanel panel  =  new JPanel ();
               
    public static
 void  main (String [] args ) {
        FlyweightTest test  =  new FlyweightTest ();
        test .show ();
    }

    
    public
 FlyweightTest () {
        super ( "Flyweight Test" );
        Container contentPane  = getContentPane ();
        contentPane .setLayout ( new BorderLayout ());
        
        contentPane .add (panel ,  BorderLayout .CENTER );
        contentPane .add (button , BorderLayout .SOUTH );
        setBounds ( 20 ,  20 , WINDOW_WIDTH , WINDOW_HEIGHT );
        setDefaultCloseOperation (JFrame .EXIT_ON_CLOSE );
        
        button .addActionListener ( new ActionListener () {
            public
 void actionPerformed (ActionEvent event ) {
                vLine .clear ();
                for
( int i  =  0 ; i  < NUMBER_OF_LINES ; i ++) {
                    int
 index  = LineFlyweightFactory .getIndex (getRandomColor (), getRandomWidth ());
                    vLine .add ( new Line ( new Point (getRandomX (), getRandomY ()),  new Point (getRandomX (), getRandomY ()), index ));
                }

                repaint ();
            }
            });
    }

    private
 int getRandomX () {
        return
 ( int )(Math .random () * WINDOW_WIDTH );
    }

    private
 int getRandomY () {
        return
 ( int )(Math .random () * WINDOW_HEIGHT );
    }

    private
 Color getRandomColor () {
        return
 colors [( int )(Math .random () * colors .length )];
    }

    private
 int getRandomWidth () {
        return
 ( int )(Math .random () *  5 );
    }

    public
 void paint (Graphics g ) {
        super .paint (g );
        Graphics gp  = panel .getGraphics ();
        Line line ;
        for
( int i  =  0 ; i  < vLine .size (); i ++) {
            line  = (Line )vLine .get (i );
            line .draw (gp );
        }
    }
}


// class which contains extrinsic state and reference to flyweight
class Line  {
    private
 Point start , end ;
    private
 int index ;     // reference to flyweight
    
    public
 Line (Point start , Point end ,  int index ) {
        this
.start  = start ;
        this
.end  = end ;
        this
.index  = index ;
    }

    public
 void draw (Graphics g ) {
        LineFlyweight line  = LineFlyweightFactory .getLine (index );
        line .draw (g , start .x , start .y , end .x , end .y );     // pass extrinsic state to flyweight
    }
}


// Flyweight
class LineFlyweight  {
    // intrinsic state
    private Color color ;
    private
 BasicStroke stroke ;
    
    public
 LineFlyweight (Color color ,  float lineWidth ) {
        this
.color  = color ;
        stroke  =  new BasicStroke (lineWidth );
    }

    public
 boolean equals (Color color ,  int lineWidth ) {
        if
 ( this .color .equals (color ) && (stroke .getLineWidth () == lineWidth ))
            return
 true ;
            
        return
 false ;
  }

    public
 void draw (Graphics g ,  int x ,  int y ,  int x2 ,  int y2 ) {
       Graphics2D g2  = (Graphics2D )g ;
        g2 .setColor (color );
        g2 .setStroke (stroke );
        g2 .drawLine (x , y , x2 , y2 );
    }
}


// Flywight Factory
class LineFlyweightFactory  {
    private static
 final ArrayList vFlyweight  =  new ArrayList ();
    
    public static
 int getIndex (Color color ,  int lineWidth ) {
        LineFlyweight line ;
        for
 ( int i  =  0 ; i  < vFlyweight .size (); i ++) {
            line  = (LineFlyweight )vFlyweight .get (i );
            if
 (line .equals (color , lineWidth ))
                return
 i ;
        }

        line  =  new LineFlyweight (color , lineWidth );
        vFlyweight .add (line );
        System .out .println ( "Creating "  + color  +  " line with width = "  + lineWidth );
        return
 vFlyweight .size () -  1 ;
    }

    public static
 LineFlyweight getLine ( int index ) {
        if
 (index  > vFlyweight .size ())
            return
 null ;
            
        return
 (LineFlyweight )vFlyweight .get (index );
    }
}
现代C++中的设计模式是用于对象重用的可重复性方法。设计模式是一种在不同情况下解决相似问题的经验总结,可以通过将问题解决方案的关键部分抽象出来,从而提供灵活性和可重用性。设计模式不是编程语言特定的功能,而是一种通用的方法论。 在现代C++中,有许多常用的设计模式可以用于对象的可重用性。以下是几个常见的设计模式示例: 1.单例模式:用于确保一个类只能创建一个实例,并提供对该实例的全局访问点。对于有些对象只需要一个实例的情况,单例模式可以确保该实例的唯一性,从而方便访问和管理。 2.工厂模式:用于创建对象的过程中封装创建逻辑,让客户端代码无需关心对象的具体创建细节。通过工厂模式,可以通过一个工厂类来创建对象,从而提供更高的灵活性和可扩展性。 3.观察者模式:用于对象之间的发布-订阅机制,让一个对象(主题)的状态发生变化时,能够通知并自动更新其他依赖于该对象的对象(观察者)。通过观察者模式,可以实现对象之间的松耦合和消息传递,提高对象的可重用性和可维护性。 4.适配器模式:用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以解决接口不兼容的问题,从而使得原本不兼容的类能够一起工作,提高可重用性和互操作性。 5.策略模式:用于定义一系列算法/行为,并将其封装成独立的类,使得它们可以互相替换。策略模式可以在运行时根据需要动态切换算法/行为,从而提供更高的灵活性和可重用性。 这些设计模式都是在现代C++中常见且有用的重用性方法,可以根据具体的应用场景选择合适的设计模式来提高代码的可维护性、可扩展性和可重用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值