關注設計模式一兩年了.看到很多談論設計模式的人卻不理解設計模式的本意.他們隨便挑一個模式,按照類圖寫幾行代碼,或者干脆把函式改名為"Factory",以為就使用了Factory模式,于是欣欣然.離開了根去逐末,離之毫厘,失之千里!
1> 什么是設計模式?
設計模式是在某一場景下解決問題的通用的方法.
注意2點:
"場景": 這是問題的背景.是問題出現的背景關系.很多書籍中把"場景"叫做"動機".
"通用": 這意味著"方法"出現的頻率很高.我們一般都使用這種"方法"來解決"問題".
2如何學習設計模式?
我們要特別注意"場景",注意"問題"在什么樣的情況下產生的.看看我們遇到的問題是否也出現在這樣的場景下.
如果我們也在同樣的場景下遇到了同樣的問題,那么我們就可以用這個模式了.
離開了"場景"模式就不存在.
其次,要注意每一個模式的結構(也就是類圖).看看使用這種結構給我們帶來了什么好處.
3>構造型模式
a> Abstract Factory
場景: 構造一系列相關或相似的物件
b> Factory Method
場景: 我們只知道要構造一個東西,卻不知道具體要構造什么. Factory將構造具體的物件延遲到子類.
c> Build 模式
場景:將一個物件的復雜的構造方法從其表現中分離. 以便同一種構造方法能夠產生不同的表現。
這里給一個切換表單風格的方案。
表單的顯示風格是多樣的,有Windows風格,Mac風格,KDE風格等。
除了表單風格不同之外,表單的構造方法以及表單上各個組件的構造方法卻是相同的。
先假設表單上有一個TLable,一個TEdit,一個“確定”按鈕TButton,一個“取消”按鈕TButton
4> 結構型模式
a> Adpater 配接器
場景: 讓2個不兼容的介面變得兼容。
舉個例子:
比如我們已經有2個下載檔案的類:
一個使用WInet實作,一個使用Indy實作。
WInet:
TWInetDownload=class
function DownloadFile(Var Strm:TStream);
end;
Indy:
IDownload=interface
function GetString:String;
end;
TIndyDownload=class(TInterfacedObject,IDownload)
function GetString:String;
end;
同樣是在下載檔案,可是介面卻不同。
我們需要把介面變得兼容,以便作其他的操作。
TWinetDowndAdpater =class(TInterfacedObject,IDownload)
private
FWinet:TWInetDownload;
public
Constructor Create;
Destroctor Destroy;override;
function GetString:String;
end;
Constructor TWinetDowndAdpater.Create;
begin
inherited;
FWinet:=TWInetDownload.Create;
end;
Destroctor TWinetDowndAdpater.Destroy;
begin
FWinet.Free;
inherited;
end;
function TWinetDowndAdpater.GetString:String;
var
Strm:TMemoryStream;
begin
Strm:=TMemoryStream.Create;
try
FWinet.DownloadFile(Strm);
SetLenght(result,Strm.Size);
copyMemory(Result,Strm.Memory,Strm.Size);
finally
Strm.Free;
end;
end;
好了,TWinetDowndAdpater.類和TIndyDownload類有了相同的介面。這可以方便我們作很多事情,設計更好的結構。
比如,現在我們要對下載的檔案加密。
ICipher=Interface
function EncodeString(Str:String):String;
function DecodeString(Str:String):String;
end;
TMD5Cipher=class(TInterfacedObject,ICipher)
function EncodeString(Str:String):String;
function DecodeString(Str:String):String;
end;
TBase64Cipher=class(TInterfacedObject,ICipher)
function EncodeString(Str:String):String;
function DecodeString(Str:String):String;
end;
。。。。
。。。
好了,讓我們實作一個可以隨意選擇使用Indy還是WInet下載檔案,并可以隨意選擇使用不同加密方法加密下載后的檔案的類。
TCipherDownload=class
private
FDownload:IDownload;
FCipher:ICipher;
public
function GetCipherFileWithEncode:String;
Constructor Create(ADownload:IDownload; ACipher:ICipher);
Destroctor Destroy;override;
published
property Download:IDownload read FDownload write FDownload;
property Cipher:ICipher read FCipher write FCipher;
end;
//-------------------------------
Constructor Create(ADownload:IDownload; ACipher:ICipher);
begin
inherited;
FDownload:=ADownload;
FCipher:= ACipher;
end;
Function TCipherDownload.GetCipherFileWithEncode:String;
begin
result:= FCipher.EncodeString(FDownload.GetString);
end;
//-------------------------------
一個使用indy下載,MD5加密的代碼如下:
var
CipherDownload :TCipherDownload;
CipherFileWithEncode:String;
CipherDownload :=TCipherDownload.Create(TIndyDownload.Create,TMD5Cipher.Create);
try
CipherFileWithEncode:=CipherDownload .GetCipherFileWithEncode;
//CipherFileWithEncode保存了下載并加密后的結果。
finally
CipherDownload .Cipher:=nil;
CipherDownload .Download:=nil;
CipherDownload.free;
end;
一個使用WInet下載,Base64加密的代碼如下:
var
CipherDownload :TCipherDownload;
CipherFileWithEncode:String;
CipherDownload :=TCipherDownload.Create(TWinetDowndAdpater.Create,TBase64Cipher.Create);
try
CipherFileWithEncode:=CipherDownload .GetCipherFileWithEncode;
//CipherFileWithEncode保存了下載并加密后的結果。
finally
CipherDownload .Cipher:=nil;
CipherDownload .Download:=nil;
CipherDownload.free;
end;
同理,你可以很輕松地寫出 indy下載Base64加密,WInet下載MD5加密的代碼。
甚至,你可以自己寫一個(一些)下載檔案的類(只要他們繼承自IDownload介面),還可以自己寫一些加密演算法(只要它們繼承自ICipher介面),你就可以實作各種下載方法,各種加密方法了。 很輕松地在下載方法和加密演算法之間切換。
另外,以上的加密演算法實際上是橋接入CipherDownload 類的。見下面的橋接模式。
b>Brigde 橋接
場景: 如果一個類有2個發展方向。我們就把這2個發展方向分開,讓它們獨立發展。
就拿上面的例子來說,TCipherDownload 類有2個發展方向:
1, 不同的下載方式。
2, 不同的加密解密方法。
于是,我們就把加密演算法獨立出來,讓它獨立演化。
這會給我們帶來什么好處?
好了,假設我們不這樣做。
那么,如果你要實作 Indy下載MD5加密,indy下載Base64加密,WInet下載MD5加密,WInet下載Base64加密 ,你就必須寫4個不同的類。 如果現在要求加入另一種下載檔案的方法(假設使用WinSock下載吧),使用ElAES加密。那么你該怎么做?
你就必須寫9個類了。
(Indy下載MD5加密,indy下載Base64加密,indy下載ElAES加密,WInet下載MD5加密,WInet下載Base64加密,WInet下載ElAES加密,WinSock下載MD5加密,WinSock下載Base64加密,WinSock下載ElAES加密)。
這是不是很讓你頭大?!
Brigde模式的好處之一就是避免類爆炸。
c>visitor 訪問者
場景: 在不改變一個物件類結構的情況下,為物件添加新的操作。
舉個“病人看醫生的”例子. 這里的visitor是patient,
java:
訪問者介面----病人看(訪問)醫生的“方法”:
public interface IVisitor {
public Object visitor();
}
假設不同的病人報銷比例是不同的。
visitor方法回傳不同病人的報銷比例。
先看“醫生”類:
public class Doctor {
public Object Accept(IVisitor v){
return v.visitor();
}
public Doctor() {
super();
// TODO Auto-generated constructor stub
}
}
醫生類有一個“接受”病人的Accept方法。
老師的報銷比例是80%:
public class Teatcher implements IVisitor{
@Override
public Object visitor() {
// TODO Auto-generated method stub
return new Double(0.8);
}
public Teatcher() {
super();
// TODO Auto-generated constructor stub
}
}
公務員報銷100%:
public class OfficeBearer implements IVisitor{
@Override
public Object visitor() {
// TODO Auto-generated method stub
return new Double(1.0);
}
public OfficeBearer() {
super();
// TODO Auto-generated constructor stub
}
}
但是,病人的報銷種類是很多的。可能隨時會出現不同報銷比例的病人。
Visitor模式可以很容易地為醫生類添加功能。
假設,現在我這個平民老百姓去看病,報銷比例為0;
只要繼承IVisitor介面:
public class Civilian implements IVisitor{
@Override
public Object visitor() {
// TODO Auto-generated method stub
return new String("None");
}
public Civilian() {
super();
// TODO Auto-generated constructor stub
}
}
Docoter一樣可以呼叫Accept方法接受我的“訪問”。
uj5u.com熱心網友回復:
d>組合
定義:將物件以樹形結構組織起來,以表達“部分-整體” 的層次結構,使得客戶端對單個物件和組合物件的使用具有一致性.
e>觀察者 Observer
f>解釋器模式 Interceptor
定義: 定義語言的文法,并且建立一個解釋器來解釋該語言中的句子.
這個模式來源于<編譯原理>了.
比如我們現在要決議一個xml檔案.
這個xml檔案定義了幾個控制元件(xml檔案定義了文法):
<?xml version="1.0" encoding="GB2312" ?>
<Controls>
<Class Type="TEdit">
<Name>edAdd</Name>
<Association>
<Class Type="TLable">
<Name>lbAdd</Name>
<Capition>Type Here:</Capition>
</Class>
</Association>
<Text>Hello World!</Text>
</Class >
<Class Type="TButton">
<Name>btAdd</Name>
<Caption>OK</Caption>
<OnClick>TOnClickCommand</OnClick>
</Class >
</Controls>
我們決議這個xml檔案,并且生成2個控制元件.
解釋器模式,為xml檔案中的每個節點定義一個類.這個類實作決議該節點的"文法".
比如我們讀入第一個節點Class ,我們生成控制元件TEdit.接著讀入Name節點,我們設定TEdit的名稱.
接著讀入Association節點,我們設定 TEdit關聯的Label.....直到,讀入 </Class >,TEdit生成完畢.
IParser=Interface
procedure Parse;
end;
TNode=(TInterfacedPersistent,IParser)
private
FParent:TNode;
FChildNodes:TNodes;
FControl:TControl;
........
public
.......
constructor Create(AParent:TNode)
procedure Parse;virtual;
property Parent:TNode read FParent write FParent;
property ChildNodes:TNodes read FChildNodes write FChildNodes;
property Control:TControl read FControlwrite FControl;
end;
constructor TNode.Create(AParent:TNode);
begin
..............
Parent:=AParent;
if Parent <>nil then Parent.ChildNodes.Add(Self);
end;
procedure TNode.Parse;
begin
//加入決議當前節點的文法.
//基類中沒有文法.由子類實作.
......................
//parse childrens
with ChildNodes.GetEnumerater do
while MoveNext do
Current.Parse;
end;
TControlsNode=class(TNode);
--------------------------------------
TClassNode=class(TNode)
procedure Parse;
end;
procedure TClassNode.Parse;
begin
Control:=(GetClass(NodeValue).NewInstance).Create;//這句在決議節點的文法.
inherited Parse;
end;
----------------
TControlAttributeNode=class(TNode) //所有設定控制元件屬性節點類的基類.
procedure Parse;
end;
procedure TControlAttributeNode.Parse;
begin
SetPropValue(Control,NodeName,NodeValue);//這句就是決議節點的文法了.
inherited Parse;
end;
TNameNode=class(TControlAttributeNode);
TCapitionNode=class(TControlAttributeNode);
TTextNode=class(TControlAttributeNode);
TAssociationNode=class(TNode);
TEventNode=Class(TNode)
procedure Parse;
end;
procedure TEventNode.Parse;
var
PPInfo:PPropInfo;
begin
PPInfo:=GetPropInfo(Control,NodeName);
If Assigned(PPInfo)then SetMethodProp ( Control , PPInfo , TMethod(TOnClickCommand.Create(Control).Execute ));
end;
TOnClickNode=class(TEventNode);
假設TXml類讀入xml檔案,并建立節點間的樹狀關系(一個組合模式).
于是我們呼叫: xml=Txml.Create(XmlFilePath); Xml.RootNode.Parse; 所有控制元件都被正確創建并設定了屬性了.
實作得不錯吧? 呵呵~~
但我總感覺TNode類寫得不倫不類,,仔細想了想,原來它把組合模式和 解釋器模式揉和在一起了.. 看看那個ChildNodes, 不就是組合模式嗎?.看起來總覺得不舒服.
如何決議delphi 的dpr,dfm檔案呢? 你有思路了嗎?
g> 門面模式 facade
h> 中介者模式 meditor
i>責任鏈 chain of responsibility
場景:在責任鏈模式里,很多的物件由每一個物件對其下家的參考而聯接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。發出這個請求的客戶端并不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。
舉個例子: 假設我們要做一個漢譯英的軟體,而我們自己并不打算建立一個漢英詞典,我們動態地在網路上查找詞語的英文翻譯.
現假設, 我們在Google,Baidu,KingSoft(金山)上查找.
ISearch=interface
function Search(Chinese:string):String;
property Next:IHandler read GetNext write SetNext;
end;
TBaseSearch=class(TInterfacedObject,ISearch)
private
FNext: ISearch;
function GetNext : ISearch;
procedure SetNext(const Value: ISearch);
public
function Search(Chinese:string):String;virtual;//abstract;
property Next : ISearch read GetNext write SetNext;
end;
function TBaseSearch.Search(Chinese:string):String;
begin
if Result='' then //沒有找到英文翻譯
if Next<>nil then
Result:=Next.handle(Chinese);
end;
function TBaseHandle.GetNext :
ISearch ;
begin
result:=FNext;
end;
function TBaseHandle.SetNext(const Value:
ISearch
);
begin
if FNext<>Value then FNext:=Value;
end;
//-------------------------------------------------------
TGoogleSearch=class(TBaseSearch)
function Search(Chinese:string):String;override;
end;
function TGoogleSearch.
Search (Chinese:string):String;
begin
Result:=GetEnglishFromGoogle(Chinese); ///在google上查找,漢語單詞的英文翻譯,為了演示的簡單明了,這個函式我不準備實作.
inherited; //交給責任鏈上的下一個物件
end;
//----------------------------------------
TBaiduSearch=class(TBaseSearch)
function Search(Chinese:string):String;override;
end;
function TBaiduSearch.Search(Chinese:string):String;
begin
Result:=GetEnglishFromBaidu(Chinese); ///在Baidu上查找,漢語單詞的英文翻譯,為了演示的簡單明了,這個函式我不準備實作.
inherited; //交給責任鏈上的下一個物件
end;
//----------------------------------------
TKingSoftSearch=class(TBaseSearch)
function Search(Chinese:string):String;override;
end;
function TKingSoftSearch.Search(Chinese:string):String;
begin
Result:=GetEnglishFromKingSoft(Chinese); ///在KingSoft上查找,漢語單詞的英文翻譯,為了演示的簡單明了,這個函式我不準備實作.
inherited; //交給責任鏈上的下一個物件
end;
//---------------------客戶端實作----------------------------------
TClient=class
private
FHandler:ISearch;
public
function SearchWord(Chinese:String):String;
property Handler:IHandler read FHandler write FHandler;
end;
function TClient.SearchWord(Chinese:String):String;
begin
self.Handler:=TGoogleSearch.Create;
self.Handler.Next:=TBaiduSearch.Create;
self.Handler.Next.Next:=TKingsoftSearch.Create;
try
Result:=Self.Hanlder.Search(Chinese);
finally
self.Handler.Next.Next:=nil;
self.Handler.Next:=nil;
self.Handler:=nil;
end;
end;
在上面的實作中,"查找單詞的責任"在TGoogleSearch----TBaiduSearch------TKingsoftSearch間傳遞.
責任鏈上的哪個物件最終"查到了單詞的翻譯",客戶端并不知道.
我們可以重新組織責任鏈(比如TBaiduSearch----TKingsoftSearch------TGoogleSearch,這種重新組織也是有意義的,比如:現在國內的google速度不太行,而baidu和kingsoft則比較快,所以可以將它們放在責任鏈的前面。),甚至,可以在責任鏈上加入一些新的物件(比如有道網站搜索,TYouDaoSearch),只要這些物件實作了ISearch介面.
j> 模板方法模式
我們知道一個演算法所需的關鍵步驟。但是某些步驟的具體實作是未知的,或者說某些步驟的實作與具體的環境相關。 于是我們把這些關鍵步驟抽取出來,具體實作交給子類。efsd
uj5u.com熱心網友回復:
學習了。。。。uj5u.com熱心網友回復:
設計模式來聽課!uj5u.com熱心網友回復:
怎么不寫篇文章來總結下呢uj5u.com熱心網友回復:
太長了,先頂后看
uj5u.com熱心網友回復:
mark thank youuj5u.com熱心網友回復:
mark thnak youuj5u.com熱心網友回復:
關注。。。。。uj5u.com熱心網友回復:
看樓主就是一只童鞋。當C++還沒流行的時候,大家用C編程是不會講究設計模式的,自從有了多型這個概念以后,才開始關注代碼的結構,比較出名的就是四人幫寫的那本書,很多地方都沒有仔細說,樓主買本POSA4看看吧,中文電子版也有uj5u.com熱心網友回復:
一般比較常用的就是工廠模式,觀察者模式,以及它們的各種變體,還有組合模式也很實用,Gof里面有幾個模式就是組合模式的變體,樓主可以寫個簡單的mvc小爽一下,像Resource Pool、Object Manager這些也比較常用uj5u.com熱心網友回復:
還有一個純英文的版本,和樓主發的代碼一樣,支持下uj5u.com熱心網友回復:
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/101747.html
標籤:VCL組件開發及應用
上一篇:Delphi DLL中報錯floating point invalid operation 算術運算中發生溢位或下溢
