.NET设计模式(13):享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern

——.NET设计模式系列之十三

Terrylee20063

摘要:面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题。但是在某些情况下,对象的数量可能会太多,从而导致了运行时的代价。那么我们如何去避免大量细粒度的对象,同时又不影响客户程序使用面向对象的方式进行操作?

本文试图通过一个简单的字符处理的例子,运用重构的手段,一步步带你走进Flyweight模式,在这个过程中我们一同思考、探索、权衡,通过比较而得出好的实现方式,而不是给你最终的一个完美解决方案。

主要内容:

1.  Flyweight模式解说

2.NET中的Flyweight模式

3Flyweight模式的实现要点

……

概述

面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题。但是在某些情况下,对象的数量可能会太多,从而导致了运行时的代价。那么我们如何去避免大量细粒度的对象,同时又不影响客户程序使用面向对象的方式进行操作?

意图

运用共享技术有效地支持大量细粒度的对象。[GOF 《设计模式》]

结构图

1  Flyweight模式结构图

生活中的例子

享元模式使用共享技术有效地支持大量细粒度的对象。公共交换电话网(PSTN)是享元的一个例子。有一些资源例如拨号音发生器、振铃发生器和拨号接收器是必须由所有用户共享的。当一个用户拿起听筒打电话时,他不需要知道使用了多少资源。对于用户而言所有的事情就是有拨号音,拨打号码,拨通电话。

使用拨号音发生器例子的享元模式对象图

Flyweight模式解说

Flyweight在拳击比赛中指最轻量级,即“蝇量级”,这里翻译为“享元”,可以理解为共享元对象(细粒度对象)的意思。提到Flyweight模式都会一般都会用编辑器例子来说明,这里也不例外,但我会尝试着通过重构来看待Flyweight模式。考虑这样一个字处理软件,它需要处理的对象可能有单个的字符,由字符组成的段落以及整篇文档,根据面向对象的设计思想和Composite模式,不管是字符还是段落,文档都应该作为单个的对象去看待,这里只考虑单个的字符,不考虑段落及文档等对象,于是可以很容易的得到下面的结构图:

3

示意性实现代码:

// "Charactor"
public abstract class Charactor
{
    
//Fields
    protected char _symbol;

    
protected int _width;

    
protected int _height;

    
protected int _ascent;

    
protected int _descent;

    
protected int _pointSize;

    
//Method
    public abstract void Display();
}


// "CharactorA"
public class CharactorA : Charactor

    
// Constructor 
    public CharactorA()
    
{
      
this._symbol = 'A';
      
this._height = 100;
      
this._width = 120;
      
this._ascent = 70;
      
this._descent = 0;
      
this._pointSize = 12;
    }


    
//Method
    public override void Display()
    
{
        Console.WriteLine(
this._symbol);
    }

}


// "CharactorB"
public class CharactorB : Charactor
{
    
// Constructor 
    public CharactorB()
    
{
        
this._symbol = 'B';
        
this._height = 100;
        
this._width = 140;
        
this._ascent = 72;
        
this._descent = 0;
        
this._pointSize = 10;
    }


    
//Method
    public override void Display()
    
{
        Console.WriteLine(
this._symbol);
    }

}


// "CharactorC"
public class CharactorC : Charactor
{
    
// Constructor 
    public CharactorC()
    
{
        
this._symbol = 'C';
        
this._height = 100;
        
this._width = 160;
        
this._ascent = 74;
        
this._descent = 0;
        
this._pointSize = 14;
    }


    
//Method
    public override void Display()
    
{
        Console.WriteLine(
this._symbol);
    }

}


好了,现在看到的这段代码可以说是很好地符合了面向对象的思想,但是同时我们也为此付出了沉重的代价,那就是性能上的开销,可以想象,在一篇文档中,字符的数量远不止几百个这么简单,可能上千上万,内存中就同时存在了上千上万个Charactor对象,这样的内存开销是可想而知的。进一步分析可以发现,虽然我们需要的Charactor实例非常多,这些实例之间只不过是状态不同而已,也就是说这些实例的状态数量是很少的。所以我们并不需要这么多的独立的Charactor实例,而只需要为每一种Charactor状态创建一个实例,让整个字符处理软件共享这些实例就可以了。看这样一幅示意图:

4

