作者:Wsir 来源:闪吧
有人问我flash的as应该怎么写,我可以很负责任地告诉他,想怎么写就怎么写,因为as以及flash内部的构成模式决定了它的高度自由化。理论上来说,用按钮的on事件,加上stop(),play(),gotoAndStop(),gotoAndPlay(),就可以实现一个flash里大部分的逻辑关系,而且源代码简单易懂。但是大多数人不会这么做,是因为这种方法实在太让人敬佩。稍有常识的程序员都会知道面对对象与面对过程的区别。Flash的编程虽然只是以脚本的形式出现,并且还很不完善,比如,没有多继承,但已经初步体现了oop的思想。这篇文章现在总结一下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)
到小炒窗口,看前面的哥们炒的青椒炒肉不错的样子。“师傅,我也要这样的。”
举例:这对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<Num_Msg+1; i++) {
if (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; i<Num_Msg; i++) {
if (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验证!(一口气说完,差点没憋死)。就是说我至少需要取出这个表里的一条记录,问题在于,我封装的类从来只返回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)
我中午吃六两饭,猪肉炖粉条,辣子鸡,鱼丸,咸鸭蛋,外加两杯酸奶(猪!)
这些东西都是对象,他们共同组成了我的午饭。
举例:应该说在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的as应该怎么写,我可以很负责任地告诉他,想怎么写就怎么写,因为as以及flash内部的构成模式决定了它的高度自由化。理论上来说,用按钮的on事件,加上stop(),play(),gotoAndStop(),gotoAndPlay(),就可以实现一个flash里大部分的逻辑关系,而且源代码简单易懂。但是大多数人不会这么做,是因为这种方法实在太让人敬佩。稍有常识的程序员都会知道面对对象与面对过程的区别。Flash的编程虽然只是以脚本的形式出现,并且还很不完善,比如,没有多继承,但已经初步体现了oop的思想。这篇文章现在总结一下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)
到小炒窗口,看前面的哥们炒的青椒炒肉不错的样子。“师傅,我也要这样的。”
举例:这对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<Num_Msg+1; i++) {
if (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; i<Num_Msg; i++) {
if (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验证!(一口气说完,差点没憋死)。就是说我至少需要取出这个表里的一条记录,问题在于,我封装的类从来只返回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)
我中午吃六两饭,猪肉炖粉条,辣子鸡,鱼丸,咸鸭蛋,外加两杯酸奶(猪!)
这些东西都是对象,他们共同组成了我的午饭。
举例:应该说在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开始。
回复Comments
作者:
{commentrecontent}