ActionScript 3的引入激起了人们对于架构及设计模式的兴趣。从第1章我们可以知道,设计模式基本上就是一种用以解决开发问题的蓝图或模板。它们可为程序开发提供可重用架构。在程序设计业内的某些领域,设计模式是开发中必不可少的部分。但由于悖离了ActionScript语言的固有特性,导致用AS3实现的设计模式往往会使开发受阻。其中一个原因就在于AS3语言已被设计为以一种特定机制来运作,具体来说就是事件机制。在本章中,我们将要探讨面向对象编程(OOP)的一些基本原则,我们一定要在开发中牢记这些原则。另外还将介绍一些编程风格与有效的设计模式,并且还将告诉你何时应该忽略这些华丽技巧。
12.1 OOP概念
  就像我在第1章所提到的那样,面向对象编程技术是一种围绕着对象交互这种概念的软件设计模型。就游戏范畴而言,屏幕上的每个游戏角色及其周围的每个互动元素都是一个对象。它们都有要接受的指令以及彼此间要发送的消息。通过让每种对象都负责其自有行为,程序设计就会变得更加模块化且更为灵活。从理论上来说,这个概念可能并非难于掌握。但在实践中,如果我们没有一定的规划和预计,则它将很难实现。这时就需要用到设计模式了。使用一种可靠的软件设计风格会使人们规划应用程序的过程变得更简单,因为这种模板已经将各方面因素都考虑得很周详了。注意,这里我所说的是应用程序。许多公认的企业级设计模式非常适用于创建那种执行特定任务的应用程序,比如像生产力应用程序、实用工具软件以及设计软件等。然而,设计模式却并不一定能适合游戏开发的要求,因为从人们的感觉上来说,游戏更像是一种体验,而非是那种行为固定且可预期的商业软件。开发游戏引擎的最佳方案可能根本不会遵循常规的设计模式,但它却依然能够相当优秀地完成任务。然而在使用OOP时,你最好能够遵照一些基本原则,它们能使你的代码实现模块化并具有可扩展性。
12.1.1 封装
  OOP中最重要的一个概念就是封装。简单地来说,封装指的就是对象(在ActionScript中就是指类)应该具有独立性,能够自我管理。对象不必知道其功能实现环境的全部细节,它应该具备一个规定好的功能列表(或者说接口)以使其他对象能够命令它去执行某项操作。为了与外部对象实现消息传递,它所发送的消息应该要被其他对象“侦听”到。一个封装良好的对象就相当于一台苏打水自动售货机。它所有的内部工作状况你一概不知,其全部功能也只浓缩为两种:选货按钮和出货口(“哐当”一声,你买的货品就被推送出来)。你没必要知道机器的内部工作原理,有可能是几个侏儒在里面现场酿制并灌装这些苏打水,也可能里面只是一连串的软管而已。无所谓了,你所关心的只是能否从一个易于理解与使用的接口处获取美味苏打水而已。看看任何一个Flash内置类,你就会发现它们所遵循的模式与此相同。帮助文档中所列出的类信息只有公共方法、属性及事件。尽管在该层面下肯定还有更多的信息没有暴露出来,但我们不必知道它的全部细节。你在开发自己的游戏类时也应如法炮制。
12.1.2 继承
  假设我们有两个类:Chair类与Sofa类。这两个类都有一些相似点,因为它们都是坐具,拥有坐具的一般特征——重量、尺寸、腿数、可坐人数等。为了省时,我们不会在这两个类中定义出全部这些特征,而只创建一个名叫Furniture(家具)的类,然后把家具的共有特征加入到Chair与Sofa中。然后我们就可以说Chair类与Sofa类通过成为(或者说是扩展)Furniture类而继承了这些属性。这就是继承的概念。现实生活与虚拟世界中所有的对象都有着一定的层次等级,而面向对象式编程的效率能否实现最大化,其关键也就在于你能否认清对象间的关系及其共有特征。以前我们要为Chair类与Sofa类各定义出一个属性,而现在如果用了继承,你就只需简单地把该属性定义在Furniture类上即可。当扩展一个类时,扩展出的新类就变成了子类,而原始的被扩展类现在则被称为超类。在上面这个例子中,Furniture是超类,而Chair与Sofa则是它的子类。稍后我们还将介绍纯继承(即一个类只能扩展自某一个类)在实践应用上的一些不足之处。