现在我们看到的ABC三个字符是共享的,也就是说如果文档中任何地方需要这三个字符,只需要使用共享的这三个实例就可以了。然而我们发现单纯的这样共享也是有问题的。虽然文档中的用到了很多的A字符,虽然字符的symbol是相同的,它可以共享;但是它们的pointSize却是不相同的,即字符在文档中中的大小是不相同的,这个状态不可以共享。为解决这个问题,首先我们将不可共享的状态从类里面剔除出去,即去掉pointSize个状态(只是暂时的J),类结构图如下所示:

5

示意性实现代码:

// "Charactor"
public abstract class Charactor
{
    
//Fields
    protected char _symbol;

    
protected int _width;

    
protected int _height;

    
protected int _ascent;

    
protected int _descent;

    
//Method
    public abstract void Display();
}


// "CharactorA"
public class CharactorA : Charactor
{
    
// Constructor 
    public CharactorA()
    
{
        
this._symbol = 'A';
        
this._height = 100;
        
this._width = 120;
        
this._ascent = 70;
        
this._descent = 0;
    }


    
//Method
    public override void Display()
    
{
        Console.WriteLine(
this._symbol);
    }

}


// "CharactorB"
public class CharactorB : Charactor
{
    
// Constructor 
    public CharactorB()
    
{
        
this._symbol = 'B';
        
this._height = 100;
        
this._width = 140;
        
this._ascent = 72;
        
this._descent = 0;
    }


    
//Method
    public override void Display()
    
{
        Console.WriteLine(
this._symbol);
    }

}


// "CharactorC"
public class CharactorC : Charactor
{
    
// Constructor 
    public CharactorC()
    
{
        
this._symbol = 'C';
        
this._height = 100;
        
this._width = 160;
        
this._ascent = 74;
        
this._descent = 0;
    }


    
//Method
    public override void Display()
    
{
        Console.WriteLine(
this._symbol);
    }

}


好,现在类里面剩下的状态都可以共享了,下面我们要做的工作就是控制Charactor类的创建过程,即如果已经存在了“A”字符这样的实例,就不需要再创建,直接返回实例;如果没有,则创建一个新的实例。如果把这项工作交给Charactor类,即Charactor类在负责它自身职责的同时也要负责管理Charactor实例的管理工作,这在一定程度上有可能违背类的单一职责原则,因此,需要一个单独的类来做这项工作,引入CharactorFactory类,结构图如下:

6

示意性实现代码:

// "CharactorFactory"
public class CharactorFactory
{
    
// Fields
    private Hashtable charactors = new Hashtable();

    
// Constructor 
    public CharactorFactory()
    
{
        charactors.Add(
"A"new CharactorA());
        charactors.Add(
"B"new CharactorB());
        charactors.Add(
"C"new CharactorC());
    }

       
    
// Method
    public Charactor GetCharactor(string key)
    
{
        Charactor charactor 
= charactors[key] as Charactor;

        
if (charactor == null)
        
{
            
switch (key)
            
{
                
case "A": charactor = new CharactorA(); break;
                
case "B": charactor = new CharactorB(); break
                
case "C": charactor = new CharactorC(); break;
                
//
            }

            charactors.Add(key, charactor);
        }

        
return charactor;
    }

}


到这里已经完全解决了可以共享的状态(这里很丑陋的一个地方是出现了switch语句,但这可以通过别的办法消除,为了简单期间我们先保持这种写法)。下面的工作就是处理刚才被我们剔除出去的那些不可共享的状态,因为虽然将那些状态移除了,但是Charactor对象仍然需要这些状态,被我们剥离后这些对象根本就无法工作,所以需要将这些状态外部化。首先会想到一种比较简单的解决方案就是对于不能共享的那些状态,不需要去在Charactor类中设置,而直接在客户程序代码中进行设置,类结构图如下:

7

示意性实现代码:

public class Program
{
    
public static void Main()
    
{
        Charactor ca 
= new CharactorA();
        Charactor cb 
= new CharactorB();
        Charactor cc 
= new CharactorC();

        
//显示字符

        
//设置字符的大小ChangeSize();
    }


    
public void ChangeSize()
    
{
        
//在这里设置字符的大小
    }

}


按照这样的实现思路,可以发现如果有多个客户端程序使用的话,会出现大量的重复性的逻辑,用重构的术语来说是出现了代码的坏味道,不利于代码的复用和维护;另外把这些状态和行为移到客户程序里面破坏了封装性的原则。再次转变我们的实现思路,可以确定的是这些状态仍然属于Charactor对象,所以它还是应该出现在Charactor类中,对于不同的状态可以采取在客户程序中通过参数化的方式传入。类结构图如下:

8

示意性实现代码:

