870920 Menu

小小的说明之三:关于设计模式

继续上文。除了“四人帮”罗列的那一堆设计模式之外,实际上在现代面向对象的软件工程中还有一些常用、常见的模式,或者“模式变体”。本文只列出一部分(比较大众和大众熟知的)。仅为了配合介绍JUCE类库做个导引或预备,并非专门谈论模式(实际上,真实项目中的模式运用是很复杂,很模糊的,不下一番苦工梳理和去芜存菁,很难直接套用现成的或对号入座)。


组合模式(Composite Pattern):结构性模式,组合多个对象形成树型结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性(相同的接口)。叶子对象和容器对象均继承自同一个基类(因此具有相同的接口),客户端直接针对基类编程。文件和目录浏览遍历、XML遍历、复杂的GUI界面之组成均属于典型的组合模式(以JUCE GUI为例:控件和控件容器具有相同的接口,均继承自Component类。客户端仅针对基类Component类编程即可)。

观察者模式(Observer Pattern):行为型模式,又称为“发布-订阅”、“模型-视图”、“来源-监听”模式。该模式定义了对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象均得到通知并被自动更新。观察者模式有两类角色,一是被动改变的观察者(视图、监听),二是产生变化的被观察者(数据来源、模型、观察目标)。数据来源和模型只有一个,而观察者却可以有多个。如果数据来源和模型调用成员函数后发生改变或产生事件,观察者就会及时响应,做出处理。基于消息循环的GUI程序,其事件驱动机制就采用了观察者模式。MVC(模型-视图-控制器)框架也应用了观察者模式。JUCE类库的消息机制大量采用了这种模式。比如:“发布类”嵌套的Listener抽象基类,“订阅类”继承该抽象基类,实现纯虚函数,“发布类”在“订阅类”中注册捕获器,从而使“订阅类”能够处理“发布类”所产生的消息。

命令模式(Command pattern):行为型模式,在发送者和接收者之间增加一个命令层(抽象命令及其派生的各个具体命令),将发送者的请求封装在命令类的对象中,再通过命令对象来调用接收者的方法。该模式用于处理对象之间的调用,将发送者和接收者解耦,两者之间互不知情、各司其职,通过命令对象来中转,从而增加灵活性。该模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开来,并引入抽象的命令接口,发送者针对该接口进行编程,而实现了抽象基类的具体命令则与接收者相关联,从而使接收者能够执行发送者的命令。如果在命令类中同时提供撤销请求的方法,则该模式可实现应用程序中的“撤销/重做”功能。GUI程序中的“快捷键”、点击按钮后执行某个操作处理等功能往往采用了命令模式。即:大多数GUI类库中,提供了命令的发送者、接收者和抽象命令类,实际编程中,仅需自定义具体命令类或实现该基类的某些功能性纯虚函数即可(比如JUCE类库中实现Undo/Redo功能和ButtonListener类的buttonClicked()等等),而增加新命令,无需修改已有的三个类(发送者、抽象命令、接收者),在“单一职责”的基础上满足了程序设计中的“开闭原则”。此外,还可实现多个命令的批量处理。

享元模式(Flyweight Pattern):结构型模式,通过共享技术实现相同或相似的细粒度对象的复用,节省内存,提高性能。现代C++编程中常用的引用计数(隐式共享、写时复制)等技术就是基于享元模式的(比如JUCE类库中的ReferenceCountedObject、Value、String、var等类)。

工厂模式(Factory Pattern):创建型模式。工厂模式有简单工厂、抽象工厂、工厂方法等多个变形。其核心思想是:将对象的创建与对象的使用分割开来,互不影响。创建对象由工厂类负责,其中,抽象工厂模式中,工厂类有一个抽象基类,其派生类负责创建具体的对象。所创建的对象则为另一个类系。客户端使用对象时,直接与工厂类打交道,让工厂类提供所需的对象,而不是直接创建之。

备忘模式(Memento Pattern):行为型模式,将A对象在某一时刻的状态(属性、数据)保存到B类对象中(此时,A对象的某个成员函数创建B类对象,return new B();返回指针即可,使之保存自己当前的状态。B类往往是带参构造,构造参数即A类的属性数据。所创建的B对象由客户端负责管理)。需要恢复状态时,A对象调用自己的另一个成员函数(该函数的参数即为B类对象,该对象由客户端负责提供),读取B对象中保存的数据并重新设置自己的属性。该模式有三个角色:① 需要备忘的A对象。② A对象所创建的B类对象的堆中对象,该对象的地址由客户端负责管理并赋值给B类的指针,每个对象保存A对象在某一时刻的状态。③ 客户端。

适配器模式(Adapter Pattern):结构型模式。将一个类的接口转化为客户希望的另一个接口,使得原本由于接口不兼容的类可以一起工作。该模式有两种情况:类适配器(采用多重继承的思路)和对象适配器(对象组合的思路)。该模式堪称跨平台类库的设计之基。深入研究JUCE类库,即可体会这种模式的比比皆是。

外观模式(Facade Pattern):多个类或子系统协同实现某个功能,客户端并不直接与这些类打交道,而是将这些类的对象组合到一个外观类中,客户端使用外观类的对象完成所需的功能。

单例模式(Singleton Pattern):确保某个类最多只能实例化一个对象,提供一个访问该对象的全局访问点(静态函数)。其核心思想是:让该类负责创建并保存自己的唯一实例(类中声明一个静态数据成员为本类指针,而后写一个public静态函数创建并返回之。如果尚未创建,则创建,已经创建,则直接返回),用户无法通过正常方式实例化其对象(将构造函数和拷构函数声明为非public,也因此,单例类丧失了派生子类和多态的能力)。需注意线程安全和可重入的问题(因为类中有静态数据)。JUCE类库中,要实现线程安全的单例类,可参阅3.5.5 Singleton模式。该类库同时还有一个非线程安全的单例类实现策略。

桥接模式(Bridge Pattern):结构型模式,将继承关系转换为组合关系,即:将A类继承自B类,转换为A类中组合B类的对象(指针),A类对象调用某个函数时,该函数为参数B类的对象(指针),该函数所完成的具体功能,其实是B类对象(指针)调用其具体的功能性函数。A类和B类可为抽象基类。

迭代器模式(Iterate Pattern):提供了一个机制,使得操作或算法能够顺序访问容器(数据对象)的元素,将容器的数据与操作(属性和行为)进行解耦。C++ STL和JUCE类库的底层数据容器(及算法实现)大量使用了这种模式。