12.1.3 多态性
  尽管这个词听上去像是某种在科幻小说中出现的灾难,然而这里谈到的多态性则与之不同,它基本上是指我们可以用代码将一个类替代为另一个类,并且通过继承得到的对象的某些行为或属性可被改变或者说重写(overridden)。ActionScript只支持一种基本类型的多态,也就是我们这里要介绍的内容。拿上面所举的继承范例中的Chair类来说吧。假如我们通过扩展Chair类得到一个小孩所用的HighChair(高脚椅)类。与正常的Chair相比,HighChair中的某些椅子属性在用法上或表现形式上都有所不同。我们可以重写那些在HighChair类中用法特殊的属性,而依然继承那些用法相同的属性。实际操作过程不会如此复杂,我会在以后用到它时加以介绍。
12.1.4 接口
  面向对象编程的一个核心原则就是要将接口与实现分离开。接口就是一种含有公共方法与属性及其数据类型的列表。而实现则是使用了接口的类,它用接口来定义可被其他类公开获取的方法及属性。起初你可能不太理解这个概念,所以我们还是通过一个例子来进行说明。请注意在该例中(以及在本书的剩余部分中)按照惯例ActionScript中的接口名称首字母是大写字母I。
  在讲解继承时所用的范例中,Chair类与Sofa类都是扩展自Furniture类。但如果你要引入另一件家具(比如说Table),你就会遇到麻烦。尽管它们全都是家具,然而用途却极为不同。Table类不需要那些能让人坐下的方法,而其他两个类也不需要能在自身之上放置菜肴的方法。固然,理论上你可以通过创建一个完整的继承结构来将Furniture类拆分为SeatingFurniture类、DisplayFurniture类以及SupportFurniture类等。但这样做实在是个笨办法。另外,对较大的继承结构所作的任何改动都势必会波及到子类,并会产生一些以前根本不存在的麻烦。而接口却能很方便地解决这个问题。
  要想支持这三个类各自的需求,你只需定义出不同接口即可。你可将接口拆分为如下类型。
    IFurniture,包含move()方法。
    ISeatedFurniture,包含sitDown()方法。
    ILayingFurniture,包含layDown()方法。
    ITableFurniture,包含setDishes()方法。
  继承只允许一个类直接继承另一个类,而单独一个类却可以使用尽可能多的接口。Chair类可以实现IFurniture与ISeatedFurniture接口。Sofa类除了可以实现以上这两个接口外,还可以包含ILayingFurniture接口,而Table类则可含有IFurniture与ITableFurniture接口。另外,由于接口还可以互相扩展,你也可以扩展第一个接口而得到后三个接口,这使得实现起来就更为简单。因为我们现在已为不同的furniture用途定义好了几种基本接口,所以接下来如果遇到某种特殊家具,你就可以按照需要来组合使用这些接口。
  不要因为这种较抽象的概念让你觉得有些难懂而忧心。在第14章我们将要建立一个完整的游戏,那时你就会在实际应用中领会到这些概念的含义了。