// "Charactor"
public abstract class Charactor
{
    
//Fields
    protected char _symbol;

    
protected int _width;

    
protected int _height;

    
protected int _ascent;

    
protected int _descent;

    
protected int _pointSize;

    
//Method
    public abstract void SetPointSize(int size);
    
public abstract void Display();
}


// "CharactorA"
public class CharactorA : Charactor
{
    
// Constructor 
    public CharactorA()
    
{
        
this._symbol = 'A';
        
this._height = 100;
        
this._width = 120;
        
this._ascent = 70;
        
this._descent = 0;
    }


    
//Method
    public override void SetPointSize(int size)
    
{
        
this._pointSize = size;
    }


    
public override void Display()
    
{
        Console.WriteLine(
this._symbol +
          
"pointsize:" + this._pointSize);
    }

}


// "CharactorB"
public class CharactorB : Charactor
{
    
// Constructor 
    public CharactorB()
    
{
        
this._symbol = 'B';
        
this._height = 100;
        
this._width = 140;
        
this._ascent = 72;
        
this._descent = 0;
    }


    
//Method
    public override void SetPointSize(int size)
    
{
        
this._pointSize = size;
    }


    
public override void Display()
    
{
        Console.WriteLine(
this._symbol +
          
"pointsize:" + this._pointSize);
    }

}


// "CharactorC"
public class CharactorC : Charactor
{
    
// Constructor 
    public CharactorC()
    
{
        
this._symbol = 'C';
        
this._height = 100;
        
this._width = 160;
        
this._ascent = 74;
        
this._descent = 0;
    }


    
//Method
    public override void SetPointSize(int size)
    
{
        
this._pointSize = size;
    }


    
public override void Display()
    
{
        Console.WriteLine(
this._symbol +
          
"pointsize:" + this._pointSize);
    }

}


// "CharactorFactory"
public class CharactorFactory
{
    
// Fields
    private Hashtable charactors = new Hashtable();

    
// Constructor 
    public CharactorFactory()
    
{
        charactors.Add(
"A"new CharactorA());
        charactors.Add(
"B"new CharactorB());
        charactors.Add(
"C"new CharactorC());
    }

       
    
// Method
    public Charactor GetCharactor(string key)
    
{
        Charactor charactor 
= charactors[key] as Charactor;

        
if (charactor == null)
        
{
            
switch (key)
            
{
                
case "A": charactor = new CharactorA(); break;
                
case "B": charactor = new CharactorB(); break
                
case "C": charactor = new CharactorC(); break;
                
//
            }

            charactors.Add(key, charactor);
        }

        
return charactor;
    }

}


public class Program
{
    
public static void Main()
    
{
        CharactorFactory factory 
= new CharactorFactory();

        
// Charactor "A"
        CharactorA ca = (CharactorA)factory.GetCharactor("A");
        ca.SetPointSize(
12);
        ca.Display();
        
        
// Charactor "B"
        CharactorB cb = (CharactorB)factory.GetCharactor("B");
        ca.SetPointSize(
10);
        ca.Display();

        
// Charactor "C"
        CharactorC cc = (CharactorC)factory.GetCharactor("C");
        ca.SetPointSize(
14);
        ca.Display();
    }

}


可以看到这样的实现明显优于第一种实现思路。好了,到这里我们就到到了通过Flyweight模式实现了优化资源的这样一个目的。在这个过程中,还有如下几点需要说明:

1.引入CharactorFactory是个关键,在这里创建对象已经不是new一个Charactor对象那么简单,而必须用工厂方法封装起来。

2.在这个例子中把Charactor对象作为Flyweight对象是否准确值的考虑,这里只是为了说明Flyweight模式,至于在实际应用中,哪些对象需要作为Flyweight对象是要经过很好的计算得知,而绝不是凭空臆想。

3.区分内外部状态很重要,这是享元对象能做到享元的关键所在。

到这里,其实我们的讨论还没有结束。有人可能会提出如下问题,享元对象(Charactor)在这个系统中相对于每一个内部状态而言它是唯一的,这跟单件模式有什么区别呢?这个问题已经很好回答了,那就是单件类是不能直接被实例化的,而享元类是可以被实例化的。事实上在这里面真正被设计为单件的应该是享元工厂(不是享元)类,因为如果创建很多个享元工厂的实例,那我们所做的一切努力都是白费的,并没有减少对象的个数。修改后的类结构图如下:

9

示意性实现代码:

