轉貼:
設計模式是美國一位建筑大師(同時也是信息工程師,畫家,機械工程師…的)克里斯蒂安.亞歷山大首先提出來的,很快被軟件界的技術員們所接受推廣,成為軟件工程里至高無上的法則之一(有興趣的人可以找他的《建筑的永恒之道》一書看看,相信會受益非淺)。簡單地說就是在面對對象的基礎上,包括面對對象,把要設計的整體的各個部分模式化,層次化,細粒度化,高度復用化,可控化,人性化。其中至高無上的原則是建立在需求的基礎之上,也就是說,無論做什么,人的需求要放在第一位考慮,從這個角度考慮整個系統是否足夠合理。這門學問是非常有趣的,尤其在flash中,可以應用到很多很好玩的實例中去。下面我按照一些通用的設計模式,舉例說明,有錯誤的地方,敬請高手指正:
1.抽象工廠模式(Abstract Factory):
食堂里吃的東西很多,而我只想吃一樣,那么食堂這個概念對我來說就是個抽象工廠,每個窗口可以看成它的一個具體實現,我要做的就是,去食堂,找到那個窗口,從窗口里買我要吃的東西。
舉例:flash前臺與asp后臺的交互,訪問某個動態頁面,從數據庫里取出需要的數據,通常的做法是在后臺就把數據集解析成xml字符串,再送給swf。每個業務邏輯模塊,所取出的數據結構,也就是xml的結構是不一樣的,我們要針對各個具體的業務邏輯,對相應的xml字符串解析,轉換成可供顯示的數組。也要把flash里文本輸入的內容轉換成 xml字符串,提交給后臺也面
AbstractFactory.as
//抽象工廠的接口
Interface AbstractFactory{
//生成xml解析工廠的具體實現
function createXmlParseFactory();
}
XMLParserGetFactory.as
//生成解析讀入的xml的對象的工廠
class XMLParserGetFactory implements AbstractFactory.{
var xmlParser;
function XMLParserGetFactory(str:String){
//生成解析器的具體實現,在后面會提到
}
function createXmlParser(){
return xmlParser;
}
}
XMLParserPostFactory.as
//生成解析輸出的xml的對象的工廠
class XMLParserPostFactory implements AbstractFactory.{
var xmlParser;
function XMLParserPostFactory(str:String){
//生成解析器的具體實現
}
function createXmlParser(){
return xmlParser;
}
}
這樣,我們讀入某個xml字符串時,在onLoad里面加入
//生成對留言板的留言列表解析的工廠
var xmlParser=new XMLParserGetFactory(“xmlParseGuestbookList”)
xmlParser= XMLParserGetFactory. createXmlParser()
備注:抽象工廠模式是軟件工程里最常用的設計模式之一,實現過程在于,需要某個類的實例時,通過某個工廠創建,而不是直接創建,坦白地說,它加大了開發工作量,但是對程序的層次性變得分明和降低耦合度有極大幫助。
2.生成器模式(builder):
還是那個說法,我要吃東西就去相應的食堂窗口,但我不能吃食堂窗口,窗口里的東西也許不少,我要跟師傅說,要這個,這個,還有這個。
舉例:我已經建立了 xml解析器的工廠,現在要返回解析器本身,就讓工廠創建,返回給我。
XMLParserGetFactory.as
//生成解析讀入的xml的對象的工廠
class XMLParserGetFactory implements AbstractFactory.{
var xmlParser;
function XMLParserGetFactory(str:String){
//如果要求留言板列表解析器,就生成一個
if(str==” xmlParseGuestbookList”){
xmlParser=new xmlParserGuestbookList();
}
}
function createXmlParser(){
//返回所要求的解析器
return xmlParser;
}
}
AbstractXmlParser.as
//抽象xml解析器
Interface AbstractXmlParser{
function ParseXml();
}
xmlParserGuestBookList.as
//留言板列表解析器
Class xmlParserGuestBookList implements AbstractXmlParser{
//把xml字符串里的內容解析到一堆數組里
function ParseXml(xml:XML,arrayID:Array,arrayTitle:Array){
//具體循環操作
}
}
使用的時候:
var xmlParser=new XMLParserGetFactory(“xmlParseGuestbookList”)
xmlParser= XMLParserGetFactory. createXmlParser(xml,arrayID,arrayTitle);
3.工廠方法模式(Factory Method)
我到了食堂窗口,如果師傅跟那兒抽煙,我還是吃不著東西。我說:師傅,打飯!師傅才會完成打飯這一動作。這是工廠方法模式,抽象工廠的實現通常用工廠方法模式來完成。
舉例:還是上一條,我本來想用一句話帶一個參數就實現具體xml解析器的實現,無奈構造函數沒有返回值,所以必須用
xmlParser= XMLParserGetFactory. createXmlParser(xml,arrayID,arrayTitle);
實現。
備注:抽象工廠模式,生成器模式和工廠方法模式需要靈活應用。
4.單件模式(singleton)
我前面一個人買了一條巨大的雞腿,我說我也要一條,師傅說,就這一條
舉例:單件模式的應用是相當廣泛的,它確保每個實例在全局范圍內只被創建一次,我們flash里的mc大多數是單件。內核里的核心組件也只是單件,比如我的消息映射列表(見后)。
按照單件模式的嚴格定義,應該讓類負責保存它的唯一實例。但是我在Flash里還想不到怎么實現這一點,或者實現它的意義所在,但另外一點我們可以做到,就是在全局范圍內只提供該對象的唯一訪問點。這可以由層次關系做到,把對該對象的訪問具體實現全部封裝在下層,只給上層提供唯一的訪問點(原因是,上層不知道這個單件的具體信息,比如路徑)。
看我內核文件的一部分:
Core.as
//內核
class Core {
var strucGlobalParam:ConfigVariables;
//站點信息
var xmlConfig:XML;
//站點信息的xml化對象
var ArrayStructureInitial:Array;
//用來提供給loadObject對象的數組
var ArrayForBtn:Array;
//用來初始化導航條組件的數組
var objInitial:loadObject;
//讀取影片的對象
var objMessageMap:MessageMap;
//消息映射組件
……
}
這是我的內核類也就是全站最核心類的數據結構。里面的數據只有通過下層的BasicMovie,OriginalFunctionObject等類(見后)直接訪問。
備注,核心思想是,確保只有一個。
5.原型模式(protoType)
到小炒窗口,看前面的哥們炒的青椒炒肉不錯的樣子?!皫煾?,我也要這樣的?!?br />
舉例:這對flash的用戶來說再熟悉不過了,我們經常用duplicateMovieClip()和
attachMovie()這兩個函數。按照一個原型復制相應的實例,各自執行自己的動作。在我的blog列表,導航條的生成。。幾乎用得到多項數據的地方就要用原型模式。
6.責任鏈模式,7.中介者模式,8.觀察者模式:
食堂里廚房最遠的窗口沒熬白菜了,要告訴廚房,快送過來。
責任鏈模式:一個窗口一個窗口地傳話,一直傳到食堂,食堂一看不妙,趕快做好送過去。
中介者模式:專門派一個人負責傳話,任何窗口沒菜了,就要這個人趕快去廚房催。
觀察者模式:廚房那邊派一個盯著,看哪個窗口沒菜了就開始大聲嚷嚷。
舉例:之所以要把這三個設計模式放在一塊兒,是因為我在我的站里面結合這三者建立了一個好玩的東西,可以說是我的網站的核心所在。它解決了我的flash里面各個mc的通信問題。
比如,影片A放完了,要通知影片B開始播放,直接的做法是在A的最后一幀,寫從A到B的相對路徑或B的絕對路徑,讓B play()。這樣做A和B的耦合性是相當高的,也就是說,相互依賴程度太高。運用設計模式的解決方案如下:
MessageMap.as
//消息映射類
class MessageMap extends Object {
var Message:String;
var MessageWatcher:Function;
var Target;
var MessageList:Array;
var Num_Msg:Number;
function MessageMap() {
Num_Msg = 0;
MessageList = new Array();
Message = "HANG_UP";
MessageWatcher = function (prop, oldVar, newVar, Param) {
for (var i = 0; iif (newVar == MessageList[i][0]) {
MessageList[i][1].apply(MessageList[i][3], MessageList[i][2]);
if (!MessageList[i][4]) {
MessageList.splice(i, 1);
Num_Msg--;
i-=1;
}
}
}
};
this.watch("Message", MessageWatcher, "test");
}
function SendMessage(Msg:String, mc:MovieClip) {
Message = Msg;
}
function UpdateMessageMap(Msg:String, objFunction:Function, ArrayParam:Array, objRefer,IsMultiUsed:Boolean) {
MessageList[Num_Msg] = new Array();
MessageList[Num_Msg][0] = new String();
MessageList[Num_Msg][0] = Msg;
MessageList[Num_Msg][1] = new Function();
MessageList[Num_Msg][1] = objFunction;
MessageList[Num_Msg][2] = new Array();
MessageList[Num_Msg][2] = ArrayParam;
MessageList[Num_Msg][3] = objRefer;
MessageList[Num_Msg][4] = IsMultiUsed;
Num_Msg++;
}
function DeleteMessageMap(objRefer) {
for (var i = 0; iif (MessageList[i][2] == objRefer) {
MessageList.splice(i, 1);
Num_Msg--;
}
}
}
}
class SubTemplateMovie extends BaseMovie {
var MovieRemoveFunction:Function;
function SubTemplateMovie() {
this.stop();
MovieStartFunction = function () {
Lock();
this.play();
};
MovieEndFunction = function () {
Lock();
this.play();
};
MovieRemoveFunction = function () {
this.stop();
SendMsg("SUB_TEMPLATE_REMOVED", this);
_parent.unloadMovie();
};
MovieMainFunction = function () {
stop();
SendMsg("SUB_TEMPLATE_OPEN", this);
};
UpdateMessage("LOADING_BAR_OVER", MovieStartFunction, null, this, false);
UpdateMessage("BACK_TO_INDEX", MovieEndFunction, null, this, false);
}
}
大概機制就是,影片提前提交一個數據結構,聲明,如果有影片提交這條消息,就執行這條函數。原理在于,發送消息,實際上是把消息映射的一個變量賦值,由于消息映射繼承自object類,可以用watch方法對該變量進行監視,一旦改變,在已經提交上來的消息映射列表里檢查,如果有,執行對應函數。實際上這也造成了一定程度的耦合性,但是我們已經成功地把耦合性控制在了下級類,上級子類完全不用理會這一套消息機制的實現過程。
這個機制可以讓我們對oop的真正目的有更深的看法。舉例說明,影片A播放完了,就聲明自己播放完了,至于我播完了你要干什么,不是我的事,我不控制你。所謂的降低耦合度是個相對概念,別忘了在計算機最底層,耦合度還是一樣,cpu總是不斷的直接或間接尋址,但我們需要做的是,改變系統的拓撲結構,把耦合度控制在某一個范圍之內。
整個消息映射類相當于一個中介者,內部生成一個觀察器,一旦觸發消息,以責任鏈的方式執行。
9.橋接模式(Bridge)
菜太淡,不合有些人的胃口,所以要求食堂的師傅,專門開一個窗口,專門在做好的菜里多加些辣椒。
我在自己的站里運用了橋接模式:所有的影片都繼承自我定義的BasicMovie 類(BasicMovie繼承自MovieClip類),但是在四個下級欄目的影片里,需要定義相同的方法和事件來響應消息,BasicMovie沒有這些函數,不符合要求,這時候,在四個影片里都寫一遍是愚蠢的,我又寫了一個SubTemplateMovie類繼承自BaseMovie,里面加進一些通用的方法,然后四個下級模板影片都繼承它,這樣大大簡化了后期開發。
BasicMovie.as
//基類影片
/所有影片的原始類,一切影片的父類都繼承此類而來
class BaseMovie extends MovieClip {
var isLocked:Boolean;
//初始類開始影片函數
var MovieStartFunction:Function;
//初始類影片主功能函數
var MovieMainFunction:Function;
//初始類結束影片函數
var MovieEndFunction:Function;
var GlobalParam
//初始類構造函數
function BaseMovie() {
}
//
//發送消息
function SendMsg(Msg:String, Mc:MovieClip) {
_root.objCore.objMessageMap.SendMessage(Msg, Mc);
}
//添加消息映射
function UpdateMessage(Msg:String, MsgMapFunction:Function, ArrayParam, obj, IsMultiUsed) {
_root.objCore.objMessageMap.UpdateMessageMap(Msg, MsgMapFunction, ArrayParam, obj, IsMultiUsed);
}
//刪除消息映射
function DeleteMessage(obj) {
_root.objCore.objMessageMap.DeleteMessageMap(obj);
}
function GetGlobalParam() {
GlobalParam=_root.objCore.strucGlobalParam;
}
}
SubTemplateMovie.as
//下級模板影片類
class SubTemplateMovie extends BaseMovie {
var MovieRemoveFunction:Function;
function SubTemplateMovie() {
this.stop();
MovieStartFunction = function () {
Lock();
this.play();
};
MovieEndFunction = function () {
Lock();
this.play();
};
MovieRemoveFunction = function () {
this.stop();
SendMsg("SUB_TEMPLATE_REMOVED", this);
_parent.unloadMovie();
};
MovieMainFunction = function () {
stop();
SendMsg("SUB_TEMPLATE_OPEN", this);
};
UpdateMessage("LOADING_BAR_OVER", MovieStartFunction, null, this, false);
UpdateMessage("BACK_TO_INDEX", MovieEndFunction, null, this, false);
}
}
注(關于消息映射機制看 責任鏈模式)
10.適配器模式(Adapter)
我要一碗湯,但是只有紙飯盒,還沒勺,所以食堂的師傅給了我一次性的湯碗和勺,這叫適配器。
適配器解決的是某一個類的對外接口不合用的問題,可能是參數或者返回值類型不符等問題造成的,這時候我們需要在工作對象和這個類之間加一層間接的層次。
這個模式我在底層的數據交換層用過。我說過,flash和asp.net之間交換數據全以xml為載體。返回xml在底層只有三層,數據庫操作,數據操作,數據顯示,由數據操作層返回給數據顯示層一個xml字符串就可以了。然后我就遇到一個小問題,在另一方面,我需要提交數據到數據庫,也是提交一個xml字符串,但是我需要數據庫里對應的表的數據集的xml表現形式的xsd驗證?。ㄒ豢跉庹f完,差點沒憋死)。就是說我至少需要取出這個表里的一條記錄,問題在于,我封裝的類從來只返回xml,沒有返回xsd的。解決辦法就是適配器,新建一個項目,加了一層專用于獲得xml驗證格式,這樣就完成了不同接口之間的轉換。
備注:適配器和橋接很象,都是在已有類不符合要求的時候,加入一層間接的元素以達到目的。不同的是適配器是解決不兼容接口之間的轉換,橋接一般不涉及這個問題,只是完成一個一對多的轉換。
11.外觀模式(Facade)
每天都要去食堂,每個人去不同的窗口吃不同的菜,很累,今天全寢室推舉猴子去打飯:
你吃這個,三兩飯,我吃那個,五兩飯,所有人都只跟猴子一個人交涉,食堂所有的師傅也只見猴子一個人。
舉例:這個模式在程序的上下層的通信之間可以應用得十分廣泛。Asp的每個模塊要去不同的數據,訪問數據庫的不同表,就要跟不同的下層數據訪問組件打交道。就是說,每個mc模塊必須知道,我要去哪個具體的數據訪問組件取數據。每個模塊要維持自己的一個,至少是字符串。
如果運用外觀模式。我們可以讓所有的需要數據交互的mc訪問同一個aspx頁面,比如getStrXml.aspx。只要傳送一個標示符,就可以通知這個唯一的取數據的葉面,訪問哪個下層組件獲取數據。下層組件不知道哪個mc要求數據,mc也不知道數據的具體來源,這樣,上下層之間互相都顯得不透明。這就降低了耦合度。
12.代理模式(Proxy)
可能我們不是每個人每天都想吃飯,所以我們要求猴子每天中午必須在寢室,如果我們要吃,他就去,如果我們都不吃,他愛干嘛干嘛。
舉例:這恐怕是每個人在flash里都會無意中用到的模式。比如,一個網站,它的下級欄目不用在整個網站初始化的時候一開始就讀進來,但是我們要確保,在瀏覽者想看并且點擊導航條上的某個按鈕時,能夠正確地讀進相應的影片文件,前提是,我們必須在內部保留一個索引,可以稱作代理。通常是一個空mc
13.策略模式:(strategy)
我每天先在食堂找座位,再打飯,再打菜,再買杯酸奶。這已經模式化。要是食堂有服務員,我也會要他這么做。
舉例,策略模式是把一系列的算法封裝起來,形成一個類。這個模式幾乎是隨時隨地都可以整合到別的模式里去的,我的那一堆xml解析器實際上就是策略模式的應用,這個模式還應用到我網站的下層,因為flash提交給aspx頁面的數據也是xml字符串,下層模塊也需要相應的解析算法。同樣的,我把對xml的解析封裝進了一個類。
//Cs文件里的解析函數
Class DataModel.BlogMsgs{
…
Public DataSet parseXML(string strXml){
DataSet ds=new DataSet();
//。。把xml裝載到DataSet 里
Return ds
}
…
}
14.享元模式(Flyweight)
東西不夠吃?給你擺20面鏡子~
師傅,東西還是只有一份。。。
關于這個模式十分抱歉,我暫時還沒想到在flash顯示層里面的實現。需要舉例說明的是,瀏覽器的機制是,在有大量文字的英文文檔里,相同的字母共享一個Flyweight,在內存里其實只占一份空間,然后在文檔不同的地方顯示,這樣對于大量細粒度的效果來說,可以節省很多資源。在下層,我們可以做到的就是,對某個全局對象只在某處真正保存,任何對象想要這個全局對象,只引用它而不復制它。
在顯示層里有哪位想到了該怎么做請一定賜教。
15.訪問者模式(Visitor)
只要愿意,我隨時都可以跑到哪個窗口打要吃的東西,前提是,我必須跑這一趟。
舉例:我說過,我的所有mc都繼承自BasicMovie這個類,但不是我的所有mc都要從后來獲取數據庫數據。獲取數據庫數據所要訪問的信息,比如ip,路徑,文件保存在配置文件里,初始化的時候讀入內核,并且只有內核那里有一份。在BasicMovie里加入對這些全局變量的引用是不合適的,因為只有少數mc要用到,而且由于某些原因我無法再使用橋接模式(我已經有了SubTemplateMovie,不能多繼承),所以我用了訪問者模式。
BasicMovie.as
//獲取全局變量
function GetGlobalParam() {
GlobalParam=_root.objCore.strucGlobalParam;
}
如果上級mc不執行這個函數,是不能獲取全局變量的,如果要用,就執行。
也就是說,需要的時候,我去訪問它。
備注:聲明一個visit操作,使得訪問者可以正確訪問需要的類。
16.狀態模式(state)
我今天想吃面,師傅問我:要什么料?西紅柿雞蛋,排骨還是牛肉?
舉例:狀態模式是指將對象當前的某些狀態局部化,當對象改變狀態時,看起來好像改變了類。例子還是我的滾動條。如果要滾動的是文本框,就要引用一個TextField的Scroll,maxscroll屬性,如果是mc,引用的是_y,_height屬性,我用一個參數將二者區分,由一個if語句控制,讓滾動條可以自由區別狀態。
另外一個解決方案是定義ScrollBar的不同子類,這兩者本質區別不大,在狀態比較多時,可能要維持一個龐大的if算法,這樣就用生成子類的方法比較好。
ScrollBar.as
//滾動條組件
function BindTo(mc,type:String,intMcHeight:Number,yinitial:Number){
ScrollType=type;
if(type=="TXT"){
scrollTxt=mc;
}
if(type=="MC"){
initialY=yinitial;
McHeight=intMcHeight;
scrollMc=mc;
}
}
function Scroll() {
if(ScrollType=="TXT")
this.onEnterFrame = function() {
scrollTxt.scroll = scrollTxt.maxscroll*mcBlock._y/(BgLength-BlockLength*3/2)
};
if(ScrollType=="MC"){
this.onEnterFrame=function(){
if(scrollMc._height>McHeight){
scrollMc._y=initialY-(scrollMc._height-McHeight)*mcBlock._y/(BgLength-BlockLength*3/2)}
}
}
}
備注:這也是常見模式,在flash的邏輯控制里尤其隨處可見
17.裝飾模式(Decorator)
在食堂吃飯,沒筷子怎么行?我是從來不帶飯盆的。師傅很人性化,每個窗口都放著一大把筷子,隨用隨拿。
這個模式如果用好,有的地方可以很省力。比如,我網站里的滾動條:
ScrollBar.as
//滾動條組件
class ScrollBar extends BaseMovie {
var BgLength:Number;
var BlockLength:Number;
var mcBlock:MovieClip
var Width:Number;
var ScrollType;
var scrollTxt:TextField;
var scrollMc:MovieClip;
var McHeight:Number
var initialY:Number
function ScrollBar() {
}
function InitialScrollBar(BgLength, BlockLength) {
this.BlockLength = BlockLength;
this.BgLength = BgLength;
}
function BindTo(mc,type:String,intMcHeight:Number,yinitial:Number){
ScrollType=type;
if(type=="TXT"){
scrollTxt=mc;
}
if(type=="MC"){
initialY=yinitial;
McHeight=intMcHeight;
scrollMc=mc;
}
}
function Scroll() {
if(ScrollType=="TXT")
this.onEnterFrame = function() {
scrollTxt.scroll = scrollTxt.maxscroll*mcBlock._y/(BgLength-BlockLength*3/2)
};
if(ScrollType=="MC"){
this.onEnterFrame=function(){
if(scrollMc._height>McHeight){
scrollMc._y=initialY-(scrollMc._height-McHeight)*mcBlock._y/(BgLength-BlockLength*3/2)}
}
}
}
function ScrollMc() {
}
function StopScroll() {
this.onEnterFrame=null;
}
function Reset(){
mcBlock._y=0;
}
}
核心函數是BindTo(),把這個滾動條的實例綁定到某個動態文本框或者某個mc上,就可以實現滾動。
備注:裝飾模式的思想是,在不影響其他對象的情況下,以動態,透明的方式給單個對象添加職責。
18.組合模式(composite)
我中午吃六兩飯,豬肉燉粉條,辣子雞,魚丸,咸鴨蛋,外加兩杯酸奶(豬?。?br />這些東西都是對象,他們共同組成了我的午飯。
舉例:應該說在Flash里組合模式是無處不在的,因為只要還有mc的嵌套,就有組合模式存在。幾個mc裝在一個mc里,這個裝載用的mc稱作容器。
但是就這么說,恐怕沒人會重視這個模式,因為不管理不理解他我們都在用。他的確有很多種實現方式,我的方式之一是這樣的。
//blog.as
我的Blog
class Blog extends BaseMovie {
//blog第一界面,包括日記列表,日歷,最近留言
var mcBlogList: mcBlogList;
//blog第二界面,包括日記全文,回復,對該日記的留言。
var mcBlogDairy:MovieClip;
var currentState:String;
var currentDairyID:Number;
function blog(){
}
}
mcBlogList.as
//blog第一界面
class mcBlogList extends BaseMovie {
//最近留言
var recentMsgs:blogMsgsSC;
//日記列表
var blogList:BlogList;
//日歷
var calendar:CalenderForBlog;
}
mcblogDairy.as
//blog第二界面
class mcBlogDairy extends BaseMovie {
//日記全文顯示
var BlogDairy:BlogDairy;
//留言板
var GuestBook:BlogInputMsg;
//留言列表顯示
var BlogMsgs:BlogMsgs;
}
然后里面每個組件都還包含另外的組件,比如滾動條,動態文本框什么的,我想說的是,我寫在as里的mc嵌套模式并不一定就符合fla文件里的具體物理嵌套模式,有很多裝飾性的mc不需要包含進來,也有很多具體原因,需要改寫路徑。比如在BlogDairy下,滾動條的實際路徑是BlogDairy.mc1.ScrollBar,不做成BlogDairy.ScrollBar可能是因為我需要mc1的動畫,也可能是別的具體原因。但是我們可以在mc1的時間軸上加一句
_parent. ScrollBar =This.ScrollBar
把路徑指向改寫,達到我們要使用的模式,ScrollBar就是BlogDairy的直接成員。另外我沒有語句初始化mc,我習慣在庫里設置mc的linkage。
備注:雖然組合模式是每個人必不可少要用的,但正因為如此,我們可以想出他更多更好更靈活的實現方式
19.備忘錄模式(memento)
~師傅,晚上的雞腿沒中午的新鮮啊。
~胡說!這就是中午的。
舉例:開個玩笑,上面兩句話不是備忘錄模式的本意,實際上我一時想不出在食堂里備忘錄是什么樣子。備忘錄的意思是,在不破壞對象封裝型的前提下,保存對象的當前狀態,當需要時,恢復這個狀態或提取這個狀態。
備忘錄被廣泛地運用到邏輯關系里,它似乎是目前我提到的唯一跟時間有關的模式,在控制論里可以涉及到因果系統。備忘錄的應用非常廣泛,最常見的是瀏覽器或網站的后退功能,返回到上一個界面,但是在我的站里沒有用到,因為我的站是樹狀拓撲結構,每個欄目都只能唯一地從主界面進入或退回到主界面,那些網狀拓撲結構的站點,每個欄目都可以通向其他的任何欄目,很可能就需要這么一個功能,其實現方法往往是維持一個堆棧型的數組。
另外一個我想實現的東西是網站內部自動記錄,由于某些原因我暫時沒有把它加到網站里。很簡單,就是監視瀏覽者的每一步操作,在哪個欄目,停了多久,做了些什么之類,如果離開了,下次訪問網站時讓他選擇是否直接進入上次瀏覽的界面(比如他上次在看我寫的小說而沒看完)。這個可以通過sharedObject實現,差不多也就是flash內部的cookie。
備注:備忘錄的實現要求還是很高的,在提取對象狀態的時候不要破壞對象的封裝性。理想的備忘錄作為一個對象需要兩個借口,一個給管理者,它不知道備忘錄里封裝了什么狀態,只負責地把狀態移交給需要的對象,一個留給原發者,原發者只能了解并操作備忘錄里封裝的狀態,不能做別的操作。
小結:
小結:我會隨時補充這篇文章。但是我要舉的一些例子暫時已經說完了。其實設計模式遠遠不止這幾種,經典的有23種,我已知的有41種,而這里只有19種,還有命令模式,解釋器模式,迭代器模式,模板方法模式我沒寫到。我的站還用了一些我沒有提到的,自己都還沒細想過的設計模式。但是一一列舉其實是意義不大的,有興趣的同志們要做的就是接觸一下這些設計模式的細節和思想,然后統統忘掉,就和張無忌學太極拳,先學后忘是一樣的。招式要看,要學,但是要領會其中的深意,靈活地,創造地,相互結合,融會貫通地使用招式,不能拘泥于招式,更不能為了用招而用招。設計模式作為UML的重要組成部分,能夠極大程度地促進我們的開發效率,使我們養成好的書寫習慣,做出更人性,更和諧,更清晰,復用性和可控性更高的作品。
不要說你不是程序員,就可以跟著感覺走,隨心所欲,當然我們無權干涉。我也說了,as本來就是想怎么寫就怎么寫,但接不接觸,學不學習先進的思維模式,這也是你的自由。更不要說你是搞藝術的,頭腦偏重感性,不適合接觸這些,我之所以要順帶拿食堂舉例,是想說明,我要說的其實是就Christian Alexander先生的,建筑的永恒之道,無論是建筑本身,還是任何軟件工程,或者Flash 的as,甚至Flash 的矢量繪圖,美工,平面設計,一直到世界這一整體,無不是遵循一個結構化的,由模式構成的永恒之道,有著驚人的高度相通和相似性。參悟了這一點,不僅對做Flash,對整個生活都會有新的看法。區別僅僅在于,從哪里開始入手思考,在這里,可以從Flash開始。
設計模式是美國一位建筑大師(同時也是信息工程師,畫家,機械工程師…的)克里斯蒂安.亞歷山大首先提出來的,很快被軟件界的技術員們所接受推廣,成為軟件工程里至高無上的法則之一(有興趣的人可以找他的《建筑的永恒之道》一書看看,相信會受益非淺)。簡單地說就是在面對對象的基礎上,包括面對對象,把要設計的整體的各個部分模式化,層次化,細粒度化,高度復用化,可控化,人性化。其中至高無上的原則是建立在需求的基礎之上,也就是說,無論做什么,人的需求要放在第一位考慮,從這個角度考慮整個系統是否足夠合理。這門學問是非常有趣的,尤其在flash中,可以應用到很多很好玩的實例中去。下面我按照一些通用的設計模式,舉例說明,有錯誤的地方,敬請高手指正:
1.抽象工廠模式(Abstract Factory):
食堂里吃的東西很多,而我只想吃一樣,那么食堂這個概念對我來說就是個抽象工廠,每個窗口可以看成它的一個具體實現,我要做的就是,去食堂,找到那個窗口,從窗口里買我要吃的東西。
舉例:flash前臺與asp后臺的交互,訪問某個動態頁面,從數據庫里取出需要的數據,通常的做法是在后臺就把數據集解析成xml字符串,再送給swf。每個業務邏輯模塊,所取出的數據結構,也就是xml的結構是不一樣的,我們要針對各個具體的業務邏輯,對相應的xml字符串解析,轉換成可供顯示的數組。也要把flash里文本輸入的內容轉換成 xml字符串,提交給后臺也面
AbstractFactory.as
//抽象工廠的接口
Interface AbstractFactory{
//生成xml解析工廠的具體實現
function createXmlParseFactory();
}
XMLParserGetFactory.as
//生成解析讀入的xml的對象的工廠
class XMLParserGetFactory implements AbstractFactory.{
var xmlParser;
function XMLParserGetFactory(str:String){
//生成解析器的具體實現,在后面會提到
}
function createXmlParser(){
return xmlParser;
}
}
XMLParserPostFactory.as
//生成解析輸出的xml的對象的工廠
class XMLParserPostFactory implements AbstractFactory.{
var xmlParser;
function XMLParserPostFactory(str:String){
//生成解析器的具體實現
}
function createXmlParser(){
return xmlParser;
}
}
這樣,我們讀入某個xml字符串時,在onLoad里面加入
//生成對留言板的留言列表解析的工廠
var xmlParser=new XMLParserGetFactory(“xmlParseGuestbookList”)
xmlParser= XMLParserGetFactory. createXmlParser()
備注:抽象工廠模式是軟件工程里最常用的設計模式之一,實現過程在于,需要某個類的實例時,通過某個工廠創建,而不是直接創建,坦白地說,它加大了開發工作量,但是對程序的層次性變得分明和降低耦合度有極大幫助。
2.生成器模式(builder):
還是那個說法,我要吃東西就去相應的食堂窗口,但我不能吃食堂窗口,窗口里的東西也許不少,我要跟師傅說,要這個,這個,還有這個。
舉例:我已經建立了 xml解析器的工廠,現在要返回解析器本身,就讓工廠創建,返回給我。
XMLParserGetFactory.as
//生成解析讀入的xml的對象的工廠
class XMLParserGetFactory implements AbstractFactory.{
var xmlParser;
function XMLParserGetFactory(str:String){
//如果要求留言板列表解析器,就生成一個
if(str==” xmlParseGuestbookList”){
xmlParser=new xmlParserGuestbookList();
}
}
function createXmlParser(){
//返回所要求的解析器
return xmlParser;
}
}
AbstractXmlParser.as
//抽象xml解析器
Interface AbstractXmlParser{
function ParseXml();
}
xmlParserGuestBookList.as
//留言板列表解析器
Class xmlParserGuestBookList implements AbstractXmlParser{
//把xml字符串里的內容解析到一堆數組里
function ParseXml(xml:XML,arrayID:Array,arrayTitle:Array){
//具體循環操作
}
}
使用的時候:
var xmlParser=new XMLParserGetFactory(“xmlParseGuestbookList”)
xmlParser= XMLParserGetFactory. createXmlParser(xml,arrayID,arrayTitle);
3.工廠方法模式(Factory Method)
我到了食堂窗口,如果師傅跟那兒抽煙,我還是吃不著東西。我說:師傅,打飯!師傅才會完成打飯這一動作。這是工廠方法模式,抽象工廠的實現通常用工廠方法模式來完成。
舉例:還是上一條,我本來想用一句話帶一個參數就實現具體xml解析器的實現,無奈構造函數沒有返回值,所以必須用
xmlParser= XMLParserGetFactory. createXmlParser(xml,arrayID,arrayTitle);
實現。
備注:抽象工廠模式,生成器模式和工廠方法模式需要靈活應用。
4.單件模式(singleton)
我前面一個人買了一條巨大的雞腿,我說我也要一條,師傅說,就這一條
舉例:單件模式的應用是相當廣泛的,它確保每個實例在全局范圍內只被創建一次,我們flash里的mc大多數是單件。內核里的核心組件也只是單件,比如我的消息映射列表(見后)。
按照單件模式的嚴格定義,應該讓類負責保存它的唯一實例。但是我在Flash里還想不到怎么實現這一點,或者實現它的意義所在,但另外一點我們可以做到,就是在全局范圍內只提供該對象的唯一訪問點。這可以由層次關系做到,把對該對象的訪問具體實現全部封裝在下層,只給上層提供唯一的訪問點(原因是,上層不知道這個單件的具體信息,比如路徑)。
看我內核文件的一部分:
Core.as
//內核
class Core {
var strucGlobalParam:ConfigVariables;
//站點信息
var xmlConfig:XML;
//站點信息的xml化對象
var ArrayStructureInitial:Array;
//用來提供給loadObject對象的數組
var ArrayForBtn:Array;
//用來初始化導航條組件的數組
var objInitial:loadObject;
//讀取影片的對象
var objMessageMap:MessageMap;
//消息映射組件
……
}
這是我的內核類也就是全站最核心類的數據結構。里面的數據只有通過下層的BasicMovie,OriginalFunctionObject等類(見后)直接訪問。
備注,核心思想是,確保只有一個。
5.原型模式(protoType)
到小炒窗口,看前面的哥們炒的青椒炒肉不錯的樣子?!皫煾?,我也要這樣的?!?br />
舉例:這對flash的用戶來說再熟悉不過了,我們經常用duplicateMovieClip()和
attachMovie()這兩個函數。按照一個原型復制相應的實例,各自執行自己的動作。在我的blog列表,導航條的生成。。幾乎用得到多項數據的地方就要用原型模式。
6.責任鏈模式,7.中介者模式,8.觀察者模式:
食堂里廚房最遠的窗口沒熬白菜了,要告訴廚房,快送過來。
責任鏈模式:一個窗口一個窗口地傳話,一直傳到食堂,食堂一看不妙,趕快做好送過去。
中介者模式:專門派一個人負責傳話,任何窗口沒菜了,就要這個人趕快去廚房催。
觀察者模式:廚房那邊派一個盯著,看哪個窗口沒菜了就開始大聲嚷嚷。
舉例:之所以要把這三個設計模式放在一塊兒,是因為我在我的站里面結合這三者建立了一個好玩的東西,可以說是我的網站的核心所在。它解決了我的flash里面各個mc的通信問題。
比如,影片A放完了,要通知影片B開始播放,直接的做法是在A的最后一幀,寫從A到B的相對路徑或B的絕對路徑,讓B play()。這樣做A和B的耦合性是相當高的,也就是說,相互依賴程度太高。運用設計模式的解決方案如下:
MessageMap.as
//消息映射類
class MessageMap extends Object {
var Message:String;
var MessageWatcher:Function;
var Target;
var MessageList:Array;
var Num_Msg:Number;
function MessageMap() {
Num_Msg = 0;
MessageList = new Array();
Message = "HANG_UP";
MessageWatcher = function (prop, oldVar, newVar, Param) {
for (var i = 0; i
MessageList[i][1].apply(MessageList[i][3], MessageList[i][2]);
if (!MessageList[i][4]) {
MessageList.splice(i, 1);
Num_Msg--;
i-=1;
}
}
}
};
this.watch("Message", MessageWatcher, "test");
}
function SendMessage(Msg:String, mc:MovieClip) {
Message = Msg;
}
function UpdateMessageMap(Msg:String, objFunction:Function, ArrayParam:Array, objRefer,IsMultiUsed:Boolean) {
MessageList[Num_Msg] = new Array();
MessageList[Num_Msg][0] = new String();
MessageList[Num_Msg][0] = Msg;
MessageList[Num_Msg][1] = new Function();
MessageList[Num_Msg][1] = objFunction;
MessageList[Num_Msg][2] = new Array();
MessageList[Num_Msg][2] = ArrayParam;
MessageList[Num_Msg][3] = objRefer;
MessageList[Num_Msg][4] = IsMultiUsed;
Num_Msg++;
}
function DeleteMessageMap(objRefer) {
for (var i = 0; i
MessageList.splice(i, 1);
Num_Msg--;
}
}
}
}
class SubTemplateMovie extends BaseMovie {
var MovieRemoveFunction:Function;
function SubTemplateMovie() {
this.stop();
MovieStartFunction = function () {
Lock();
this.play();
};
MovieEndFunction = function () {
Lock();
this.play();
};
MovieRemoveFunction = function () {
this.stop();
SendMsg("SUB_TEMPLATE_REMOVED", this);
_parent.unloadMovie();
};
MovieMainFunction = function () {
stop();
SendMsg("SUB_TEMPLATE_OPEN", this);
};
UpdateMessage("LOADING_BAR_OVER", MovieStartFunction, null, this, false);
UpdateMessage("BACK_TO_INDEX", MovieEndFunction, null, this, false);
}
}
大概機制就是,影片提前提交一個數據結構,聲明,如果有影片提交這條消息,就執行這條函數。原理在于,發送消息,實際上是把消息映射的一個變量賦值,由于消息映射繼承自object類,可以用watch方法對該變量進行監視,一旦改變,在已經提交上來的消息映射列表里檢查,如果有,執行對應函數。實際上這也造成了一定程度的耦合性,但是我們已經成功地把耦合性控制在了下級類,上級子類完全不用理會這一套消息機制的實現過程。
這個機制可以讓我們對oop的真正目的有更深的看法。舉例說明,影片A播放完了,就聲明自己播放完了,至于我播完了你要干什么,不是我的事,我不控制你。所謂的降低耦合度是個相對概念,別忘了在計算機最底層,耦合度還是一樣,cpu總是不斷的直接或間接尋址,但我們需要做的是,改變系統的拓撲結構,把耦合度控制在某一個范圍之內。
整個消息映射類相當于一個中介者,內部生成一個觀察器,一旦觸發消息,以責任鏈的方式執行。
9.橋接模式(Bridge)
菜太淡,不合有些人的胃口,所以要求食堂的師傅,專門開一個窗口,專門在做好的菜里多加些辣椒。
我在自己的站里運用了橋接模式:所有的影片都繼承自我定義的BasicMovie 類(BasicMovie繼承自MovieClip類),但是在四個下級欄目的影片里,需要定義相同的方法和事件來響應消息,BasicMovie沒有這些函數,不符合要求,這時候,在四個影片里都寫一遍是愚蠢的,我又寫了一個SubTemplateMovie類繼承自BaseMovie,里面加進一些通用的方法,然后四個下級模板影片都繼承它,這樣大大簡化了后期開發。
BasicMovie.as
//基類影片
/所有影片的原始類,一切影片的父類都繼承此類而來
class BaseMovie extends MovieClip {
var isLocked:Boolean;
//初始類開始影片函數
var MovieStartFunction:Function;
//初始類影片主功能函數
var MovieMainFunction:Function;
//初始類結束影片函數
var MovieEndFunction:Function;
var GlobalParam
//初始類構造函數
function BaseMovie() {
}
//
//發送消息
function SendMsg(Msg:String, Mc:MovieClip) {
_root.objCore.objMessageMap.SendMessage(Msg, Mc);
}
//添加消息映射
function UpdateMessage(Msg:String, MsgMapFunction:Function, ArrayParam, obj, IsMultiUsed) {
_root.objCore.objMessageMap.UpdateMessageMap(Msg, MsgMapFunction, ArrayParam, obj, IsMultiUsed);
}
//刪除消息映射
function DeleteMessage(obj) {
_root.objCore.objMessageMap.DeleteMessageMap(obj);
}
function GetGlobalParam() {
GlobalParam=_root.objCore.strucGlobalParam;
}
}
SubTemplateMovie.as
//下級模板影片類
class SubTemplateMovie extends BaseMovie {
var MovieRemoveFunction:Function;
function SubTemplateMovie() {
this.stop();
MovieStartFunction = function () {
Lock();
this.play();
};
MovieEndFunction = function () {
Lock();
this.play();
};
MovieRemoveFunction = function () {
this.stop();
SendMsg("SUB_TEMPLATE_REMOVED", this);
_parent.unloadMovie();
};
MovieMainFunction = function () {
stop();
SendMsg("SUB_TEMPLATE_OPEN", this);
};
UpdateMessage("LOADING_BAR_OVER", MovieStartFunction, null, this, false);
UpdateMessage("BACK_TO_INDEX", MovieEndFunction, null, this, false);
}
}
注(關于消息映射機制看 責任鏈模式)
10.適配器模式(Adapter)
我要一碗湯,但是只有紙飯盒,還沒勺,所以食堂的師傅給了我一次性的湯碗和勺,這叫適配器。
適配器解決的是某一個類的對外接口不合用的問題,可能是參數或者返回值類型不符等問題造成的,這時候我們需要在工作對象和這個類之間加一層間接的層次。
這個模式我在底層的數據交換層用過。我說過,flash和asp.net之間交換數據全以xml為載體。返回xml在底層只有三層,數據庫操作,數據操作,數據顯示,由數據操作層返回給數據顯示層一個xml字符串就可以了。然后我就遇到一個小問題,在另一方面,我需要提交數據到數據庫,也是提交一個xml字符串,但是我需要數據庫里對應的表的數據集的xml表現形式的xsd驗證?。ㄒ豢跉庹f完,差點沒憋死)。就是說我至少需要取出這個表里的一條記錄,問題在于,我封裝的類從來只返回xml,沒有返回xsd的。解決辦法就是適配器,新建一個項目,加了一層專用于獲得xml驗證格式,這樣就完成了不同接口之間的轉換。
備注:適配器和橋接很象,都是在已有類不符合要求的時候,加入一層間接的元素以達到目的。不同的是適配器是解決不兼容接口之間的轉換,橋接一般不涉及這個問題,只是完成一個一對多的轉換。
11.外觀模式(Facade)
每天都要去食堂,每個人去不同的窗口吃不同的菜,很累,今天全寢室推舉猴子去打飯:
你吃這個,三兩飯,我吃那個,五兩飯,所有人都只跟猴子一個人交涉,食堂所有的師傅也只見猴子一個人。
舉例:這個模式在程序的上下層的通信之間可以應用得十分廣泛。Asp的每個模塊要去不同的數據,訪問數據庫的不同表,就要跟不同的下層數據訪問組件打交道。就是說,每個mc模塊必須知道,我要去哪個具體的數據訪問組件取數據。每個模塊要維持自己的一個,至少是字符串。
如果運用外觀模式。我們可以讓所有的需要數據交互的mc訪問同一個aspx頁面,比如getStrXml.aspx。只要傳送一個標示符,就可以通知這個唯一的取數據的葉面,訪問哪個下層組件獲取數據。下層組件不知道哪個mc要求數據,mc也不知道數據的具體來源,這樣,上下層之間互相都顯得不透明。這就降低了耦合度。
12.代理模式(Proxy)
可能我們不是每個人每天都想吃飯,所以我們要求猴子每天中午必須在寢室,如果我們要吃,他就去,如果我們都不吃,他愛干嘛干嘛。
舉例:這恐怕是每個人在flash里都會無意中用到的模式。比如,一個網站,它的下級欄目不用在整個網站初始化的時候一開始就讀進來,但是我們要確保,在瀏覽者想看并且點擊導航條上的某個按鈕時,能夠正確地讀進相應的影片文件,前提是,我們必須在內部保留一個索引,可以稱作代理。通常是一個空mc
13.策略模式:(strategy)
我每天先在食堂找座位,再打飯,再打菜,再買杯酸奶。這已經模式化。要是食堂有服務員,我也會要他這么做。
舉例,策略模式是把一系列的算法封裝起來,形成一個類。這個模式幾乎是隨時隨地都可以整合到別的模式里去的,我的那一堆xml解析器實際上就是策略模式的應用,這個模式還應用到我網站的下層,因為flash提交給aspx頁面的數據也是xml字符串,下層模塊也需要相應的解析算法。同樣的,我把對xml的解析封裝進了一個類。
//Cs文件里的解析函數
Class DataModel.BlogMsgs{
…
Public DataSet parseXML(string strXml){
DataSet ds=new DataSet();
//。。把xml裝載到DataSet 里
Return ds
}
…
}
14.享元模式(Flyweight)
東西不夠吃?給你擺20面鏡子~
師傅,東西還是只有一份。。。
關于這個模式十分抱歉,我暫時還沒想到在flash顯示層里面的實現。需要舉例說明的是,瀏覽器的機制是,在有大量文字的英文文檔里,相同的字母共享一個Flyweight,在內存里其實只占一份空間,然后在文檔不同的地方顯示,這樣對于大量細粒度的效果來說,可以節省很多資源。在下層,我們可以做到的就是,對某個全局對象只在某處真正保存,任何對象想要這個全局對象,只引用它而不復制它。
在顯示層里有哪位想到了該怎么做請一定賜教。
15.訪問者模式(Visitor)
只要愿意,我隨時都可以跑到哪個窗口打要吃的東西,前提是,我必須跑這一趟。
舉例:我說過,我的所有mc都繼承自BasicMovie這個類,但不是我的所有mc都要從后來獲取數據庫數據。獲取數據庫數據所要訪問的信息,比如ip,路徑,文件保存在配置文件里,初始化的時候讀入內核,并且只有內核那里有一份。在BasicMovie里加入對這些全局變量的引用是不合適的,因為只有少數mc要用到,而且由于某些原因我無法再使用橋接模式(我已經有了SubTemplateMovie,不能多繼承),所以我用了訪問者模式。
BasicMovie.as
//獲取全局變量
function GetGlobalParam() {
GlobalParam=_root.objCore.strucGlobalParam;
}
如果上級mc不執行這個函數,是不能獲取全局變量的,如果要用,就執行。
也就是說,需要的時候,我去訪問它。
備注:聲明一個visit操作,使得訪問者可以正確訪問需要的類。
16.狀態模式(state)
我今天想吃面,師傅問我:要什么料?西紅柿雞蛋,排骨還是牛肉?
舉例:狀態模式是指將對象當前的某些狀態局部化,當對象改變狀態時,看起來好像改變了類。例子還是我的滾動條。如果要滾動的是文本框,就要引用一個TextField的Scroll,maxscroll屬性,如果是mc,引用的是_y,_height屬性,我用一個參數將二者區分,由一個if語句控制,讓滾動條可以自由區別狀態。
另外一個解決方案是定義ScrollBar的不同子類,這兩者本質區別不大,在狀態比較多時,可能要維持一個龐大的if算法,這樣就用生成子類的方法比較好。
ScrollBar.as
//滾動條組件
function BindTo(mc,type:String,intMcHeight:Number,yinitial:Number){
ScrollType=type;
if(type=="TXT"){
scrollTxt=mc;
}
if(type=="MC"){
initialY=yinitial;
McHeight=intMcHeight;
scrollMc=mc;
}
}
function Scroll() {
if(ScrollType=="TXT")
this.onEnterFrame = function() {
scrollTxt.scroll = scrollTxt.maxscroll*mcBlock._y/(BgLength-BlockLength*3/2)
};
if(ScrollType=="MC"){
this.onEnterFrame=function(){
if(scrollMc._height>McHeight){
scrollMc._y=initialY-(scrollMc._height-McHeight)*mcBlock._y/(BgLength-BlockLength*3/2)}
}
}
}
備注:這也是常見模式,在flash的邏輯控制里尤其隨處可見
17.裝飾模式(Decorator)
在食堂吃飯,沒筷子怎么行?我是從來不帶飯盆的。師傅很人性化,每個窗口都放著一大把筷子,隨用隨拿。
這個模式如果用好,有的地方可以很省力。比如,我網站里的滾動條:
ScrollBar.as
//滾動條組件
class ScrollBar extends BaseMovie {
var BgLength:Number;
var BlockLength:Number;
var mcBlock:MovieClip
var Width:Number;
var ScrollType;
var scrollTxt:TextField;
var scrollMc:MovieClip;
var McHeight:Number
var initialY:Number
function ScrollBar() {
}
function InitialScrollBar(BgLength, BlockLength) {
this.BlockLength = BlockLength;
this.BgLength = BgLength;
}
function BindTo(mc,type:String,intMcHeight:Number,yinitial:Number){
ScrollType=type;
if(type=="TXT"){
scrollTxt=mc;
}
if(type=="MC"){
initialY=yinitial;
McHeight=intMcHeight;
scrollMc=mc;
}
}
function Scroll() {
if(ScrollType=="TXT")
this.onEnterFrame = function() {
scrollTxt.scroll = scrollTxt.maxscroll*mcBlock._y/(BgLength-BlockLength*3/2)
};
if(ScrollType=="MC"){
this.onEnterFrame=function(){
if(scrollMc._height>McHeight){
scrollMc._y=initialY-(scrollMc._height-McHeight)*mcBlock._y/(BgLength-BlockLength*3/2)}
}
}
}
function ScrollMc() {
}
function StopScroll() {
this.onEnterFrame=null;
}
function Reset(){
mcBlock._y=0;
}
}
核心函數是BindTo(),把這個滾動條的實例綁定到某個動態文本框或者某個mc上,就可以實現滾動。
備注:裝飾模式的思想是,在不影響其他對象的情況下,以動態,透明的方式給單個對象添加職責。
18.組合模式(composite)
我中午吃六兩飯,豬肉燉粉條,辣子雞,魚丸,咸鴨蛋,外加兩杯酸奶(豬?。?br />這些東西都是對象,他們共同組成了我的午飯。
舉例:應該說在Flash里組合模式是無處不在的,因為只要還有mc的嵌套,就有組合模式存在。幾個mc裝在一個mc里,這個裝載用的mc稱作容器。
但是就這么說,恐怕沒人會重視這個模式,因為不管理不理解他我們都在用。他的確有很多種實現方式,我的方式之一是這樣的。
//blog.as
我的Blog
class Blog extends BaseMovie {
//blog第一界面,包括日記列表,日歷,最近留言
var mcBlogList: mcBlogList;
//blog第二界面,包括日記全文,回復,對該日記的留言。
var mcBlogDairy:MovieClip;
var currentState:String;
var currentDairyID:Number;
function blog(){
}
}
mcBlogList.as
//blog第一界面
class mcBlogList extends BaseMovie {
//最近留言
var recentMsgs:blogMsgsSC;
//日記列表
var blogList:BlogList;
//日歷
var calendar:CalenderForBlog;
}
mcblogDairy.as
//blog第二界面
class mcBlogDairy extends BaseMovie {
//日記全文顯示
var BlogDairy:BlogDairy;
//留言板
var GuestBook:BlogInputMsg;
//留言列表顯示
var BlogMsgs:BlogMsgs;
}
然后里面每個組件都還包含另外的組件,比如滾動條,動態文本框什么的,我想說的是,我寫在as里的mc嵌套模式并不一定就符合fla文件里的具體物理嵌套模式,有很多裝飾性的mc不需要包含進來,也有很多具體原因,需要改寫路徑。比如在BlogDairy下,滾動條的實際路徑是BlogDairy.mc1.ScrollBar,不做成BlogDairy.ScrollBar可能是因為我需要mc1的動畫,也可能是別的具體原因。但是我們可以在mc1的時間軸上加一句
_parent. ScrollBar =This.ScrollBar
把路徑指向改寫,達到我們要使用的模式,ScrollBar就是BlogDairy的直接成員。另外我沒有語句初始化mc,我習慣在庫里設置mc的linkage。
備注:雖然組合模式是每個人必不可少要用的,但正因為如此,我們可以想出他更多更好更靈活的實現方式
19.備忘錄模式(memento)
~師傅,晚上的雞腿沒中午的新鮮啊。
~胡說!這就是中午的。
舉例:開個玩笑,上面兩句話不是備忘錄模式的本意,實際上我一時想不出在食堂里備忘錄是什么樣子。備忘錄的意思是,在不破壞對象封裝型的前提下,保存對象的當前狀態,當需要時,恢復這個狀態或提取這個狀態。
備忘錄被廣泛地運用到邏輯關系里,它似乎是目前我提到的唯一跟時間有關的模式,在控制論里可以涉及到因果系統。備忘錄的應用非常廣泛,最常見的是瀏覽器或網站的后退功能,返回到上一個界面,但是在我的站里沒有用到,因為我的站是樹狀拓撲結構,每個欄目都只能唯一地從主界面進入或退回到主界面,那些網狀拓撲結構的站點,每個欄目都可以通向其他的任何欄目,很可能就需要這么一個功能,其實現方法往往是維持一個堆棧型的數組。
另外一個我想實現的東西是網站內部自動記錄,由于某些原因我暫時沒有把它加到網站里。很簡單,就是監視瀏覽者的每一步操作,在哪個欄目,停了多久,做了些什么之類,如果離開了,下次訪問網站時讓他選擇是否直接進入上次瀏覽的界面(比如他上次在看我寫的小說而沒看完)。這個可以通過sharedObject實現,差不多也就是flash內部的cookie。
備注:備忘錄的實現要求還是很高的,在提取對象狀態的時候不要破壞對象的封裝性。理想的備忘錄作為一個對象需要兩個借口,一個給管理者,它不知道備忘錄里封裝了什么狀態,只負責地把狀態移交給需要的對象,一個留給原發者,原發者只能了解并操作備忘錄里封裝的狀態,不能做別的操作。
小結:
小結:我會隨時補充這篇文章。但是我要舉的一些例子暫時已經說完了。其實設計模式遠遠不止這幾種,經典的有23種,我已知的有41種,而這里只有19種,還有命令模式,解釋器模式,迭代器模式,模板方法模式我沒寫到。我的站還用了一些我沒有提到的,自己都還沒細想過的設計模式。但是一一列舉其實是意義不大的,有興趣的同志們要做的就是接觸一下這些設計模式的細節和思想,然后統統忘掉,就和張無忌學太極拳,先學后忘是一樣的。招式要看,要學,但是要領會其中的深意,靈活地,創造地,相互結合,融會貫通地使用招式,不能拘泥于招式,更不能為了用招而用招。設計模式作為UML的重要組成部分,能夠極大程度地促進我們的開發效率,使我們養成好的書寫習慣,做出更人性,更和諧,更清晰,復用性和可控性更高的作品。
不要說你不是程序員,就可以跟著感覺走,隨心所欲,當然我們無權干涉。我也說了,as本來就是想怎么寫就怎么寫,但接不接觸,學不學習先進的思維模式,這也是你的自由。更不要說你是搞藝術的,頭腦偏重感性,不適合接觸這些,我之所以要順帶拿食堂舉例,是想說明,我要說的其實是就Christian Alexander先生的,建筑的永恒之道,無論是建筑本身,還是任何軟件工程,或者Flash 的as,甚至Flash 的矢量繪圖,美工,平面設計,一直到世界這一整體,無不是遵循一個結構化的,由模式構成的永恒之道,有著驚人的高度相通和相似性。參悟了這一點,不僅對做Flash,對整個生活都會有新的看法。區別僅僅在于,從哪里開始入手思考,在這里,可以從Flash開始。