12.2 游戏开发中的实用OOP技巧
  通过使用事件在对象间传递消息,AS3默认能够支持OOP及良好的封装。据说AS3的事件模型类似于观察者(observer)设计模式,但不管定位如何,它都只是这门语言的固有运行方式。你一定要记住:无论其他的设计模式有多么优秀,如果偏离了这种事件模型,那么它们势必将改变这门语言的默认行为。图12-1展示出在AS3显示列表层级中对象彼此间的关系。
  在该示意图中,Object1位于层级顶部,它可以是一个根显示对象或者只是一个普通的数据事件分发器(EventDispatcher)。它有一个指向Object2的引用,且知道Object2的数据类型,故而可以直接通过公共接口传达给Object2一些指令。然而由于封装的缘故,致使Object2无法得知它的父级对象,但这并不妨碍Object2执行这些指令。为了向外传送消息,Object2会分发一些事件。如果Object1为自身注册了针对Object2所发事件的侦听器,它就能接收到这些事件。Object2与Object3之间的关系也是如此。假如所有这些对象都是显示对象,那么被Object3设定为冒泡的事件注定最后会抵达Object1处(前提是Object1注册了针对这些事件的侦听器)。你可以将这些对象看成一列同朝着一个方向站立的人群。队列后面的人能够看到位于其前面的所有人,并且能直接跟他们讲话,即使这番话要直接经过前面的人才能传递到听者处。但是其他人并不知道在他身后站的是谁(如果有人的话),以及他们是否正好在收听讲话。他们能做的只有说话(也就是分发事件),而且他们不在乎所说的话是否能被人听到。正因为你并不需要知道某个特定对象之上的层级关系,所以为层级加入新的对象就相对容易多了。
  如图12-2所示,我们把Object4加入到了显示列表的第二层。这时只需改变一点即可,即你要让Object1知道Object4的数据类型,这样它才能正确地使用Object4的公共接口。而Object4与Object2的关系也同样如此。当然,这是一个很抽象而又很简单的情况,但如果架构考虑得不周详,作出这样的改变就会给应用程序的其余部分带来灾难性后果。 因为各种游戏在机制与行为上都有很大不同,而且游戏在测试时其玩法也经常会改变,所以在创建游戏引擎时必须要保证系统的灵活性。
12.3 单例模式:一种良好的文档模式
  尽管我并不赞成用设计模式来进行游戏开发,但我的确喜欢用一种特殊模式来创建游戏的文档类。它叫作单例模式(Singleton)。从名称上你多少就能猜出其含义。采用单例模式构建的类永远只会在内存中存在单独一个实例,并且为了访问这个实例,它还提供了全局访问指针。用这种模式来创建文档类或者一个站点的顶级类时,它永远都能保证你能够轻易地访问到一些基本的核心功能。比如说,由于游戏文本需要本地化为另一种语言,所以我们通过一个外部的XML文件将所有文本加载进来。但我不想在需要时再一遍遍地加载这个XML文件,所以应该由文档类负责加载它,并使其能被显示列表中所有对象获取。单例模式能很好地实现这一点,因为它可以从任何位置创建一个全局访问指针,甚至也包括非显示对象。不过这可是把双刃剑,滥用该模式会造成存储过多数据,或者会造成你过多依赖于指向主类的引用,这些都能破坏封装。在实际应用中,你永远都不要把对单例类的引用放在你会重用的引擎组件内,因为这样做会使得引擎变得很僵化。这些引用应该预留给那些要构建具体游戏的类。接下来就让我们来看一个单例类。该类文件位于第12章源文件夹下,名为SingletonExample.as。