// "CharactorFactory"
public class CharactorFactory
{
    
// Fields
    private Hashtable charactors = new Hashtable();

    
private CharactorFactory instance;
    
// Constructor 
    private CharactorFactory()
    
{
        charactors.Add(
"A"new CharactorA());
        charactors.Add(
"B"new CharactorB());
        charactors.Add(
"C"new CharactorC());
    }

    
    
// Property
    public CharactorFactory Instance
    
{
        
get 
        
{
            
if (instance != null)
            
{
                instance 
= new CharactorFactory();
            }

            
return instance;
        }

    }


    
// Method
    public Charactor GetCharactor(string key)
    
{
        Charactor charactor 
= charactors[key] as Charactor;

        
if (charactor == null)
        
{
            
switch (key)
            
{
                
case "A": charactor = new CharactorA(); break;
                
case "B": charactor = new CharactorB(); break
                
case "C": charactor = new CharactorC(); break;
                
//
            }

            charactors.Add(key, charactor);
        }

        
return charactor;
    }

}


.NET框架中的Flyweight

Flyweight更多时候的时候一种底层的设计模式,在我们的实际应用程序中使用的并不是很多。在.NET中的String类型其实就是运用了Flyweight模式。可以想象,如果每次执行string s1 = “abcd”操作,都创建一个新的字符串对象的话,内存的开销会很大。所以.NET中如果第一次创建了这样的一个字符串对象s1,下次再创建相同的字符串s2时只是把它的引用指向“abcd”,这样就实现了“abcd”在内存中的共享。可以通过下面一个简单的程序来演示s1s2的引用是否一致:

public class Program
{
    
public static void Main(string[] args)
    
{
        
string s1 = "abcd";
        
string s2 = "abcd";

        Console.WriteLine(Object.ReferenceEquals(s1,s2));

        Console.ReadLine();
    }

}


可以看到,输出的结果为True。但是大家要注意的是如果再有一个字符串s3,它的初始值为“ab”,再对它进行操作s3 = s3 + “cd”,这时虽然s1s3的值相同,但是它们的引用是不同的。关于String的详细情况大家可以参考SDK,这里不再讨论了。

效果及实现要点

1.面向对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。

2Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。

3享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。另外它将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

适用性

当以下所有的条件都满足时,可以考虑使用享元模式:

1、   一个系统有大量的对象。

2、   这些对象耗费大量的内存。

3、   这些对象的状态中的大部分都可以外部化。

4、   这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。

5、   软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

满足以上的这些条件的系统可以使用享元对象。最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。

总结

Flyweight模式解决的是由于大量的细粒度对象所造成的内存开销的问题,它在实际的开发中并不常用,但是作为底层的提升性能的一种手段却很有效。

参考资料

Erich Gamma等,《设计模式:可复用面向对象软件的基础》,机械工业出版社

Robert C.Martin,《敏捷软件开发:原则、模式与实践》,清华大学出版社

阎宏,《Java与模式》,电子工业出版社

Alan Shalloway James R. Trott,《Design Patterns Explained》,中国电力出版社

MSDN WebCast C#面向对象设计模式纵横谈(12)Flyweight享元模式(结构型模式)

http://www.dofactory.com/

作者:TerryLee
出处:http://terrylee.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2006-03-29 14:35 TerryLee 阅读(12059) 评论(52)  编辑 收藏 网摘 所属分类: [05]  架构与设计

  回复  引用    
#1楼2006-03-29 15:24 | YongChao[未注册用户]
分析的思路很清晰,谢了!
  回复  引用  查看    
#2楼2006-03-29 15:51 | onekey      
你的模式设计写的月来月好了
  回复  引用  查看    
#3楼[楼主]2006-03-29 15:54 | Terrylee      
@onekey
呵呵,谢谢~~~

不断的学习,不断的认识!

学习的多了,对于设计模式的认识可能更深了:)

  回复  引用    
#4楼2006-03-29 17:35 | KingKing[未注册用户]
精彩!!!
  回复  引用  查看    
#5楼2006-03-29 18:29 | 雁儿飞飞      
@Terrylee

大哥的文章真筋道!
但是补充一点:享元节省拉大量内存,但是在需要使用享元时要查找,从而增加拉处理时间.呵呵

要是把"共享对象"拉出来一起溜溜就更爽拉!

  回复  引用    
#6楼2006-03-29 21:27 | DDream[未注册用户]
呵呵,刚刚看了你的这几篇关于设计模式的文章,感觉我已经没有必须要再写了,呵呵
通俗易懂!

  回复  引用  查看    
