by Joanna Carter
譯文:skyblue(轉(zhuǎn)載請(qǐng)注明作者)
在一篇已經(jīng)發(fā)表的文章中做了微小改動(dòng);可以從這篇文章中看到關(guān)于Model View Presenter(模型-視圖-推薦者)的概念更勝于Model View Controller(模型-視圖-控制器)?!暗俏疫€不知道什么是模型-視圖-控制器!”,你可能會(huì)說。好,在本文得最后篇章中我希望你得問題或者其他更多得問題能夠得到解答。
在最近得文章中,我已經(jīng)討論過oo設(shè)計(jì)模式,包括觀察者模式,并且我猜想MVC可能被當(dāng)成一種超級(jí)觀察者;但是我想最好把它描述成一種開發(fā)框架。
為了不使你有太多迷惑,我將忽略MVC中控制器的概念,并用MVP中的推介者作為替代來描述;他們完成大多數(shù)相同的工作,不同的是推介者在GUI腳本中的操作有點(diǎn)詭異。
讓我們開始描述MVP的構(gòu)成元素。
模型(Model)
模型在一個(gè)系統(tǒng)中表示一個(gè)具體對(duì)象的數(shù)據(jù)和行為,例如,一個(gè)列表或者一個(gè)樹。模型將扮演驅(qū)動(dòng)視圖的角色。
視圖(View)
視圖是一種顯示下層模型的可視方式。例如,TListView 或者 TTreeView。視圖將扮演模型的觀察者角色。
選擇(Selection)
模型維護(hù)一個(gè)可供選擇的對(duì)象,這個(gè)對(duì)象可以反映當(dāng)前視圖中突出的條目。任何指令發(fā)給模型處理都依賴這個(gè)選擇對(duì)象。
指令(Command)
一個(gè)指令是模型上預(yù)知的每個(gè)行為。處理者和交互都要求模型處理指令。指令可以不被處理或者重新被處理,從而為結(jié)構(gòu)提供基礎(chǔ)。指令通常作為觀察者,在選擇模式中執(zhí)行相同的操作于每個(gè)條目上。
處理者(Handler)
處理者用作獲取簡(jiǎn)單的鼠標(biāo)和菜單事件并對(duì)事件做出不需要在一個(gè)GUI組件上寫入其邏輯反映的一種方式。一個(gè)處理者保存一個(gè)相關(guān)指令的引用。
交互(Interactor)
一個(gè)交互被用作處理復(fù)雜的事件,例如拖放,和處理者一樣,從任何一個(gè)GUI元素上分離邏輯反映。一個(gè)交互通常從處理者派生
推薦者(Presenter)
推介者被依賴于:
管理來自于視圖的輸入表示
根據(jù)GUI選擇對(duì)象刷新模型選擇對(duì)象
激活和撤銷處理者和交互
管理和執(zhí)行指令
組件(Componet)
在一個(gè)窗體里每個(gè)可視控件通常以一種特殊格式呈現(xiàn)數(shù)據(jù);然而這里有不止需要一個(gè)在下層數(shù)據(jù)上的視圖的地方。MVP允許在一個(gè)單獨(dú)組件里面同時(shí)有數(shù)據(jù)封裝(model),可視外觀(View)和輸入管理(Presenter)。這些組件能夠聚合形成大的組件和應(yīng)用程序。
回到現(xiàn)實(shí)世界
討論理論固然很好,但是在這個(gè)例子里面,我將創(chuàng)建一個(gè)簡(jiǎn)單但是完整的組件示范一下這些組成部分怎樣建立和協(xié)同工作的。
我將描述的這個(gè)組件將基于字符串列表;在每個(gè)人大喊“難道這不是多余的?”之前,我必須強(qiáng)調(diào)一點(diǎn),這是學(xué)習(xí)實(shí)踐的捷徑而且我們需要從簡(jiǎn)單學(xué)起。
在我開始示范MVP之前,我需要為觀察者模式定義一些接口:
ISubject = interface;
IObserver = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
procedure Update(Subject: ISubject);
end;
ISubject = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
procedure Attach(Observer: IObserver);
procedure Detach(Observer: IObserver);
procedure Notify;
end;
你可能注意到觀察者包含一個(gè)update方法;因?yàn)樵赿elphi里面許多可視組件都包含一個(gè)update方法不需要ISubject參數(shù),我們需要做一些特別的組件用作視圖。
模型
下一步,我們需要定義基于這個(gè)mvp組件的模型的行為:
IListModel = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
procedure BeginUpdate;
procedure EndUpdate;
function GetCount: Integer;
function GetItem(Idx: Integer): string;
procedure Add(Item: string);
procedure Clear;
procedure Insert(Item, Before: string);
procedure Move(Item, Before: string);
procedure Remove(Item: string);
property Count: Integer
read GetCount;
property Item[Idx: Integer]: string
read GetItem;
end;
因?yàn)槊總€(gè)在這個(gè)模型中改變的屬性應(yīng)該告知它的觀察者對(duì)象它已經(jīng)改變了,BeginUpdate 和 EndUpdate用來讓我們告訴事件發(fā)生,不是每個(gè)細(xì)小的變化都會(huì)被告知。隨著我們討論這個(gè)實(shí)現(xiàn)類這將變得明朗。
GetCount和GetItem都是屬性的簡(jiǎn)單的存取方法。
增加和移除方法是我們定義模型的行為。
由于接口不包含實(shí)現(xiàn),現(xiàn)在我們需要?jiǎng)?chuàng)建一個(gè)能執(zhí)行定義的方法的類:
TListModel = class(TInterfacedObject, IListModel, ISubject)
這個(gè)類的申明有兩點(diǎn)需要解釋一下:
我們必須從TInterfacedObject繼承勝于從TObject,因?yàn)檫@將自動(dòng)讓我們定義_AddRef, _Release and QueryInterface;這都是實(shí)現(xiàn)任何接口所必須的。
用接口允許我們建立一個(gè)實(shí)現(xiàn)類包括更多的行為。這不是多重繼承,但是它給我們用于支持更多的特征或者行為提供了可能。
private
fItems: TStringList;
fObservers: IInterfaceList;
fUpdateCount: Integer;
私有開始部分申明了三個(gè)成員:
fItems是一個(gè)一般對(duì)象指針用于保存我們管理的字符串列表
fObeservers是一個(gè)接口指針用于保存每個(gè)分離的觀察者列表;因?yàn)槲覀冇眠@個(gè)接口指針在用完之后我們將不需要釋放它。
fUpdateCount用于BeginUpdate和EndUpdate方法,我們將很快討論得。
隨著我們繼續(xù)討論這個(gè)類得方法,我想強(qiáng)調(diào)這些方法已經(jīng)申明在私有部分是因?yàn)橐粋€(gè)很好得原因。一個(gè)接口緊緊有'public',一旦實(shí)現(xiàn)對(duì)象已經(jīng)創(chuàng)建到IModel接口指針中,TModel類型將不再需要引用它。因此每個(gè)接口得所有方法可以而且必須寫在私有部分。
// IListModel
procedure BeginUpdate;
procedure EndUpdate;
function GetCount: Integer;
function GetItem(Idx: Integer): string;
procedure Add(Item: string);
procedure Clear;
procedure Insert(Item, Before: string);
procedure Move(Item, Before: string);
procedure Remove(Item: string);
// ISubject
procedure Attach(Observer: Observer);
procedure Detach(Observer: IObserver);
procedure Notify;
public
constructor Create; virtual;
destructor Destroy; override;
end;
現(xiàn)在讓我們看看這些方法得實(shí)現(xiàn)部分:
constructor TListModel.Create;
begin
inherited Create;
fItems := TStringList.Create;
fObservers := TInterfaceList.Create;
end;
destructor TListModel.Destroy;
begin
fItems.Free;
inherited Destroy;
end;
注意到我們把fObservers定義成一個(gè)TInterfaceList,由于我想保存一個(gè)接口指針得列表而且fObservers不需要釋放因?yàn)樗靡糜?jì)數(shù)接口指針再用完之后將全部垃圾回收。
function TListModel.GetCount: Integer;
begin
Result := fItems.Count;
end;
function TListModel.GetItem(Idx: Integer): string;
begin
Result := fItems[Idx];
end;
GetCount和GetItem可以自動(dòng)說明,但是現(xiàn)在我將實(shí)現(xiàn)方法影響到列表模型的狀態(tài):
procedure TListModel.Add(Item: string);
begin
BeginUpdate;
fItems.Add(Item);
EndUpdate;
end;
procedure TListModel.Clear;
begin
BeginUpdate;
fItems.Clear;
EndUpdate;
end;
procedure TListModel.Insert(Item, Before: string);
begin
BeginUpdate;
fItems.Insert(fItems.IndexOf(Before), Item);
EndUpdate;
end;
procedure TListModel.Move(Item, Before: string);
var
IndexOfBefore: Integer;
begin
BeginUpdate;
IndexOfBefore := fItems.IndexOf(Before);
if IndexOfBefore < 0 then
IndexOfBefore := 0;
fItems.Delete(fItems.IndexOf(Item));
fItems.Insert(IndexOfBefore, Item);
EndUpdate;
end;
procedure TListModel.Remove(Item: string);
begin
BeginUpdate;
fItems.Delete(fItems.IndexOf(Item));
EndUpdate;
end;
這些方法得代碼相當(dāng)直接,但是我想你應(yīng)該注意這些方法最后都調(diào)用了BeginUpdate和EndUpdate方法。這是這些方法得代碼:
procedure TListModel.BeginUpdate;
begin
Inc(fUpdateCount);
end;
procedure TListModel.EndUpdate;
begin
Dec(fUpdateCount);
if fUpdateCount = 0 then
Notify;
end;
運(yùn)用這些方法之后得想法不言而喻如果我們看到同時(shí)刷新很多屬性的時(shí)候。如果不用這種結(jié)構(gòu),再每個(gè)屬性改變之后,模型必須通知每個(gè)觀察者它已經(jīng)改變;如果相當(dāng)多的屬性很快發(fā)生了變化,這將觸發(fā)成倍連續(xù)性得可視刷新,導(dǎo)致視圖閃爍。然而,通過再每個(gè)屬性改變之前調(diào)用BeginUpdate,我們?cè)黾觙UpdateCount,它意味著當(dāng)?shù)谝粋€(gè)屬性發(fā)生改變,BeginUpdate和EndUpdate都將調(diào)用,但是測(cè)試fUpdateCount=0 失敗。當(dāng)我們?cè)偎懈淖兌纪瓿芍笳{(diào)用EndUpdate,fUpdateCount現(xiàn)在應(yīng)該被減小到0并調(diào)用Nodify。
最后,我們要實(shí)現(xiàn)這個(gè)模型得ISubject;但是注意當(dāng)加入一個(gè)觀察者后,我們需要調(diào)用Nodify確保新加入得觀察者已經(jīng)更新。
procedure TListModel.Attach(Observer: IObserver);
begin
fObservers.Add(Observer);
Notify;
end;
procedure TListModel.Attach(Observer: IObserver);
begin
fObservers.Remove(Observer);
end;
procedure TListModel.Notify;
var
i: Integer;
begin
for i := 0 to Pred(fObservers.Count) do
(fObservers[i] as IObserver).Update(self);
end;
視圖
或者這里應(yīng)該是“一個(gè)視圖”?用MVP框架得一個(gè)好處就是改變和增加視圖不影響模型。這是一個(gè)很長(zhǎng)簡(jiǎn)單得視圖用來表示我們得列表:
TListBoxView = class(TListBox, IObserver)
private
procedure IObserver.Update = ObserverUpdate;
procedure ObserverUpdate(Subject: ISubject);
end;
和TListModel一樣,任何接口得方法可以也必須申明再類得私有部分。因?yàn)槟銓⒆⒁獾?,一般類得方法名定義得和它的接口一樣;但是這里有個(gè)沖突再TControl的Update方法和IObserver的Update方法中,解決辦法就是在Update方法前加入IObserver指明。ObserverUpdate方法就是為了解決Update的命名沖突。
procedure TListBoxView.ObserverUpdate (Subject: ISubject);
var
Obj: IListModel;
i: Integer;
begin
Subject.QueryInterface(IListModel, Obj);
if Obj <> nil then
begin
Items.BeginUpdate;
Items.Clear;
for i := 0 to Pred(Obj.Count) do
Items.Add(Obj.Item[i]);
Items.EndUpdate;
end;
end;
所有這些都是為觀察者模式的觀察者建立一個(gè)可視控件所必須的,也是為了實(shí)現(xiàn)IObserver.Update方法;這樣就使得可視控件具有主觀意識(shí)。
Subject參數(shù)通過ISubject指針傳入這個(gè)方法,但是我們需要用TListModel來處理;第一行我們利用QueryInterface判斷是否傳入的Subject支
持IListModel接口。如果支持,Obj變量將設(shè)置成有效指針;如果不支持Obj將設(shè)置為nil。如果我們有一個(gè)有效Obj指針,我們現(xiàn)在就可以像對(duì)
待其他任何一個(gè)IListModel的引用使用Obj。
首先我們調(diào)用基于ListBox的Items字符串列表的BeginUpdate方法(避免閃爍);然后我們簡(jiǎn)單的清除字符串列表并讀入ListMode中的每個(gè)Item。
當(dāng)然最好緊緊更新那些已經(jīng)變化了的Items,但是我已經(jīng)說過這個(gè)實(shí)例將盡可能要簡(jiǎn)單!
另一視圖
當(dāng)我們創(chuàng)建我們第一個(gè)列表視圖,我想我將告訴你在ListModel上創(chuàng)建一個(gè)選擇性視圖會(huì)多么簡(jiǎn)單:
TComboBoxView = class(TComboBox, IObserver)
private
procedure IObserver.Update = ObserverUpdate;
procedure ObserverUpdate(Subject: ISubject);
end;
你將看到這個(gè)控件的申明和其他可視控件的申明多么類似;這個(gè)時(shí)候我想讓用戶在這個(gè)List Model上選擇一個(gè)Item。當(dāng)我們實(shí)現(xiàn)推介者的時(shí)候你將知道我用這個(gè)選擇條目來做什么。
procedure TComboBoxView.ObserverUpdate (Subject: ISubject);
var
Obj: IListModel;
i: Integer;
begin
Subject.QueryInterface(IListModel, Obj);
if Obj <> nil then
begin
Items.BeginUpdate;
Items.Clear;
for i := 0 to Pred(Obj.Count) do
Items.Add(Obj.Item[i]);
ItemIndex := 0;
Items.EndUpdate;
end;
end;
唯一不同之處就是Combo box視圖設(shè)置它的ItemIndex屬性以便這個(gè)選擇了的Item在編輯框中可見?,F(xiàn)在我將其值設(shè)置為0;這緊緊是個(gè)臨時(shí)值——以后將看到。
推介者
推介者的概念主要是處理所有視圖和模型之間的交互。為了根見清楚的明白推介者的作用,我將最先描述另一個(gè)參與在MVP框架中組件;這將會(huì)
導(dǎo)致這篇文章過長(zhǎng),所以我將在下期更加全面的討論。
如果我們緊緊描述MVC框架,可能所有的事件操作者都寫入控制器會(huì)比在GUI窗體上面好;這將讓我們和所有交互在列表MVC組件的上下文中的列表視圖處理所有的邏輯關(guān)系。當(dāng)我們創(chuàng)建好這些MVC組件時(shí),我們?cè)诖绑w上面可以簡(jiǎn)單的放置一個(gè)視圖組件,創(chuàng)建一個(gè)控制器并用這個(gè)可視組件為控制器的視圖屬性賦值。
這樣做我們將攔截控制器的所有這些事件并把這個(gè)視圖加入進(jìn)去作為Subject模型的一個(gè)觀察者。
IController = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
function GetModel: IModel;
function GetView: IView;
procedure SetModel(Value: IModel);
procedure SetView(Value: IView);
property Model: IModel
read GetModel
write SetModel;
property View: IView
read GetView
write SetView;
end;
我將完整的IController的申明;當(dāng)然實(shí)現(xiàn)類也要包括任何來自視圖的事件操作者并將通過那些操作者調(diào)用模型,但是隨著我們看到推介者和它相關(guān)聯(lián)的操作者和交互者,還有接下來篇章中的選擇模式和命令模式,我這里將不作更深的詳細(xì)闡述。
結(jié)束語(yǔ)
在這篇文章中我們開始學(xué)習(xí)關(guān)于MVP的概念;高級(jí)oo設(shè)計(jì)模式。
MVP解決一個(gè)包含了模型,視圖和推介者組成的組件的建立,并使之和選擇模式,命令模式,操作者模式和交互者模式相聯(lián)系。
我們建立了實(shí)現(xiàn)IListModel接口和ISubject接口的TListModel類。讓我們可以不用通過多重繼承混和不同的方法行為。
在建立TListModel中,我們注意到所有實(shí)現(xiàn)方法都應(yīng)該放在類的私有部分中因?yàn)椴荒芡ㄟ^接口被訪問他們。
視圖的建立討論了怎樣獲得一個(gè)標(biāo)準(zhǔn)可視控件并使它具有主觀意識(shí);我們也看到了怎樣解決Observer的Update方法和TControl的Update方法的
命名沖突。
最后我們簡(jiǎn)單的接觸到了推介者的概念,簡(jiǎn)單描述了控制器在MVC框架中怎樣扮演它的角色。
該段落就此結(jié)束,希望你將贊同MVP在建立一個(gè)好的設(shè)計(jì)和可以輕松維護(hù)的程序中確實(shí)是最有價(jià)值的扮演者。
part 2
在第一篇文章中,我們學(xué)到了關(guān)于MVP框架的概念,高級(jí)oo設(shè)計(jì)模式。
我們創(chuàng)建了一個(gè)列表模型類用于實(shí)現(xiàn)一個(gè)列表模型接口和一個(gè)Subject接口。在設(shè)計(jì)的兩個(gè)視圖中,討論了怎樣獲得一個(gè)標(biāo)準(zhǔn)可視控件并使它具
有主觀意識(shí);我們也看到了怎樣解決Observer的Update方法和TControl的Update方法的命名沖突。
我們僅僅是接觸了一點(diǎn)推介者的概念,簡(jiǎn)單描述了控制器在MVC框架中怎樣扮演它的角色。在我們學(xué)習(xí)一些MVP架構(gòu)中地其他部分之后,我們以后將更加全面地討論推介者。
The Latest Model
我們?cè)谏险鹿?jié)用到的列表模型只是簡(jiǎn)單的介紹了MVP的基本概念。現(xiàn)在我們需要修改IListModel的版本以便在模型的所有Item中我們都可以用接口指針。
我們可以利用接口這種方式寫出起連結(jié)作用的代碼,盡管實(shí)現(xiàn)接口有許多種方式,所以我們需要用字符串做一個(gè)接口的模型,并用一個(gè)類實(shí)現(xiàn)它。
IString = interface
['{xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx}']
function GetAsString: string;
procedure SetAsString(const Value: string);
property AsString: string
read GetAsString
write SetAsString;
end;
TString = class(TInterfacedObject, IString, IVisited)
private
fString: string;
// IString
function GetAsString: string;
procedure SetAsString(Value: string);
// IVisited
procedure Accept(Visitor: IVisitor);
end;
現(xiàn)在我們需要改變列表模型接口使其可以和普通的IInterface類型兼容:
IListModel = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
function GetCount: Integer;
function GetItem(Idx: Integer): IInterface;
procedure Add(const Item: IInterface);
procedure Clear;
procedure Insert(const Item, Before: IInterface);
procedure Move(const Item, Before: IInterface);
procedure Remove(const Item: IInterface);
property Count: Integer
read GetCount;
property Item[Idx: Integer]: IInterface
read GetItem;
end;
現(xiàn)在有了一個(gè)一般的列表模型,在這個(gè)接口里面我們可以接受任何Item類型,只要它實(shí)現(xiàn)IInterface。你將注意到BeginUpdate和EndUpdate方法沒有出現(xiàn);后面會(huì)提到:
TListModel = class(TInterfacedObject, IListModel, ISubject)
private
fItems: IInterfaceList;
fSubject: ISubject;
protected
property Items: IInterfaceList
read fItems;
// ISubject
property Subject: ISubject
read fSubject
implements ISubject;
// IListModel
procedure Add(const Item: IInterface);
procedure Clear;
function GetCount: Integer;
function GetItem(Idx: Integer): IInterface;
procedure Insert(const Item: IInterface; Before: IInterface);
procedure Move(const Item: IInterface; Before: IInterface);
procedure Remove(const Item: IInterface);
end;
這里我們申明了一個(gè)類,實(shí)現(xiàn)了IListModel接口和ISubject接口。這個(gè)類的主要部分和前一個(gè)基本相同,除了我們用IInterface和TInterfaceList代替了String和TStringList。在實(shí)現(xiàn)上這兩個(gè)版本的區(qū)別是不言而喻的:
procedure TListModel.Add(const Item: IInterface);
begin
fSubject.BeginUpdate;
if fItems = nil then
fItems := TInterfaceList.Create;
fItems.Add(Item);
fSubject.EndUpdate;
end;
procedure TListModel.Clear;
begin
fSubject.BeginUpdate;
fItems.Clear;
fItems := nil;
fSubject.EndUpdate;
end;
function TListModel.GetCount: Integer;
begin
if fItems <> nil then
Result := fItems.Count
else
Result := 0;
end;
function TListModel.GetItem(Idx: Integer): IInterface;
begin
Result := fItems[Idx];
end;
procedure TListModel.Insert(const Item, Before: IInterface);
var
InsertIdx: Integer;
begin
if fItems = nil then
fItems := TInterfaceList.Create;
if fItems.IndexOf(Item) < 0 then
begin
fSubject.BeginUpdate;
InsertIdx := fItems.IndexOf(Before);
if InsertIdx < 0 then
InsertIdx := 0;
fItems.Insert(InsertIdx, Item);
fSubject.EndUpdate;
end;
end;
procedure TListModel.Move(const Item, Before: IInterface);
var
IdxItem: Integer;
IdxBefore: Integer;
MoveItem: IInterface;
begin
if fItems <> nil then
begin
fSubject.BeginUpdate;
IdxItem := fItems.IndexOf(Item);
if IdxItem >= 0 then
begin
MoveItem := fItems[IdxItem];
fItems.Delete(IdxItem);
IdxBefore := fItems.IndexOf(Before);
if IdxBefore >= 0 then
fItems.Insert(IdxBefore, MoveItem);
fSubject.EndUpdate;
end;
end;
end;
procedure TListModel.Remove(const Item: IInterface);
begin
if fItems <> nil then
begin
fSubject.BeginUpdate;
fItems.Remove(Item);
fSubject.EndUpdate;
end;
end;
控制你的Subject
你可能注意到調(diào)用BeginUpdate實(shí)際上是作用著fSubject成員。利用接口的一個(gè)好處就是你可以用一個(gè)非常有力叫做聚合的技巧用另一個(gè)類委托一個(gè)接口的實(shí)現(xiàn);一個(gè)類只能被寫入一次然后就可以一次一次重復(fù)使用。
在MVP種,模型通常是有一個(gè)或更多的加入其中作為觀察者的視圖的Subjects。我們前面已經(jīng)討論過,這意味著模型發(fā)生改變時(shí)所有這些視圖都將被通告并更新他們自己。
現(xiàn)在我們可以為觀察者模式申明一個(gè)增強(qiáng)的接口:
IObserver = interface
['{7504BB57-65D8-4D5D-86F1-EC8FFED8ED5E}']
procedure Update(const Subject: IInterface);
end;
ISubject = interface
['{7A5BDDA0-C40C-40E5-BAB0-BF27E538C72A}']
procedure Attach(const Observer: IObserver);
procedure Detach(const Observer: IObserver);
procedure Notify;
procedure BeginUpdate;
procedure EndUpdate;
end;
現(xiàn)在你看到BeginUpdate和EndUpdate在上篇文章的IListModel中現(xiàn)在放到了ISubject接口中;這是因?yàn)樗麄儽挥脕碜鳛镾ubject更新機(jī)制的一個(gè)必須部分。
注意IObserver.Update現(xiàn)在用IInterface作為參數(shù)比ISubject要好;這樣使其更加具有通用型。
雖然IObserver要在每個(gè)需要更新的類中重新實(shí)現(xiàn),ISubject可以被一個(gè)類完全通用的實(shí)現(xiàn),如果它用作一個(gè)實(shí)現(xiàn)ISubject和其他接口類的子對(duì)象,例如TListModel。
TSubject = class(TInterfacedObject, ISubject)
private
fController: Pointer;
fObservers: IInterfaceList;
fUpdateCount: Integer;
function GetController: IInterface;
procedure Attach(const Observer: IObserver);
procedure Detach(const Observer: IObserver);
procedure Notify;
procedure BeginUpdate;
procedure EndUpdate;
public
constructor Create(const Controller: IInterface);
end;
在TSubject類中有三個(gè)明顯的地方:
1、fController和GetController用來使Notify方法可以傳遞一個(gè)包含這個(gè)Subject委托的類的實(shí)例,比傳遞它本身好,因?yàn)檫@意味觀察者對(duì)象將僅僅擁有ISubject接口的屬性,比需要實(shí)際的模型更好。
constructor TSubject.Create(const Controller: IInterface);
begin
inherited Create;
fController := Pointer(Controller);
end;
如果我們注意TSubject的構(gòu)造我們將看到我們強(qiáng)制儲(chǔ)存一個(gè)IInterface的引用;這樣避免在控制器對(duì)象上增加引用計(jì)數(shù)并讓Subject在適當(dāng)?shù)臅r(shí)候釋放。
function TSubject.GetController: IInterface;
begin
Result := IInterface(fController);
end;
GetController方法用于轉(zhuǎn)換指針變量為IInterface類型并不增加引用計(jì)數(shù)。
2、fObservers簡(jiǎn)單的獲取一個(gè)加入的Observers列表,是加入,分離和通知的重點(diǎn)。
procedure TSubject.Attach(const Observer: IObserver);
begin
if fObservers = nil then
fObservers := TInterfaceList.Create;
if fObservers.IndexOf(Observer) < 0 then
fObservers.Add(Observer);
end;
procedure TSubject.Detach(const Observer: IObserver);
begin
if fObservers <> nil then
begin
if fObservers.IndexOf(Observer) >= 0 then
fObservers.Remove(Observer);
if fObservers.Count = 0 then
fObservers := nil;
end;
end;
procedure TSubject.Notify;
var
i: Integer;
begin
if fObservers <> nil then
for i := 0 to Pred(fObservers.Count) do
(fObservers[i] as IObserver).Update(GetController);
end;