as3设计模式乱用之工厂模式
好久没写技术相关的日记了,一忙,二懒,三则被这单调的生活熏得没什么感悟。
其实这篇日记早就想写了,项目开发初期的时候,带学生。经常看到那种乱用设计模式的现象。一方面,公司面试人的时候喜欢问设计模式,另一方向设计模式相关的书夸大了设计模式。紧接着的就是一群刚入行的人盲目的崇拜和乱用,感觉用了设计模式就牛逼,用了就脱离菜鸟群体。
好吧,今天蛋痛。先声明,这篇文字纯个人感觉写,我不是博士也不是研究生,不保证观点全正确。
我用一个简单的程序员说法,工厂模式是一个类里面有个工厂,工厂通过switch把字符串转成新建的子对象。
as代码大概如下:
class factory { public function createItem(itemName:String):ItemBase { switch(itemName) { case... } }
详细的那几个类的网上随便搜,我就不粘贴了。
我想说的是这个模式给我们开发带来的好处有哪些。读过两本关卡设计模式的书,不知是巧合还是不负责的抄袭。两本书为说明工厂的好处都举了个打印机的例子。
按书上说,用工厂的好处是隐藏产品类。如果我需要创建一台打印机,我只需要创建工厂就行。其实这个说法非常别扭,换个说法我只需要打印,我干麻要非要多余找个工厂。尽信书不如无书啊,这摆明就是google翻译忽悠买书人啊。
o'reilly的书(as设计模式)还举了个飞机和子弹的例子。当年刚开始看的时候,是和两个刚出社会的同事一起的。有人表示,很灵活啊,我要创建子弹就从工厂里面创建。其实我那时就很纳闷,我直接new一个子弹就得了。搞个子弹基类,还得建立工厂,建好了还要从工厂里面通过字符串创建。码量多了好多,其结果就是出个子弹而已。这个飞机子弹的例子,我们加个另一个前提就更容易理解了,因为前后端交互,服务端给客户端的是字符串或者是整型,客户端要通过这个字符串转换成子弹显示在场景。如此就不得不用上工厂模式。不过话说回来,既然是不得不用,那书就算不看我们也一样是这样写代码,而看了书的人大多只会用switch。不看的还会用反射和映射。
谈到了这里我想起不只一次听过的话,"用模式,性能会下降些"。其实这句话是有问题的,很多情况下我们是不得不用模式。就如上面说的服务端字段转子弹的例子。所以跟本不存在用会下降不用提升的说法。硬要说会造成性能浪费的话,只能说是乱用,见下例。
这是个奇怪的例子,这串代码是要创建一个按钮显示到界面上。我读旧项目经常碰到这种写法,毕竟一个项目经过的人手多,什么风格都有。
var btn:RedButton = BtnFactory.createBtn(BtnName.RED) as RedButton;
开发者本来只需要一个红色的按钮,但是却弄了个工厂,在工厂里面通过switch来创建。这样做有什么好处?干麻不直接btn = new RedButton();我只能说,死读书害死人。上例这种写法我们也不能说他有错,但是不得不说,浪费这么多代码没换来什么好处。
如果说我们希望一个xml来配置显示产生不同的按钮,那工厂写法是有用的,因为配置文件全是字符串,我们需要字符串来创建不同的按钮。但是自己写的一个项目,并无任何配置的东西,这样硬生生加个工厂不就变成了浪费体力了吗。
搜了很多国外的资料,老外关于as3用是否合适用设计模式进行了不少口水战。而到了国内,就变成了尽信模式,省下了口水。首先我先表明一下自己的关点,本人认为如果追求快速开发,并且对代码严谨性没有强迫证的话,as3的很多设计模式都可以用反射替代。
说起反射这个词,是从java那学来的,详细的作用是通过类的字符串创建实例。在as里面实现比java简单得多。例如:
public function getBut(btnName:String="Button"):Button { var cls:Class = applicationDomain.getDefinition("Button") as Class; return new cls(); }
这个写法也很简单和工厂一样,通过一个字符串来创建子项产品。但是为什么书上不说用反射工厂,却要搞好几个类来连接呢?其实这也是有原因的,因为早年,java刚开始的那几个版本是没有反射这个东西的。别说早年的java,现在的C要实现反射也是件麻烦的事。
举个常用的例子,例如我们要写个列表组件List。列表通过循环创建有子项,每个子项从上到下按Y抽进行排列。我写好了列表之后,给其他人用,别人当然就不希望再重新写一次子项创建和Y排列啦,但是他们要显示的子项是不一样的。相信开发过一个项目的人都知道,在列表里面用一个Class类型的变量存子项类型,循环创建子项可通过这个变量来创建子项。
大致代码如下:
var ls:List = new List(); ls.itemClass = OtherItem; ls.data = [11,22,33];
在这里我提个假设,如果不用Class变量来反射的话,(也就是不准用Class类型的变量)。别人要用你写的List类,但又要产生不同的子项,要怎么办?
这种情况下我们可以写一个protected方法来创建子项,别人用的时候就新建一个List2和一个自己的MyItem,继承于List,然后覆盖掉那个protected方法来返回新子项MyItem。这个方法比较简单就不上码了,至于叫什么模式我不记得了,毕竟不是搞学术,唉。
还有另一种方法就是搞模式的人最喜欢用的,定一个工厂方法接口IItemFactory,这个接口有一个创建子项的方法,List里定一个接口变量。别人扩展的时候新建一个子项工厂接入那个IItemFactory,然后实现那个方法返回一个新的MyItem。(写完这段,发现自己表述水平真有限,算了,上码吧)。
//IItemFactory代码 class IItemFactory { function createItem():ItemBase; } //ItemFactory代码: class ItemFactory implements IItemFactory { public function createItem():ItemBase { return new MyItem(); } } //List部份代码: class List { ... public var itemFactory:IItemFactory; for (var i:int = 0; i < data.length; i++) { var itm:ItemBase = itemFactory.createItem(); ... addChild(itm); } } //使用代码 var ls:List = new List(); var itmf:IItemFactory = new ItemFactory(); ls.itemFactory = itmf; ls.data = [11,22,33]; ...
怎么样,很复杂吧。这就是命贱不用反射,硬生生要用工厂的后果。呵呵~~
这两个例子算是把反射替代工厂好处说完了吧。下面想说一下严谨性这个东西。虽然我目前是搞as3的,但说句实话,as3的语法确实不如java严谨,java有一个叫做泛型的东西,泛型用得好的话,项目在编译之前就几乎把bug找完了。as3的Vector硬搬了泛型这个概念,相比全抄这概念的C#算是弱暴了。
举个例子,List中的itemClass属性能赋值任何类型,如果赋值对象不是显示对象的话,我们只有在项目运行的时候,调整这个List才能知道这个赋值有问题。java的泛型解决了这点,直接来个Class<DisplayObject>这样的变量,如果赋值的不是显示对象,那在项目编译之前就会报错了。
as3没有严格的泛型,所以有严谨性强迫证的同学,就宁愿弃反射而用工厂。因为工厂方法也是可以在编译之前报错的。详细的我就说不了太多,自己也不知道自己是否研究的到可以吹的程度。
C语言是没有反射,java是早期的版本没有反射(本人研究不算深,纯属听说)。Adobe在设计as的时候很多高级语言里复杂的东西都被简化了,我们干麻还非要走回高级语言那个套路呢。
as3的Object已经包含有了HashMap的功能,但有些人还是硬生生的又搞一个HashMap类。里面又是一堆看着蛋疼的代码。算了,扯远了,就写这么多。下次有空再扯一下设计模式之接口乱用。以博主懒散的更新的速度,不知道是哪个朝代咯。