#7楼[楼主]2006-03-30 08:26 | Terrylee      
@雁儿飞飞
嗯,这一点在使用享元时很重要,我在文章中也提到了“另外它将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。”,哪些对象需要作为Flyweight对象是要经过很好的计算得知,而绝不是凭空臆想!

呵呵~~~~

  回复  引用  查看    
#8楼[楼主]2006-03-30 08:28 | Terrylee      
@DDream
呵呵,谢谢~~~

  回复  引用    
#9楼2006-03-30 09:08 | FANS[未注册用户]
Cool!
  回复  引用    
#10楼2006-03-30 13:49 | xmthor[未注册用户]
哪些对象需要作为Flyweight对象是要经过很好的计算得知,而绝不是凭空臆想!
================================
请教LZ,如何计算呢?

  回复  引用  查看    
#11楼[楼主]2006-03-30 18:45 | Terrylee      
@xmthor
我所说的计算是指通过计算比较来得出对象是否值得用Flyweight,拿文章中的例子来说,我们假设每个Charactor所占的内存大小是8,现在就可以这样计算:如果我们系统中总共用到的Charactor对象总共只有10个,那使用了享元之后可能会节省:
8*10 - (3*8 +2*10)=36,这点节省根本是微不足道的,相反降低了性能;但是如果整个系统中用到的Charactor对象可能达到10万,那又会如何呢?

所以并不一定要准确地就算出对象所占的内存,而是要通过一个简单的计算得出是否值得用Flyweight对象!总之,要记住一句话就是并不是任何时候任何情况下Flyweight都会提高性能!

  回复  引用    
#12楼2006-04-19 15:29 | wanglerby[未注册用户]
文章是写的不错,思路比较清晰,不错的文章。

有一点疑惑,

CharactorFactory factory = new CharactorFactory();

// Charactor "A"
CharactorA ca = (CharactorA)factory.GetCharactor("A");
ca.SetPointSize(12);
ca.Display();

在上面客户端通过ca.SetPointSize(12)设置了字符的大小,从某种意义上来讲是改变了工厂类享员的状态,一篇文档种的'A'字符可能有100个,但是其中有20个是12号大小,50个是9号大小,30个是15号大小,如果你笼统的用下面的方法维护'A'字符的状态,是不是有些问题?请执教

// Method
public Charactor GetCharactor(string key)
{
Charactor charactor = charactors[key] as Charactor;

if (charactor == null)
{
switch (key)
{
case "A": charactor = new CharactorA(); break;
==========================
上面这个地方,是不是要判断字体的大小??
case "B": charactor = new CharactorB(); break;
case "C": charactor = new CharactorC(); break;
//
}
charactors.Add(key, charactor);
}
return charactor;
}



  回复  引用  查看    
#13楼[楼主]2006-04-20 11:18 | Terrylee      
@wanglerby
在这个例子中,享元工厂维护的是Charactor的状态,包括A,B,C三个,字体的大小是通过外部方法去设置的。

完全可以不按照我这样去写代码,在实际运用中可能会差别很大,具体怎么去写要根据实际情况。设计模式重在于设计的思想!

  回复  引用    
#14楼2006-04-28 15:41 | 空空无量[未注册用户]
同意wanglerby的疑惑,这样做的话实质上有点混淆了享元模式中的内蕴状态和外蕴状态。在处理字符型问题的时候,character是内蕴状态,修饰的内容,包括颜色/大小/字体,都是外蕴状态,应该不存储在对象内部的。
Charactor这样是不是更好一些
public abstract class Charactor
{
//Fields
protected char _symbol;

protected int _width;

protected int _height;

protected int _ascent;

protected int _descent;

//Method
public abstract void Display(int _pointSize);
}

  回复  引用  查看    
#15楼[楼主]2006-04-28 16:25 | Terrylee      
@空空无量
我没看出来你的做法跟我的做法有什么本质的区别,都是把字体大小作为外蕴状态,只不过我用了SetPointSize()方法,而你用了Display()方法而已!

  回复  引用    
#16楼2006-04-29 17:59 | 空空无量[未注册用户]
不一样的地方在于:
使用SetPointSize更改的是对象的内部属性,不同的PointSize是不同的对象。而使用Display(int _pointSize)不更改对象的内部属性,这样不同的PonitSize对应的是相同的对象。

  回复  引用    
#17楼2006-06-01 22:31 | lilacdd[未注册用户]
享元(Flyweight)模式要求所有的享元对象都是可以共享的,是这样吗?为什么?
  回复  引用    