package {

import flash.display.MovieClip;

public class SingletonExample extends MovieClip {

static private var _instance:SingletonExample;

public function SingletonExample(se:SingletonEnforcer) {
      if (!se) throw new Error("The SingletonExample class is
a Singleton. Access it via the static getInstance method.");
    }
    static public function getInstance():SingletonExample {
      if (_instance) return _instance;
      _instance = new SingletonExample(new SingletonEnforcer());
      return _instance;
    }
  }
}
internal class SingletonEnforcer { }
  在传统上,其他语言会为单例类设置一个private构造函数,以此来防止对该类实例的调用。但在AS3中,构造函数只能是public型,所以我们不得不加入一个错误检查来强制用户正确地使用该类。该类的一个静态引用指向它唯一的实例,静态方法getInstance负责将该实例返回。为了防止人们任意将该类实例化,我们还创建了一个只能被主文档类所访问的私有类。 我们可以将这个类看作Singleton构造函数的密钥。只有getInstance方法才知道如何正确地创建一个新的SingletonExample类实例,没有这个密钥这就无法办到。这是一种很常用的在AS3中编写基本单例类的方法,但当我们把这个简单的例子用作文档类时,它同样也会失效。这是因为Flash会自动试图将该类实例化,以便能够创建显示列表层级。为了解决这个问题,我们必须修改实例化的时间并改变构造函数的运行方式,同时还要去掉那个私有类。SingletonExample- Document.as就是改变之后的新单例类。
package {

import flash.display.MovieClip;

public class SingletonExampleDocument extends MovieClip {

static private var _instance:SingletonExampleDocument;

public function SingletonExampleDocument() {
      if (_instance) throw new Error( " This class is a
Singleton. Access it via the static SingletonExampleDocument.
getInstance method. " );
      _instance = this;
      addEventListener(Event.REMOVED_FROM_STAGE, onRemove,
false, 0, true);
    }

private function onRemove(e:Event):void {
      _instance = null;
    }

static public function getInstance():SingletonExampleDocument {
      if (_instance) return _instance;
      _instance = new SingletonExampleDocument();
      return _instance;
    }
  }
}
  在这个修改过的版本中,我们利用Flash的特点用构造函数将该类实例化一次。构造函数就会在该实例创建伊始就抛出错误。为了解决该文档有可能被载入另一个SWF这种问题,我们新增了另外一些代码。如果游戏被载入一个容器并且该容器可多次加载并卸载游戏,那么最好是让这个单例类实例在其被从舞台上移除之后能够实现自我清除。这将防止单例类实例在内存中滞留。
  你还可以翻回到介绍音频的第7章去看看另一个实际的单例类范例。SoundEngine类用的就是这种单例模式。单例模式非常适用于创建这些不同种的控制器或者说“引擎”,因为你需要在游戏的任何位置都能轻松地访问它们。
12.4 本章小结
  如果你有心想多学一些游戏开发所适用的设计模式,那么可以看看本书网站所链接的一些非常好的文章与书。你最起码要记住“因地制宜”这四个字,不要过分追求使用那些根本不切合实际的解决方案。没人会在乎游戏的实现及设计是否很完美,以及你是否用了“模型—视图—控制器”设计模式,玩家最终只关心游戏是否好玩。

建筑师——由来已久的梦想相关推荐

  1. 浙大计算机系个人陈述,个人陈述(浙大).doc

    文档介绍: 个人陈述18岁是花开的季节,也是多梦的季节,多少个日日夜夜,多少个寒来暑往,"海阔凭鱼跃,天高任鸟飞",作为即将参加高考的我,心潮渐渐在澎湃,梦想越来越清晰----那就 ...

  2. 12亿行代码,阿里巴巴这一年的技术报告和梦想报告

    78年前,图灵用代码编译出的情报破解系统,让二战至少提前2年结束,挽救了2000万人的生命:50年前,登月科学家敲下的一行关键代码,启动了阿波罗号的着陆,成就了人类的一大步:30年前,蒂姆·伯纳斯·李 ...

  3. 造梦师的梦想是什么样的?

    这篇博文的本意似乎是要做一个总结,总结一下舟车劳顿一天后就已经不见踪影的大一上. 士兵突击里有一句话,"记得在钢七连的每一天吗?","记得". 这样,我是否又能 ...

  4. 8305天距离梦想还有8,352 千米 2012-03-31 23:18:53 柏林勃兰登堡门 再过两天就是我22周岁生日了,我在生命网站上摁下自己的出生年月,上面显示我已经度过了8305天,走

    8305天距离梦想还有8,352 千米   http://www.douban.com/note/207745712/ 2012-03-31 23:18:53 柏林勃兰登堡门 再过两天就是我22周岁生 ...

  5. 诚之和:举行虚拟婚礼、养独角兽、在月球上漫步 我们在元宇宙中能实现这些梦想

    1元宇宙的概念正逐渐成型,许多科技领域的未来学家预计,我们将可以在元宇宙中实现无数曾经的梦想. 2在元宇宙中,人们可以做许多想象中的事情,比如在月球上漫步.饲养独角兽.与同学组团参观罗马竞技场等. 3 ...

  6. 炫!迪奥元宇宙首展、著名建筑师马岩松元宇宙首作落地百度希壤

    五一前夕,百度希壤迎来"大作". 4月28日,由中国著名建筑师.MAD 建筑事务所创始人马岩松创作虚拟建筑主体的"Meta ZiWU 元宇宙誌屋"在希壤元宇宙世 ...

  7. 该如何理解黄峥的新电商梦想?

    //编者按: 拼多多的出现是必然也是偶然. 必然的是,伴随着互联网转向与消费新生力量的转移,在已有商业格局外必然诞生新的竞争者:偶然的是,它是于当时出现.做对了一些事情的拼多多. 拼多多做对了什么?黄 ...

  8. 1月10日云栖精选夜读 | 12亿行代码,阿里巴巴这一年的技术报告和梦想报告...

    78年前,图灵用代码编译出的情报破解系统,让二战至少提前2年结束,挽救了2000万人的生命:50年前,登月科学家敲下的一行关键代码,启动了阿波罗号的着陆,成就了人类的一大步:30年前,蒂姆·伯纳斯·李 ...

  9. 迪奥元宇宙首展、著名建筑师马岩松元宇宙首作落地百度希壤

    五一前夕,百度希壤迎来"大作". 4月28日,由中国著名建筑师.MAD 建筑事务所创始人马岩松创作虚拟建筑主体的"Meta ZiWU 元宇宙誌屋"在希壤元宇宙世 ...

最新文章

  1. 未来十年,中间商不会死,但是经销商可能会无店可供!
  2. MTK 添加自定义PMS权限 Patch
  3. 计算机网络基础_4_传输层与应用层的作用
  4. 一个简单的拖动层(兼容IE,FF)
  5. guava缓存数据到本地_扩展Guava缓存以溢出到磁盘
  6. 技术人最不该忽视可视化数据分析!
  7. ubuntu下安装proxychains
  8. Windows备份服务器运行失败,用Windows Server Backup搞定服务器备份
  9. mysql-fron快速写sql语句,SQL优化系列:如何书写高质量SQL语句(一)
  10. STM32F103使用DAC功能输出正弦波
  11. javaweb(06) 初步了解HTTP协议
  12. 2017百度之星资格赛:1002. 度度熊的王国战略
  13. vscode eslint插件对vue文件无效
  14. c 生成html的div,createElement动态创建HTML对象脚本代码
  15. python打印字符串数组_python3字符串格式打印:如何从数组打印?
  16. centos7构建kylo-0.10.1
  17. kindle DXG 安装多看
  18. 关于视觉SLAM中特征点法,光流法和直接法的区别和理解
  19. #Geek Founders# 蒋涛的 CES 2016 感受 - Day 4 (总结版)
  20. 巴比特国际站观察 | 海外新晋“网红”亮相,数字人民币引密码社区热议

热门文章

  1. 查询mysql数量_科研学习记录--MySQL
  2. freebsd mysql删_FreeBSD 下 mysql 的相关问题
  3. java 变量作用域 c语言_java - 数据结构 c语言 作用域问题
  4. vs发生生成错误_S7报告系统错误:
  5. Java实现简单的RPC框架
  6. Android性能优化之App应用启动分析与优化
  7. 编程中的一个易错点:判断某个点是否超出棋盘边界
  8. JavaScript深入之变量对象
  9. jQuery-切换2
  10. 数据结构--KMP算法总结