java23中设计模式——结构模式——Flyweight(享元)
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
面向?qū)ο笳Z(yǔ)言的原則就是一切都是對(duì)象,但是如果真正使用起來,有時(shí)對(duì)象數(shù)可能顯得
很龐大,比如,字處理軟件,如果以每個(gè)文字都作為一個(gè)對(duì)象,幾千個(gè)字,對(duì)象數(shù)就是幾千,
無(wú)疑耗費(fèi)內(nèi)存,那么我們還是要"求同存異",找出這些對(duì)象群的共同點(diǎn),設(shè)計(jì)一個(gè)元類,封
裝可以被共享的類,另外,還有一些特性是取決于應(yīng)用(context),是不可共享的,這也
Flyweight 中兩個(gè)重要概念內(nèi)部狀態(tài) intrinsic 和外部狀態(tài) extrinsic 之分。
說白點(diǎn),就是先捏一個(gè)的原始模型,然后隨著不同場(chǎng)合和環(huán)境,再產(chǎn)生各具特征的具體
模型,很顯然,在這里需要產(chǎn)生不同的新對(duì)象,所以 Flyweight 模式中常出現(xiàn) Factory 模
式.Flyweight 的內(nèi)部狀態(tài)是用來共享的,Flyweight factory 負(fù)責(zé)維護(hù)一個(gè)
Flyweight pool(模式池)來存放內(nèi)部狀態(tài)的對(duì)象.
Flyweight模式是一個(gè)提高程序效率和性能的模式,會(huì)大大加快程序的運(yùn)行速度.應(yīng)
用場(chǎng)合很多:比如你要從一個(gè)數(shù)據(jù)庫(kù)中讀取一系列字符串,這些字符串中有許多是重復(fù)的,
那么我們可以將這些字符串儲(chǔ)存在Flyweight池(pool)中.
享元模式(Flyweight):運(yùn)用共享的技術(shù)有效地支持大量細(xì)粒度的對(duì)象。
?????? 抽象享元角色(Flyweight):此角色是所有的具體享元類的超類,為這些類規(guī)定出需要實(shí)現(xiàn)的公共接口或抽象類。那些需要外部狀態(tài)(External State)的操作可以通過方法的參數(shù)傳入。抽象享元的接口使得享元變得可能,但是并不強(qiáng)制子類實(shí)行共享,因此并非所有的享元對(duì)象都是可以共享的。
?????? 具體享元(ConcreteFlyweight)角色:實(shí)現(xiàn)抽象享元角色所規(guī)定的接口。如果有內(nèi)部狀態(tài)的話,必須負(fù)責(zé)為內(nèi)部狀態(tài)提供存儲(chǔ)空間。享元對(duì)象的內(nèi)部狀態(tài)必須與對(duì)象所處的周圍環(huán)境無(wú)關(guān),從而使得享元對(duì)象可以在系統(tǒng)內(nèi)共享。有時(shí)候具體享元角色又叫做單純具體享元角色,因?yàn)閺?fù)合享元角色是由單純具體享元角色通過復(fù)合而成的。
?????? 復(fù)合享元(UnsharableFlyweight)角色:復(fù)合享元角色所代表的對(duì)象是不可以共享的,但是一個(gè)復(fù)合享元對(duì)象可以分解成為多個(gè)本身是單純享元對(duì)象的組合。復(fù)合享元角色又稱做不可共享的享元對(duì)象。這個(gè)角色一般很少使用。
?????? 享元工廠(FlyweightFactoiy)角色:本角色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)毓蚕怼.?dāng)一個(gè)客戶端對(duì)象請(qǐng)求一個(gè)享元對(duì)象的時(shí)候,享元工廠角色需要檢查系統(tǒng)中是否已經(jīng)有一個(gè)符合要求的享元對(duì)象,如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè)新的合適的享元對(duì)象。
?????? 客戶端(Client)角色:本角色還需要自行存儲(chǔ)所有享元對(duì)象的外部狀態(tài)。
?????? 內(nèi)部狀態(tài)與外部狀態(tài):在享元對(duì)象內(nèi)部并且不會(huì)隨著環(huán)境改變而改變的共享部分,可以稱之為享元對(duì)象的內(nèi)部狀態(tài),反之隨著環(huán)境改變而改變的,不可共享的狀態(tài)稱之為外部狀態(tài)。
?????? 現(xiàn)在讓我們通過一個(gè)面向?qū)ο蟮奈谋揪庉嬈髟O(shè)計(jì)來說明享元模式的應(yīng)用。假設(shè)我們要設(shè)計(jì)一個(gè)文本編輯器,而且它必須創(chuàng)建字符對(duì)象來表示文檔中的每個(gè)字符,現(xiàn)在讓我們考慮字符對(duì)象保持什么信息呢?如:字體、字體大小和位置等等信息。
?????? 一個(gè)文檔通常包含許多字符對(duì)象,它們需要大容量的內(nèi)存。值得我們注意的是一般字符都是由數(shù)字、字母和其他字符組成的(它們是固定的,可知的),這些字符對(duì)象可以共享字體和字體大小等信息,現(xiàn)在它們專有屬性只剩下位置了,每個(gè)字符對(duì)象只需保持它們?cè)谖臋n中的位置就OK了,通過分析我們已經(jīng)降低了編輯器的內(nèi)存需求。
?
?
?
圖2 享元模式(Flyweight)共享對(duì)象
?
///?<summary> ///?The?'Flyweight'?class.///?</summary>public?class?Character{????//?intrinsic?state????protected?char?_symbol;????protected?int?_size;????protected?string?_font;????//?extrinsic?state????protected?Position?_position;????public?void?Display(Position?position){????????Console.WriteLine(????????????String.Format("Symbol:?{0}?Size:?{1}?Font:?{2}?Position:?{3}?{4}",_symbol,?_size,?_font,?position._x,?position._y));}}
?
?????? 現(xiàn)在我們定義了一個(gè)字符享元類,其中符合、字體和字體大小都是內(nèi)部狀態(tài),而位置則是外部狀態(tài)。
?
///?<summary> ///?A?'ConcreteFlyweight'?class///?</summary>public?class?CharacterA?:?Character{????public?CharacterA(){_symbol?=?'A';_size?=?10;_font?=?"宋體";????????//_position?=?new?Position(0,?1);????} }?????? 接著我們定義具體字符A的享元類,并且對(duì)內(nèi)部狀態(tài)符號(hào)、字體和字體大小進(jìn)行初始化,而且其他字符B到Z享元類都類似。
?
?
圖3具體享元模式(ConcreteFlyweight)設(shè)計(jì)
?
?
///?<summary> ///?The?'FlyweightFactory'?class///?</summary>public?class?CharacterFactory{????//?Keeps?the?character?object?by?specifying?key/value.????private?Dictionary<char,?Character>?_characters?=????????new?Dictionary<char,?Character>();????public?Character?this[char?key]{????????get????????{????????????Character?character?=?null;????????????//?Checked?the?character?whether?existed?or?not,//?if?the?character?existed,?then?directly?returns,//?otherwise,?instantiates?a?character?object.????????????if?(_characters.ContainsKey(key)){character?=?_characters[key];}????????????else????????????{????????????????string?name?=?this.GetType().Namespace?+?"."?+??????????????????????????????"Character"?+?key.ToString();character?=?Activator.CreateInstance(????????????????????????Type.GetType(name))?as?Character;_characters.Add(key,?character);}????????????return?character;}} }
?
?????? 現(xiàn)在我們定義了一間字符工廠,通過一個(gè)Dictionary<Tkey, Tvalue>來保存字符對(duì)象,使用字符值來查找字符對(duì)象是否已經(jīng)創(chuàng)建了,如果查找的字符對(duì)象已經(jīng)存在,那么直接返回該對(duì)象,反之就創(chuàng)建字符對(duì)象實(shí)例。
?
///?<summary> ///?The?client.///?</summary> ///?<param?name="args">The?args.</param>[STAThread]static?void?Main(string[]?args) {????Application.EnableVisualStyles();????Application.SetCompatibleTextRenderingDefault(false);????Application.Run(new?FrmFlyweight());????string?text?=?"ABZABBZZ";????char[]?letters?=?text.ToCharArray();????var?characterFactory?=?new?CharacterFactory();????//?Creates?random?position?ranges?0?to?100.????var?rd?=?new?Random();????foreach?(char?c?in?letters){????????Character?character?=?characterFactory[c];????????var?p?=?new?Position(rd.Next(0,?100),?rd.Next(0,?100));character.Display(p);}????Console.ReadKey(); }?
圖4享元模式(ConcreteFlyweight)測(cè)試結(jié)果
?
?????? 接著讓我們實(shí)現(xiàn)一個(gè)享元模式的繪圖程序,假設(shè)我們的程序要畫各種各樣的圓,而且圓的屬性有形狀,位置和顏色,其中形狀和顏色是內(nèi)部狀態(tài),而位置是外部狀態(tài)。
設(shè)計(jì)分析:
1.提供一個(gè)抽象類Shape,讓具體的形狀如:Circle繼承于它
2.定義一個(gè)位置結(jié)構(gòu)圖記錄每個(gè)圖形的位置
3.設(shè)計(jì)一間享元圖形工廠用來創(chuàng)建圖形對(duì)象
?????? 以上就是我們的享元模式的繪圖程序的設(shè)計(jì),接下來讓我們實(shí)現(xiàn)享元模式的繪圖程序吧!
?
///?<summary> ///?Shape?can?be?inherited?by?Circle,?Retangle?or?triangle?and?so?forth.///?Includes?a?color?property?and?Draw?methods.///?</summary>public?abstract?class?Shape{????public?Color?Color?{?get;?set;?}????public?abstract?void?Draw(Graphics?graphics,?Position?position); }?????? 上述示意代碼定義了一個(gè)抽象類Shape,我們的具體圖形都必須繼承于該類。
?
///?<summary> ///?Circle?implements?Shape.///?</summary>public?class?Circle?:?Shape{????public?Circle(Color?color){Color?=?color;}????///?<summary>///?Draws?circle?with?the?specified?graphics?and?position.????///?</summary>///?<param?name="graphics">The?graphics.</param>///?<param?name="position">The?position?of?circle.</param>????public?override?void?Draw(Graphics?graphics,?Position?position){????????var?pen?=?new?Pen(Color);graphics.DrawEllipse(pen,?position.X?-?position.R,position.Y?-?position.R,position.R,?position.R);} }????? 接著我們定義具體圖形類Circle,它實(shí)現(xiàn)Draw()方法通過Graphics調(diào)用DrawEllipse()方法來實(shí)現(xiàn)畫圓。
?
///?<summary> ///?Generate?the?position?of?concrete?shape.///?</summary>public?struct?Position{????private?int?_x;????private?int?_y;????private?int?_r;????public?Position?GetPosition(Form?form){????????var?rd?=?new?Random();_x?=?rd.Next(0,?form.Width);_y?=?rd.Next(0,?form.Height);????????float?r?=?_x?<?_y???_x?:?_y;_r?=?rd.Next(0,?(int)r);????????return?this;}????public?Position(Graphics?graphics,?int?x,?int?y,?int?r){????????if?(x?>?graphics.DpiX)????????????throw?new?ArgumentOutOfRangeException("x");????????if?(y?>?graphics.DpiY)????????????throw?new?ArgumentOutOfRangeException("y");????????if?(r?>?graphics.DpiY?&&?r?>?graphics.DpiX)????????????throw?new?ArgumentOutOfRangeException("r");_x?=?x;_y?=?y;_r?=?r;}????public?int?X{????????get?{?return?_x;?}}????public?int?Y{????????get?{?return?_y;?}}????public?int?R{????????get?{?return?_r;?}} }?????? 接著我們定義享元工廠負(fù)責(zé)創(chuàng)建圖形對(duì)象,如果圖形對(duì)象不存在就創(chuàng)建該對(duì)象,反正直接返回該圖形對(duì)象。
?
///?<summary> ///?The?flyweight?factory///?Generates?the?instance?of?shape?if?object?not?exists,///?otherwish?returns?the?object?directly.///?</summary>public?class?ShapeFactory{????//?Saves?the?shape?object?in?Dictionary<Color,?Shape>????private?static?readonly?Dictionary<Color,?Shape>?Shapes?=????????new?Dictionary<Color,?Shape>();????//?Gets?the?object?in?Dictionray.????public?Shape?this[Color?key]{????????get????????{????????????Shape?shape?=?null;????????????//?if?the?object?exists?return?directly.//?otherwish?generates?anew?one.????????????if?(Shapes.ContainsKey(key)){shape?=?Shapes[key];}????????????else????????????{shape?=?new?Circle(key);Shapes.Add(key,?shape);}????????????return?shape;}} }??????? 現(xiàn)在我們已經(jīng)完成了享元圖形類,由于圖形的外部狀態(tài)包括位置和顏色,前面我們通過隨機(jī)函數(shù)生成隨機(jī)位置,我們要設(shè)計(jì)一個(gè)拾色板來提供用戶選擇自定義顏色。
?
?圖5拾色板設(shè)計(jì)
?
?????? 由于時(shí)間的關(guān)系我們已經(jīng)把拾色板的界面設(shè)置,接下來讓我們實(shí)現(xiàn)拾色板的具體功能。
?????? 首先我們新建一個(gè)用戶自定義控件命名為ColorPanel,接著我們要處理用戶點(diǎn)擊選擇顏色的事件
?
//?Sets?the?default?color.private?Color?_color?=?Color.Black;public?delegate?void?ColorChangedHandler(object?sender,?ColorChangedEventArgs?e);public?event?ColorChangedHandler?ColorChanged;///?<summary> ///?Raises?the?<see?cref="E:ColorChanged"/>?event.///?</summary> ///?<param?name="e">The?color?changed?event?arguments.</param>protected?virtual?void?OnColorChanged(ColorChangedEventArgs?e) {????if?(null?!=?ColorChanged)ColorChanged(this,?e); }?????? 上述示意代碼定義了一個(gè)委托ColorChangedHandler,當(dāng)顏色值發(fā)現(xiàn)改變時(shí)相應(yīng)具體處理方法和一個(gè)事件ColorChangedHandler,其實(shí)事件是對(duì)委托的封裝,猶如字段和屬性的關(guān)系,具體委托和事件的介紹請(qǐng)參看這里和這里。
?
圖6自定義事件
?
?????? 我們先介紹一下EventArgs這個(gè)的類型。其實(shí)這個(gè)類并沒有太多的功能,它主要是作為一個(gè)基類讓其他類去實(shí)現(xiàn)具體的功能和定義,當(dāng)我們自定義事件參數(shù)時(shí)都必須繼承于該類。
?????? 現(xiàn)在回到我們自定義事件參數(shù)ColorChangedEventArgs,其中包含初始化顏色值的方法和獲取顏色值的屬性。
?
?
///?<summary> ///?The?color?changed?event?arguments.///?</summary>public?class?ColorChangedEventArgs?:?EventArgs{????private?readonly?Color?_color;????///?<summary>///?Initializes?a?new?instance?of?the?<see?cref="ColorChangedEventArgs"/>?class.????///?</summary>///?<param?name="color">The?color.</param>????public?ColorChangedEventArgs(Color?color){_color?=?color;}????///?<summary>///?Gets?the?color.????///?</summary>????public?Color?Color{????????get?{?return?_color;?}} }
?
?????? 現(xiàn)在我們終于完成了拾色板的基本功能了,接著只需把拾色板控件添加到我們的應(yīng)用程序中就OK了。
?
?
圖6享元模式繪圖程序界面
?
?????? 由于時(shí)間的關(guān)系我們已經(jīng)把程序的界面設(shè)計(jì)好了,接下來讓我們實(shí)現(xiàn)一系列的事件處理方法。
?
///?<summary> ///?Handles?the?Click?event?of?the?btnDrawCircle?control.///?</summary> ///?<param?name="sender">The?source?of?the?event.</param> ///?<param?name="e">The?<see?cref="System.EventArgs"/> ///??instance?containing?the?event?data.</param>private?void?btnDrawCircle_Click(object?sender,?EventArgs?e) {????Graphics?graphics?=?Graphics.FromImage(_drawArea);????//?Gets?shape?object?with?specified?color?in?flyweight?object.????Shape?shape?=?_factory[colorPanel1.Color];shape.Draw(graphics,?_position.GetPosition(this));????this.Invalidate(); }///?<summary> ///?Handles?the?Click?event?of?the?btnClear?control.///?</summary> ///?<param?name="sender">The?source?of?the?event.</param> ///?<param?name="e">The?<see?cref="System.EventArgs"/>? ///?instance?containing?the?event?data.</param>private?void?btnClear_Click(object?sender,?EventArgs?e) {????Graphics?graphics?=?Graphics.FromImage(_drawArea);graphics.Clear(Color.SkyBlue);graphics.Dispose();????this.Invalidate(); }
?
?????? 上面我們定義了處理繪圖點(diǎn)擊方法和清除圖形的方法,當(dāng)用戶選擇顏色值時(shí),我們的程序到享元工廠中獲取該對(duì)象實(shí)例,這個(gè)對(duì)象可能是新建的,也可能是已經(jīng)存在的。
?
圖7繪圖程序效果
解釋一下概念:也就是說在一個(gè)系統(tǒng)中如果有多個(gè)相同的對(duì)象,那么只共享一份就可以了,不必每個(gè)都去實(shí)例化一個(gè)對(duì)象。比如說一個(gè)文本系統(tǒng),每個(gè)字母定一個(gè)對(duì)象,那么大小寫字母一共就是52個(gè),那么就要定義52個(gè)對(duì)象。如果有一個(gè)1M的文本,那么字母是何其的多,如果每個(gè)字母都定義一個(gè)對(duì)象那么內(nèi)存早就爆了。那么如果要是每個(gè)字母都共享一個(gè)對(duì)象,那么就大大節(jié)約了資源。
在Flyweight模式中,由于要產(chǎn)生各種各樣的對(duì)象,所以在Flyweight(享元)模式中常出現(xiàn)Factory模式。Flyweight的內(nèi)部狀態(tài)是用來共享的,Flyweight factory負(fù)責(zé)維護(hù)一個(gè)對(duì)象存儲(chǔ)池(Flyweight Pool)來存放內(nèi)部狀態(tài)的對(duì)象。Flyweight模式是一個(gè)提高程序效率和性能的模式,會(huì)大大加快程序的運(yùn)行速度.應(yīng)用場(chǎng)合很多,下面舉個(gè)例子:
先定義一個(gè)抽象的Flyweight類:
[java]?view plain?copy?print?
package?Flyweight;??
public?abstract?class?Flyweight{??
public?abstract?void?operation();??
}??
實(shí)現(xiàn)一個(gè)具體類:
[java]?view plain?copy?print?
package?Flyweight;??
public?class?ConcreteFlyweight?extends?Flyweight{??
private?String?string;??
public?ConcreteFlyweight(String?str){??
string?=?str;??
}??
public?void?operation()??
{??
System.out.println("Concrete---Flyweight?:?"?+?string);??
}??
}??
實(shí)現(xiàn)一個(gè)工廠方法類:
[java]?view plain?copy?print?
package?Flyweight;??
import?java.util.Hashtable;??
public?class?FlyweightFactory{??
private?Hashtable?flyweights?=?new?Hashtable();//----------------------------1??
public?FlyweightFactory(){}??
public?Flyweight?getFlyWeight(Object?obj){??
Flyweight?flyweight?=?(Flyweight)?flyweights.get(obj);//----------------2??
if(flyweight?==?null){//---------------------------------------------------3??
//產(chǎn)生新的ConcreteFlyweight??
flyweight?=?new?ConcreteFlyweight((String)obj);??
flyweights.put(obj,?flyweight);//--------------------------------------5??
}??
return?flyweight;//---------------------------------------------------------6??
}??
public?int?getFlyweightSize(){??
return?flyweights.size();??
}??
}??
這個(gè)工廠方法類非常關(guān)鍵,這里詳細(xì)解釋一下:
在1處定義了一個(gè)Hashtable用來存儲(chǔ)各個(gè)對(duì)象;在2處選出要實(shí)例化的對(duì)象,在6處將該對(duì)象返回,如果在Hashtable中沒有要選擇的對(duì)象,此時(shí)變量flyweight為null,產(chǎn)生一個(gè)新的flyweight存儲(chǔ)在Hashtable中,并將該對(duì)象返回。
最后看看Flyweight的調(diào)用:
[java]?view plain?copy?print?
package?Flyweight;??
import?java.util.Hashtable;??
public?class?FlyweightPattern{??
FlyweightFactory?factory?=?new?FlyweightFactory();???
Flyweight?fly1;??
Flyweight?fly2;??
Flyweight?fly3;??
Flyweight?fly4;??
Flyweight?fly5;??
Flyweight?fly6;??
/**?*//**?Creates?a?new?instance?of?FlyweightPattern?*/??
public?FlyweightPattern(){??
fly1?=?factory.getFlyWeight("Google");??
fly2?=?factory.getFlyWeight("Qutr");??
fly3?=?factory.getFlyWeight("Google");??
fly4?=?factory.getFlyWeight("Google");??
fly5?=?factory.getFlyWeight("Google");??
fly6?=?factory.getFlyWeight("Google");??
}??
public?void?showFlyweight(){??
fly1.operation();??
fly2.operation();??
fly3.operation();??
fly4.operation();??
fly5.operation();??
fly6.operation();??
int?objSize?=?factory.getFlyweightSize();??
System.out.println("objSize?=?"?+?objSize);??
}??
public?static?void?main(String[]?args){??
System.out.println("The?FlyWeight?Pattern!");??
FlyweightPattern?fp?=?new?FlyweightPattern();??
fp.showFlyweight();??
}??
}??
下面是運(yùn)行結(jié)果:
[java]?view plain?copy?print?
Concrete---Flyweight?:?Google??
Concrete---Flyweight?:?Qutr??
Concrete---Flyweight?:?Google??
Concrete---Flyweight?:?Google??
Concrete---Flyweight?:?Google??
Concrete---Flyweight?:?Google??
objSize?=?2??
我們定義了6個(gè)對(duì)象,其中有5個(gè)是相同的,按照Flyweight模式的定義“Google”應(yīng)該共享一個(gè)對(duì)象,在實(shí)際的對(duì)象數(shù)中我們可以看出實(shí)際的對(duì)象卻是只有2個(gè)。
總結(jié):
Flyweight(享元)模式是如此的重要,因?yàn)樗軒湍阍谝粋€(gè)復(fù)雜的系統(tǒng)中大量的節(jié)省內(nèi)存空間。在JAVA語(yǔ)言中,String類型就是使用了享元模式。String對(duì)象是final類型,對(duì)象一旦創(chuàng)建就不可改變。在JAVA中字符串常量都是存在常量池中的,JAVA會(huì)確保一個(gè)字符串常量在常量池中只有一個(gè)拷貝。String a="abc",其中"abc"就是一個(gè)字符串常量。
熟悉java的應(yīng)該知道下面這個(gè)例子:
[java]?view plain?copy?print?
String?a?=?"hello";??
String?b?=?"hello";??
if(a?==?b)??
System.out.println("OK");??
else??
System.out.println("Error");??
輸出結(jié)果是:OK。可以看出if條件比較的是兩a和b的地址,也可以說是內(nèi)存空間
核心總結(jié),可以共享的對(duì)象,也就是說返回的同一類型的對(duì)象其實(shí)是同一實(shí)例,當(dāng)客戶端要求生成一個(gè)對(duì)象時(shí),工廠會(huì)檢測(cè)是否存在此對(duì)象的實(shí)例,如果存在那么直接返回此對(duì)象實(shí)例,如果不存在就創(chuàng)建一個(gè)并保存起來,這點(diǎn)有些單例模式的意思。通常工廠類會(huì)有一個(gè)集合類型的成員變量來用以保存對(duì)象,如hashtable,vector等。在java中,數(shù)據(jù)庫(kù)連接池,線程池等即是用享元模式的應(yīng)用。
轉(zhuǎn)載于:https://my.oschina.net/dengdajun/blog/643595
總結(jié)
以上是生活随笔為你收集整理的java23中设计模式——结构模式——Flyweight(享元)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis(方法通用)开机自启动
- 下一篇: ASP.NET MVC中使用DropDo