#18楼2006-06-05 11:20 | popoer[未注册用户]
同意空空无量的方法,楼主把_pointSize作为类变量,其实也成了内蕴状态了,只不过没有在构造的时候设置这个内蕴状态的值,而是用了另外一个方法类设置而已。我觉得外蕴状态是不能作为类变量的
  回复  引用    
#19楼2006-06-07 16:33 | 鐙楃嫍[未注册用户]
前面的文章都写的很好,只是这一篇文章的例子举的不是很恰当。
我想.net中 数据库连接池 应该是flyweight的例子。

  回复  引用    
#20楼2006-07-06 18:15 | 雨荷荷[未注册用户]
学习中.
  回复  引用    
#21楼2006-08-08 17:18 | 云游道士[未注册用户]
当用SetPointSize()的时候。改动是所有的一类对象。而不是单个对像。
  回复  引用  查看    
#22楼2006-08-09 22:34 | 肖鹏      
基本同意空空无量,
但是不能把Charactor做抽象类,否则你打算如何子类化呢?
我的解法Java代码请见谅,参照http://www.codeproject.com/cs/design/csdespat_3.asp">http://www.codeproject.com/cs/design/csdespat_3.asp
public class DecoratedCharactor {
//楼主的Charactor去掉pointSize成员变量。
Character character;
int pointSize;

public DecoratedCharactor(Character character, int pointSize) {
this.character = character;
this.pointSize = pointSize;
}

public void dispay() {
System.out.println(character + pointSize);
}
}
即保证不会产生过多的Charactor,同时有把pointSize真正作为(Charactor的)外蕴状态。

  回复  引用    
#23楼2006-08-18 18:12 | txd_lf[未注册用户]
我也同意空空无量的做法,SetPointSize()和Display()效果完全不一样~~
希望lz在示例上多多考虑

  回复  引用    
#24楼2006-08-23 17:41 | joyli[未注册用户]
看了肖鹏同学的回复,觉得这种解决方式8错!支持一下。
  回复  引用    
#25楼2006-08-24 11:39 | wang[未注册用户]
赞成 空空无量 的方法
对于size应该有客户端决定
不应该是内部状态
肖鹏 的方法好像和flyweiht的思想有出入
但是好像很实用
呵呵

  回复  引用    
#26楼2006-12-08 14:32 | king[匿名][未注册用户]
下面的例子举的不对,这个处理是.net compiler优化的结果,不属于.net的string flyweight. 可以reflect看一下,很明确.


-------------------------------------------------------
在.NET中的String类型其实就是运用了Flyweight模式。可以想象,如果每次执行string s1 = “abcd”操作,都创建一个新的字符串对象的话,内存的开销会很大。所以.NET中如果第一次创建了这样的一个字符串对象s1,下次再创建相同的字符串s2时只是把它的引用指向“abcd”,这样就实现了“abcd”在内存中的共享
public class Program
{
public static void Main(string[] args)
{
string s1 = "abcd";
string s2 = "abcd";

Console.WriteLine(Object.ReferenceEquals(s1,s2));

Console.ReadLine();
}
}

  回复  引用    
#27楼2006-12-12 08:51 | balala[未注册用户]
这个例子举的不是很好,既然size是作为extrinsicState来考虑的话,那就不应该作为character的属性了。否则这些对象也就不一样了。
  回复  引用    
#28楼2007-01-08 21:38 | 青岛开和[未注册用户]
我觉得这个例子有问题,怎么说呢?
如果开始用到了一个CharactorA设置它的SizeA,如果下次再用到该对象设置SizeB,那么以前出现的CharatorA的Size都变成了SizeB,我说的对不对?
不过感谢楼住的辛苦劳动。

  回复  引用  查看    
#29楼2007-01-16 17:23 | williambirkin      
事例中的单件代码是不是写错了?
  回复  引用    
#30楼2007-02-08 16:30 | [匿名] [未注册用户]
学习
  回复  引用    
#31楼2007-03-02 09:14 | - -[未注册用户]
读您的文章收益多多,- -特此表示感谢:〉
  回复  引用    
#32楼2007-03-02 10:35 | - -[未注册用户]
using System;
public abstract class Charactor
{
//Fields
protected char _symbol;

protected int _width;

protected int _height;

protected int _ascent;

protected int _descent;

protected int _pointSize;

//Method
public abstract void SetPointSize(int size);
public abstract void Display();
}

