游戏中所有的种族都有几乎同类的单位和建筑。因此你可以在不同的种族上复用相同的 AI 结构,同时还需要具备重写一些细节的能力。通过这种方式,你可以重写半兽人的 AI 使其更富攻击性,也可以让人类侧重防守,还可以禁止怪物建造建筑。在游戏中新增种族需要创建新的 AI 子类,还需要重写 AI 基类中所声明的默认方法。
// 具体类必须实现基类中的所有抽象操作,但是它们不能重写模板方法自身。 class OrcsAI extends GameAI is method buildStructures() is if (there are some resources) then // 建造农场,接着是谷仓,然后是要塞。
method buildUnits() is if (there are plenty of resources) then if (there are no scouts) // 建造苦工,将其加入侦查编组。 else // 建造兽族步兵,将其加入战士编组。
// ...
method sendScouts(position) is if (scouts.length > 0) then // 将侦查编组送到指定位置。
method sendWarriors(position) is if (warriors.length > 5) then // 将战斗编组送到指定位置。
// 子类可以重写部分默认的操作。 class MonstersAI extends GameAI is method collectResources() is // 怪物不会采集资源。
// 享元类包含一个树的部分状态。这些成员变量保存的数值对于特定树而言是唯一 // 的。例如,你在这里找不到树的坐标。但这里有很多树木之间所共有的纹理和颜 // 色。由于这些数据的体积通常非常大,所以如果让每棵树都其进行保存的话将耗 // 费大量内存。因此,我们可将纹理、颜色和其他重复数据导出到一个单独的对象 // 中,然后让众多的单个树对象去引用它。 class TreeType is field name field color field texture constructor TreeType(name, color, texture) { ... } method draw(canvas, x, y) is // 1. 创建特定类型、颜色和纹理的位图。 // 2. 在画布坐标 (X,Y) 处绘制位图。
// 享元工厂决定是否复用已有享元或者创建一个新的对象。 class TreeFactory is static field treeTypes: collection of tree types static method getTreeType(name, color, texture) is type = treeTypes.find(name, color, texture) if (type == null) type = new TreeType(name, color, texture) treeTypes.add(type) return type
// 情景对象包含树状态的外在部分。程序中可以创建数十亿个此类对象,因为它们 // 体积很小:仅有两个整型坐标和一个引用成员变量。 class Tree is field x,y field type: TreeType constructor Tree(x, y, type) { ... } method draw(canvas) is type.draw(canvas, this.x, this.y)
// 树(Tree)和森林(Forest)类是享元的客户端。如果不打算继续对树类进行开 // 发,你可以将它们合并。 class Forest is field trees: collection of Trees
method plantTree(x, y, name, color, texture) is type = TreeFactory.getTreeType(name, color, texture) tree = new Tree(x, y, type) trees.add(tree)
method draw(canvas) is foreach (tree in trees) do tree.draw(canvas)
// 这里是具体命令。 class CopyCommand extends Command is // 复制命令不会被保存到历史记录中,因为它没有改变编辑器的状态。 method execute() is app.clipboard = editor.getSelection() return false
class CutCommand extends Command is // 剪切命令改变了编辑器的状态,因此它必须被保存到历史记录中。只要方法 // 返回 true,它就会被保存。 method execute() is saveBackup() app.clipboard = editor.getSelection() editor.deleteSelection() return true
class PasteCommand extends Command is method execute() is saveBackup() editor.replaceSelection(app.clipboard) return true
// 撤销操作也是一个命令。 class UndoCommand extends Command is method execute() is app.undo() return false
// 全局命令历史记录就是一个堆桟。 class CommandHistory is private field history: array of Command
// 后进... method push(c: Command) is // 将命令压入历史记录数组的末尾。
// ...先出 method pop():Command is // 从历史记录中取出最近的命令。
// 编辑器类包含实际的文本编辑操作。它会担任接收者的角色:最后所有命令都会 // 将执行工作委派给编辑器的方法。 class Editor is field text: string
method getSelection() is // 返回选中的文字。
method deleteSelection() is // 删除选中的文字。
method replaceSelection(text) is // 在当前位置插入剪贴板中的内容。
// 应用程序类会设置对象之间的关系。它会担任发送者的角色:当需要完成某些工 // 作时,它会创建并执行一个命令对象。 class Application is field clipboard: string field editors: array of Editors field activeEditor: Editor field history: CommandHistory
// 服务连接器的具体实现。该类的方法可以向腾讯视频请求信息。请求速度取决于 // 用户和腾讯视频的互联网连接情况。如果同时发送大量请求,即使所请求的信息 // 一模一样,程序的速度依然会减慢。 class ThirdPartyTVClass implements ThirdPartyTVLib is method listVideos() is // 向腾讯视频发送一个 API 请求。
method getVideoInfo(id) is // 获取某个视频的元数据。
method downloadVideo(id) is // 从腾讯视频下载一个视频文件。
// 为了节省网络带宽,我们可以将请求结果缓存下来并保存一段时间。但你可能无 // 法直接将这些代码放入服务类中。比如该类可能是第三方程序库的一部分或其签 // 名是`final(最终)`。因此我们会在一个实现了服务类接口的新代理类中放入 // 缓存代码。当代理类接收到真实请求后,才会将其委派给服务对象。 class CachedTVClass implements ThirdPartyTVLib is private field service: ThirdPartyTVLib private field listCache, videoCache field needReset
constructor CachedTVClass(service: ThirdPartyTVLib) is this.service = service
method listVideos() is if (listCache == null || needReset) listCache = service.listVideos() return listCache
method getVideoInfo(id) is if (videoCache == null || needReset) videoCache = service.getVideoInfo(id) return videoCache
method downloadVideo(id) is if (!downloadExists(id) || needReset) service.downloadVideo(id)
// 之前直接与服务对象交互的 GUI 类不需要改变,前提是它仅通过接口与服务对 // 象交互。我们可以安全地传递一个代理对象来代替真实服务对象,因为它们都实 // 现了相同的接口。 class TVManager is protected field service: ThirdPartyTVLib
constructor TVManager(service: ThirdPartyTVLib) is this.service = service
method renderVideoPage(id) is info = service.getVideoInfo(id) // 渲染视频页面。
method renderListPanel() is list = service.listVideos() // 渲染视频缩略图列表。
method reactOnUserInput() is renderVideoPage() renderListPanel()
// 程序可在运行时对代理进行配置。 class Application is method init() is aTVService = new ThirdPartyTVClass() aTVProxy = new CachedTVClass(aTVService) manager = new TVManager(aTVProxy) manager.reactOnUserInput()
// 具体中介者类可解开各组件之间相互交叉的连接关系并将其转移到中介者中。 class AuthenticationDialog implements Mediator is private field title: string private field loginOrRegisterChkBx: Checkbox private field loginUsername, loginPassword: Textbox private field registrationUsername, registrationPassword, registrationEmail: Textbox private field okBtn, cancelBtn: Button
constructor AuthenticationDialog() is // 创建所有组件对象并将当前中介者传递给其构造函数以建立连接。
// 当组件中有事件发生时,它会通知中介者。中介者接收到通知后可自行处理, // 也可将请求传递给另一个组件。 method notify(sender, event) is if (sender == loginOrRegisterChkBx and event == "check") if (loginOrRegisterChkBx.checked) title = "登录" // 1. 显示登录表单组件。 // 2. 隐藏注册表单组件。 else title = "注册" // 1. 显示注册表单组件。 // 2. 隐藏登录表单组件。
if (sender == okBtn && event == "click") if (loginOrRegister.checked) // 尝试找到使用登录信息的用户。 if (!found) // 在登录字段上方显示错误信息。 else // 1. 使用注册字段中的数据创建用户账号。 // 2. 完成用户登录工作。 …
// 组件会使用中介者接口与中介者进行交互。因此只需将它们与不同的中介者连接 // 起来,你就能在其他情境中使用这些组件了。 class Component is field dialog: Mediator
constructor Component(dialog) is this.dialog = dialog
method click() is dialog.notify(this, "click")
method keypress() is dialog.notify(this, "keypress")
// 具体组件之间无法进行交流。它们只有一个交流渠道,那就是向中介者发送通知。 class Button extends Component is // ...
class Textbox extends Component is // ...
class Checkbox extends Component is method check() is dialog.notify(this, "check") // ...
// 具体工厂可生成属于同一变体的系列产品。工厂会确保其创建的产品能相互搭配 // 使用。具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进 // 行实例化。 class WinFactory implements GUIFactory is method createButton():Button is return new WinButton() method createCheckbox():Checkbox is return new WinCheckbox()
// 每个具体工厂中都会包含一个相应的产品变体。 class MacFactory implements GUIFactory is method createButton():Button is return new MacButton() method createCheckbox():Checkbox is return new MacCheckbox()
// 系列产品中的特定产品必须有一个基础接口。所有产品变体都必须实现这个接口。 interface Button is method paint()
// 具体产品由相应的具体工厂创建。 class WinButton implements Button is method paint() is // 根据 Windows 样式渲染按钮。
class MacButton implements Button is method paint() is // 根据 macOS 样式渲染按钮
// 这是另一个产品的基础接口。所有产品都可以互动,但是只有相同具体变体的产 // 品之间才能够正确地进行交互。 interface Checkbox is method paint()
class WinCheckbox implements Checkbox is method paint() is // 根据 Windows 样式渲染复选框。
class MacCheckbox implements Checkbox is method paint() is // 根据 macOS 样式渲染复选框。
// 客户端代码仅通过抽象类型(GUIFactory、Button 和 Checkbox)使用工厂 // 和产品。这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。 class Application is private field factory: GUIFactory private field button: Button constructor Application(factory: GUIFactory) is this.factory = factory method createUI() is this.button = factory.createButton() method paint() is button.paint()
// 程序会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初 // 始化阶段)。 class ApplicationConfigurator is method main() is config = readApplicationConfigFile()
if (config.OS == "Windows") then factory = new WinFactory() else if (config.OS == "Mac") then factory = new MacFactory() else throw new Exception("错误!未知的操作系统。")
// 发布者基类包含订阅管理代码和通知方法。 class EventManager is private field listeners: hash map of event types and listeners
method subscribe(eventType, listener) is listeners.add(eventType, listener)
method unsubscribe(eventType, listener) is listeners.remove(eventType, listener)
method notify(eventType, data) is foreach (listener in listeners.of(eventType)) do listener.update(data)
// 具体发布者包含一些订阅者感兴趣的实际业务逻辑。我们可以从发布者基类中扩 // 展出该类,但在实际情况下并不总能做到,因为具体发布者可能已经是子类了。 // 在这种情况下,你可用组合来修补订阅逻辑,就像我们在这里做的一样。 class Editor is public field events: EventManager private field file: File
constructor Editor() is events = new EventManager()
// 业务逻辑的方法可将变化通知给订阅者。 method openFile(path) is this.file = new File(path) events.notify("open", file.name)
method saveFile() is file.write() events.notify("save", file.name)
// ...
// 这里是订阅者接口。如果你的编程语言支持函数类型,则可用一组函数来代替整 // 个订阅者的层次结构。 interface EventListener is method update(filename)
// 具体订阅者会对其注册的发布者所发出的更新消息做出响应。 class LoggingListener implements EventListener is private field log: File private field message
constructor LoggingListener(log_filename, message) is this.log = new File(log_filename) this.message = message
method update(filename) is log.write(replace('%s',filename,message))
class EmailAlertsListener implements EventListener is private field email: string
constructor EmailAlertsListener(email, message) is this.email = email this.message = message
method update(filename) is system.email(email, replace('%s',filename,message))
// 应用程序可在运行时配置发布者和订阅者。 class Application is method config() is editor = new Editor()
logger = new LoggingListener( "/path/to/log.txt", "有人打开了文件:%s"); editor.events.subscribe("open", logger)
emailAlerts = new EmailAlertsListener( "admin@example.com", "有人更改了文件:%s") editor.events.subscribe("save", emailAlerts)