java23中设计模式——结构模式——Flyweight(享元)
2019獨角獸企業重金招聘Python工程師標準>>>
面向對象語言的原則就是一切都是對象,但是如果真正使用起來,有時對象數可能顯得
很龐大,比如,字處理軟件,如果以每個文字都作為一個對象,幾千個字,對象數就是幾千,
無疑耗費內存,那么我們還是要"求同存異",找出這些對象群的共同點,設計一個元類,封
裝可以被共享的類,另外,還有一些特性是取決于應用(context),是不可共享的,這也
Flyweight 中兩個重要概念內部狀態 intrinsic 和外部狀態 extrinsic 之分。
說白點,就是先捏一個的原始模型,然后隨著不同場合和環境,再產生各具特征的具體
模型,很顯然,在這里需要產生不同的新對象,所以 Flyweight 模式中常出現 Factory 模
式.Flyweight 的內部狀態是用來共享的,Flyweight factory 負責維護一個
Flyweight pool(模式池)來存放內部狀態的對象.
Flyweight模式是一個提高程序效率和性能的模式,會大大加快程序的運行速度.應
用場合很多:比如你要從一個數據庫中讀取一系列字符串,這些字符串中有許多是重復的,
那么我們可以將這些字符串儲存在Flyweight池(pool)中.
享元模式(Flyweight):運用共享的技術有效地支持大量細粒度的對象。
?????? 抽象享元角色(Flyweight):此角色是所有的具體享元類的超類,為這些類規定出需要實現的公共接口或抽象類。那些需要外部狀態(External State)的操作可以通過方法的參數傳入。抽象享元的接口使得享元變得可能,但是并不強制子類實行共享,因此并非所有的享元對象都是可以共享的。
?????? 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定的接口。如果有內部狀態的話,必須負責為內部狀態提供存儲空間。享元對象的內部狀態必須與對象所處的周圍環境無關,從而使得享元對象可以在系統內共享。有時候具體享元角色又叫做單純具體享元角色,因為復合享元角色是由單純具體享元角色通過復合而成的。
?????? 復合享元(UnsharableFlyweight)角色:復合享元角色所代表的對象是不可以共享的,但是一個復合享元對象可以分解成為多個本身是單純享元對象的組合。復合享元角色又稱做不可共享的享元對象。這個角色一般很少使用。
?????? 享元工廠(FlyweightFactoiy)角色:本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象請求一個享元對象的時候,享元工廠角色需要檢查系統中是否已經有一個符合要求的享元對象,如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個新的合適的享元對象。
?????? 客戶端(Client)角色:本角色還需要自行存儲所有享元對象的外部狀態。
?????? 內部狀態與外部狀態:在享元對象內部并且不會隨著環境改變而改變的共享部分,可以稱之為享元對象的內部狀態,反之隨著環境改變而改變的,不可共享的狀態稱之為外部狀態。
?????? 現在讓我們通過一個面向對象的文本編輯器設計來說明享元模式的應用。假設我們要設計一個文本編輯器,而且它必須創建字符對象來表示文檔中的每個字符,現在讓我們考慮字符對象保持什么信息呢?如:字體、字體大小和位置等等信息。
?????? 一個文檔通常包含許多字符對象,它們需要大容量的內存。值得我們注意的是一般字符都是由數字、字母和其他字符組成的(它們是固定的,可知的),這些字符對象可以共享字體和字體大小等信息,現在它們專有屬性只剩下位置了,每個字符對象只需保持它們在文檔中的位置就OK了,通過分析我們已經降低了編輯器的內存需求。
?
?
?
圖2 享元模式(Flyweight)共享對象
?
///?<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));}}
?
?????? 現在我們定義了一個字符享元類,其中符合、字體和字體大小都是內部狀態,而位置則是外部狀態。
?
///?<summary> ///?A?'ConcreteFlyweight'?class///?</summary>public?class?CharacterA?:?Character{????public?CharacterA(){_symbol?=?'A';_size?=?10;_font?=?"宋體";????????//_position?=?new?Position(0,?1);????} }?????? 接著我們定義具體字符A的享元類,并且對內部狀態符號、字體和字體大小進行初始化,而且其他字符B到Z享元類都類似。
?
?
圖3具體享元模式(ConcreteFlyweight)設計
?
?
///?<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;}} }
?
?????? 現在我們定義了一間字符工廠,通過一個Dictionary<Tkey, Tvalue>來保存字符對象,使用字符值來查找字符對象是否已經創建了,如果查找的字符對象已經存在,那么直接返回該對象,反之就創建字符對象實例。
?
///?<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)測試結果
?
?????? 接著讓我們實現一個享元模式的繪圖程序,假設我們的程序要畫各種各樣的圓,而且圓的屬性有形狀,位置和顏色,其中形狀和顏色是內部狀態,而位置是外部狀態。
設計分析:
1.提供一個抽象類Shape,讓具體的形狀如:Circle繼承于它
2.定義一個位置結構圖記錄每個圖形的位置
3.設計一間享元圖形工廠用來創建圖形對象
?????? 以上就是我們的享元模式的繪圖程序的設計,接下來讓我們實現享元模式的繪圖程序吧!
?
///?<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); }?????? 上述示意代碼定義了一個抽象類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,它實現Draw()方法通過Graphics調用DrawEllipse()方法來實現畫圓。
?
///?<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;?}} }?????? 接著我們定義享元工廠負責創建圖形對象,如果圖形對象不存在就創建該對象,反正直接返回該圖形對象。
?
///?<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;}} }??????? 現在我們已經完成了享元圖形類,由于圖形的外部狀態包括位置和顏色,前面我們通過隨機函數生成隨機位置,我們要設計一個拾色板來提供用戶選擇自定義顏色。
?
?圖5拾色板設計
?
?????? 由于時間的關系我們已經把拾色板的界面設置,接下來讓我們實現拾色板的具體功能。
?????? 首先我們新建一個用戶自定義控件命名為ColorPanel,接著我們要處理用戶點擊選擇顏色的事件
?
//?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); }?????? 上述示意代碼定義了一個委托ColorChangedHandler,當顏色值發現改變時相應具體處理方法和一個事件ColorChangedHandler,其實事件是對委托的封裝,猶如字段和屬性的關系,具體委托和事件的介紹請參看這里和這里。
?
圖6自定義事件
?
?????? 我們先介紹一下EventArgs這個的類型。其實這個類并沒有太多的功能,它主要是作為一個基類讓其他類去實現具體的功能和定義,當我們自定義事件參數時都必須繼承于該類。
?????? 現在回到我們自定義事件參數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;?}} }
?
?????? 現在我們終于完成了拾色板的基本功能了,接著只需把拾色板控件添加到我們的應用程序中就OK了。
?
?
圖6享元模式繪圖程序界面
?
?????? 由于時間的關系我們已經把程序的界面設計好了,接下來讓我們實現一系列的事件處理方法。
?
///?<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(); }
?
?????? 上面我們定義了處理繪圖點擊方法和清除圖形的方法,當用戶選擇顏色值時,我們的程序到享元工廠中獲取該對象實例,這個對象可能是新建的,也可能是已經存在的。
?
圖7繪圖程序效果
解釋一下概念:也就是說在一個系統中如果有多個相同的對象,那么只共享一份就可以了,不必每個都去實例化一個對象。比如說一個文本系統,每個字母定一個對象,那么大小寫字母一共就是52個,那么就要定義52個對象。如果有一個1M的文本,那么字母是何其的多,如果每個字母都定義一個對象那么內存早就爆了。那么如果要是每個字母都共享一個對象,那么就大大節約了資源。
在Flyweight模式中,由于要產生各種各樣的對象,所以在Flyweight(享元)模式中常出現Factory模式。Flyweight的內部狀態是用來共享的,Flyweight factory負責維護一個對象存儲池(Flyweight Pool)來存放內部狀態的對象。Flyweight模式是一個提高程序效率和性能的模式,會大大加快程序的運行速度.應用場合很多,下面舉個例子:
先定義一個抽象的Flyweight類:
[java]?view plain?copy?print?
package?Flyweight;??
public?abstract?class?Flyweight{??
public?abstract?void?operation();??
}??
實現一個具體類:
[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);??
}??
}??
實現一個工廠方法類:
[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??
//產生新的ConcreteFlyweight??
flyweight?=?new?ConcreteFlyweight((String)obj);??
flyweights.put(obj,?flyweight);//--------------------------------------5??
}??
return?flyweight;//---------------------------------------------------------6??
}??
public?int?getFlyweightSize(){??
return?flyweights.size();??
}??
}??
這個工廠方法類非常關鍵,這里詳細解釋一下:
在1處定義了一個Hashtable用來存儲各個對象;在2處選出要實例化的對象,在6處將該對象返回,如果在Hashtable中沒有要選擇的對象,此時變量flyweight為null,產生一個新的flyweight存儲在Hashtable中,并將該對象返回。
最后看看Flyweight的調用:
[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();??
}??
}??
下面是運行結果:
[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個對象,其中有5個是相同的,按照Flyweight模式的定義“Google”應該共享一個對象,在實際的對象數中我們可以看出實際的對象卻是只有2個。
總結:
Flyweight(享元)模式是如此的重要,因為它能幫你在一個復雜的系統中大量的節省內存空間。在JAVA語言中,String類型就是使用了享元模式。String對象是final類型,對象一旦創建就不可改變。在JAVA中字符串常量都是存在常量池中的,JAVA會確保一個字符串常量在常量池中只有一個拷貝。String a="abc",其中"abc"就是一個字符串常量。
熟悉java的應該知道下面這個例子:
[java]?view plain?copy?print?
String?a?=?"hello";??
String?b?=?"hello";??
if(a?==?b)??
System.out.println("OK");??
else??
System.out.println("Error");??
輸出結果是:OK。可以看出if條件比較的是兩a和b的地址,也可以說是內存空間
核心總結,可以共享的對象,也就是說返回的同一類型的對象其實是同一實例,當客戶端要求生成一個對象時,工廠會檢測是否存在此對象的實例,如果存在那么直接返回此對象實例,如果不存在就創建一個并保存起來,這點有些單例模式的意思。通常工廠類會有一個集合類型的成員變量來用以保存對象,如hashtable,vector等。在java中,數據庫連接池,線程池等即是用享元模式的應用。
轉載于:https://my.oschina.net/dengdajun/blog/643595
總結
以上是生活随笔為你收集整理的java23中设计模式——结构模式——Flyweight(享元)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis(方法通用)开机自启动
- 下一篇: 【windows核心编程】系统消息与自定