// "CharactorA"
public class CharactorA : Charactor
{
// Constructor
public CharactorA()
{
this._symbol = 'A';
this._height = 100;
this._width = 120;
this._ascent = 70;
this._descent = 0;
}

//Method
public override void SetPointSize(int size)
{
this._pointSize = size;
}

public override void Display()
{
Console.WriteLine(this._symbol +
"pointsize:" + this._pointSize);
}
}

// "CharactorB"
public class CharactorB : Charactor
{
// Constructor
public CharactorB()
{
this._symbol = 'B';
this._height = 100;
this._width = 140;
this._ascent = 72;
this._descent = 0;

}

//Method
public override void SetPointSize(int size)
{
this._pointSize = size;
}

public override void Display()
{
Console.WriteLine(this._symbol +
"pointsize:" + this._pointSize);
}
}

// "CharactorC"
public class CharactorC : Charactor
{
// Constructor
public CharactorC()
{
this._symbol = 'C';
this._height = 100;
this._width = 160;
this._ascent = 74;
this._descent = 0;
}

//Method
public override void SetPointSize(int size)
{
this._pointSize = size;
}

public override void Display()
{
Console.WriteLine(this._symbol +
"pointsize:" + this._pointSize);
}
}
//--控制共享的单见构造
public class CharactorFactory
{
// Fields
private System.Collections.Generic.Dictionary<string, Charactor> charactors = new System.Collections.Generic.Dictionary<string, Charactor>();
//--负责参数
private System.Collections.Generic.Dictionary<string, CharactorType> charactorsType = new System.Collections.Generic.Dictionary<string, CharactorType>();
private static CharactorFactory instance;
// Constructor
private CharactorFactory()
{
charactors.Add("A", new CharactorA());
charactorsType.Add("A",new CharactorType (11));

}

// Property
public static CharactorFactory Instance
{
get
{
if (instance == null)
{
instance = new CharactorFactory();
}
return instance;
}
}

// Method,共享数据
public Charactor GetCharactor(string key)
{
Charactor charactor = charactors[key] as Charactor;

if (charactor == null)
{
switch (key)
{
case "A": charactor = new CharactorA(); break;
case "B": charactor = new CharactorB(); break;
case "C": charactor = new CharactorC(); break;
//
}
charactors.Add(key, charactor);
}
charactor.SetPointSize(charactorsType[key].pointSize);
return charactor;
}
}

/// <summary>
/// 保存参数列表
/// </summary>
public class CharactorType
{
public int pointSize;
public CharactorType(int size )
{
this.pointSize = size;
}

}
public class Program
{
public static void Main()
{
CharactorFactory factory = CharactorFactory.Instance;

// Charactor "A"
CharactorA ca = (CharactorA)factory.GetCharactor("A");

ca.Display();


}
}

charactorsType里面也写成共享参数的
- -赫赫看大大门的文章有感,随便写写,不对请指教

  回复  引用    
#33楼2007-03-05 09:22 | Eric.Zhang[未注册用户]
我另有看法。
上述楼主提到的节省了对象的数量,其实从根本上是为了节省内存空间。
应该说享元模式注重的是:节省空间。减少对象的数量只是一个表面现象。
我的写法如下:

using System;
using System.Collections;
using System.Text;

namespace ForTest
{
public class CharactorWarpper
{
#region Charactor Segment

private class Charactor
{
//private member variables..
private char fChar;

//private Constructor
private Charactor(char charactor)
{
fChar = charactor;
}

public char Char
{
get
{
return fChar;
}
}

//static Segment
private static Hashtable fCharactors = null;
static Charactor()
{
fCharactors = new Hashtable();
}

public static Charactor CreateCharactor(char charactor)
{
Charactor c = fCharactors[charactor] as Charactor;
if (c == null)
{
c = new Charactor(charactor);
Console.WriteLine(string.Format("--{0} Has Been Created!", charactor));
fCharactors.Add(charactor, c);
}
return c;
}

#endregion


}

private Charactor fCharactor = null;
private int fSize = 9;//Default is 9

public CharactorWarpper(char charactor)
{
fCharactor = Charactor.CreateCharactor(charactor);
}

public CharactorWarpper(char charactor,int size) : this(charactor)
{
fSize = size;
}

public override string ToString()
{
return string.Format("{0}-{1}", fCharactor.Char,fSize);
}

}

public class TestCharactorWarpper
{
public void Execute()
{
char[] chars = new char[] { 'A', 'B', 'C', 'D', 'E', 'F','F','E','D','C','B','A' };
foreach (char c in chars)
{
CharactorWarpper cw = new CharactorWarpper(c);
Console.WriteLine(cw.ToString());
}
}
}
}

---------------------------------------------
在啰嗦几句,我的写法和肖鹏的模式基本一致。认为可以把Charactor对象通用的抽取出来,单独放在另外的对象中。而新的特定对象通过引用的方式使用该对象的信息。

BTW: 如果全部采用Decorator模式,在字处理方面将会面临很大的性能问题,因为字的属性太多了,会导致太多的修饰。

Eric.Zhang / www.applexml.com






  回复  引用  查看    
#34楼2007-03-13 21:26 | YaziMyWife      
正如LS的朋友所说享元模式归根结底是为了节省内存而不是减少对象数量,所以我觉得再使用一下装饰模式和组合模式应该也是达到了享元模式的目的
  回复  引用    
#35楼2007-05-09 17:17 | 飞扬跋扈
顶!
同时顶肖鹏、Eric.Zhang

  回复  引用    
#36楼2007-06-29 10:43 | XphteR[未注册用户]
肖鹏的方法不错。
LZ的例子可以用共享+修饰来解决。对于字处理的这个例子,我想,绝对做到对象共享不太可能,呵呵。
这个例子单独用桥接模式也可解决,但违背了共享的初衷 :D
[个人愚见]

  回复  引用    
#37楼2007-08-07 13:36 | Allan[未注册用户]
代码两个地方有问题:

单件模式 + 内蕴外蕴混合

  回复  引用  查看    
#38楼2007-08-21 10:59 | Sink      
期待出书,
有漏字请更正:

.NET框架中的Flyweight

Flyweight更多时候的时候一种底层的设计模式

(第二行的第二个"的时候"是否应改为"是")

  回复  引用  查看    
#39楼2007-08-21 12:06 | Sink      
最后CharactorFactory的单例少了static??
  回复  引用  查看    
#40楼2007-09-25 23:16 | 暗香浮动      
看了很多写设计模式的。看来看去还是你这里写的最清楚。
循序渐进的分析思路很容易理解。
可惜没有写完。
还可惜好久没看到你写的东西了。

  回复  引用    
#41楼2007-09-29 11:29 | kevinlzf[未注册用户]
thanks
  回复  引用    
#42楼2008-01-29 16:36 | 汤金华[未注册用户]
楼主举的例子有问题,对象指向的是同一个引用,pointSize并不是所说的可以保证不相同,
执行:
CharactorA ca = (CharactorA)factory.GetCharactor("A");
ca.SetPointSize(12);

CharactorA cb = (CharactorA)factory.GetCharactor("A");
cb.SetPointSize(13);

ca.Display();
cb.Display();
执行结果:
pointsize:13
pointsize:13

  回复  引用    
#43楼2008-04-10 15:57 | yangjohnnnyyyyy[未注册用户]
感谢楼主!
  回复  引用  查看    
#44楼2008-11-08 07:39 | 王某      
是否这样理解享元模式?
1. 例子中的享元模式其实是基于工厂模式的享元模式.
2. 享元模式实际是单件模式的一个增强.
3. 享元模式,其实类似一个容器,将需要重复全多次被创建的对像, 只创建一次并放到这个容器中, 如果有需要,则从这个容器中查找并取出.

这样理解是否正确?

  回复  引用  查看    
#45楼[楼主]2008-11-09 12:08 | TerryLee      
@王某
嗯,可以这么理解,但要记住一点:享元模式关注的大量细粒度的对象创建,这点跟单件模式有些不同。

  回复  引用  查看    
#46楼2009-01-01 17:16 | pillow      
享元模式是对拷贝对象的应用,原型模式是对浅拷贝对象的应用.讲拷贝,浅拷贝,深拷贝弄清楚了对这两个设计模式就好理解了.
  回复  引用  查看    
#47楼[楼主]2009-01-04 11:09 | TerryLee      
@pillow
嗯,拷贝的基本原理必须搞清楚,才能更好的理解这个模式。

  回复  引用  查看    
#48楼2009-01-07 19:00 | pillow      
flyweight pattern 是sigleton模式的扩展,只是这个singleton存在一个对象表。
  回复  引用  查看    
#49楼2009-01-07 19:01 | pillow      
@王某
理解的太对了!

  回复  引用  查看    
#50楼2009-06-26 14:25 | 史泽昊      
目前为止我看到写的最好的模式讲解了,为什么不写完那,加油博主!



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 361767




相关文章:

相关链接: