GOF设计模式 读书笔记

程序开发思想 设计模式


参考书籍

  • GOF设计模式
    图书封面

  • 《设计模式:可复用面向对象软件的基础》

  • 原作名《Design Patterns: Elements of Reusable Object-Oriented Software》

  • 作者: [美] Erich Gamma / Richard Helm / Ralph Johnson / John Vlissides

  • 译者: 李英军 / 马晓星 / 蔡敏 / 刘建中 等

  • 出版社: 机械工业出版社

  • 出品方: 华章IT

图书相关的轶事:
由于设计模式这一话题的特殊性,造就了这么一本出版业界的销量传奇,由于其文字描述比较晦涩难懂,《设计模式》也是完读率最低的图书之一。


读书笔记

  • 设计原则

    原则 说明
    OCP 【开放封闭原则】 类应该可扩展,但是不可修改。
    LSP 【里氏代换】 子类必须能够在任何场合替代父类,继承表达类型抽象 。
    DIP 【依赖倒置】 高层稳定的模块不能依赖于变化的低层模块。
    SRP 【单一职责】 一个类仅有一个引起其变化的原因。(变化方向隐含类的责任)
    ISP 【接口隔离】 不强迫客户程序依赖不用的方法。(接口要小而完备)
    优先使用对象的组合 -
    封装变化点 -
    面向接口,而不是针对实现 -
  • 面向对象的特性--多态

    观察23种设计模式的类图,可以发现经常出现如图所示多态指针。
    提高复用是设计模式的目标之一,这里的复用一般指的是二进制层面的复用。
    二进制复用意味着在代码编译阶段稳定部分的代码不能够直接依赖变化的部分。
    多态指针可以很好的实现运行时依赖,使得在不改变源码的情况下,通过传递不同的派生类实现行为的变化。
    多态指针

  • 23种设计模式简要说明

组件协作 模板方法 算法中有固定的流程,而具体的步骤有差异。由基类定义模板,子类实现流程中的步骤。
策略模式 通过更换子类来使用不同的方法。有多个if else时就很有可能需要使用该模式。
观察者模式 也叫发布订阅模式,观察者类能够向目标类登记订阅。目标类应当公开订阅方法,由观察者主动订阅。
单一职责 装饰模式 一系列子类能被相同基类的子类所装饰,将套接后的类任然属于该基类。比较好的实现有JAVA IO库和C# .NET IO库的文件流。
桥模式 解除抽象和实现的耦合,间接调用目标对象的方法。
对象创建 工厂方法 使用间接方式获取对象。一种工厂只能获取一种产品。
抽象工厂 抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
原型模式 通过复制的方式获取对象。
构建器 获得由多种类构建的对象。产品类型不变,具体的产品可以任意组合。
对象性能 单例(单件) 由于类的职责,类在程序中有且只能有一个实例,如一些管理类。
享元 通过共享资源的方式解决对象创建导致的性能问题,如常见的实现有内存池。
接口隔离 门面 在一系列类的外部定义一层接口,可以明显感受到分层架构的特征。
代理 代理模式没有很具体的实现方式。一般是由于项目特征需要而使用该模式。比如分布式的项目。
适配器 使用原有的类,改装接口以适应当前需求。如C++库中的stack,list都是用deque改装的。
中介者 实例之间体现一种类星型结构,中介类类似于一种转发机构。
状态变化 状态模式 环境类内部封装了状态类。当类内部的状态发生变化时,其行为也发生变化。状态模式的实现与策略模式相似,都是通过具体类的改变来改变行为。环境类就是一台状态机。
备忘录 捕获目标类内部的状态,保存在备忘录中,在必要时能够将目标类恢复为原来保存的状态。又叫快照模式。
数据结构 组合模式 一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。参考 .NET WPF框架的设计。
责任链 将处理类的实例构造成单链,逐步转发任务直到有处理类能够执行任务。用轮询的方式处理任务。
迭代器 提供一种访问对象集合的方法,无需了解对象集合的组织方式。符合单一职责原则。可参考C++库中的迭代器。
行为变化 命令模式 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
访问器 由访问者调用目标对象的函数,在调用函数的外部增加函数行为。一旦使用该模式,就需要重重的使用。
领域问题 解析器 更像是一种语法分析的算法。

设计模式小结

GOF设计模式是根据面向对象编写的。由于实现性能的原因,GOF中很多实现在C++标准库中已经过时。设计模式的核心是隔离变化,寻找稳定的地方,妥善处理变化的地方。相比起具体的模式,设计原则更具有普遍指导意义,在GOF的23种设计模式中也可以发现,模式并不一定符合所有的设计原则。


案例:操控游戏世界中的角色

不考虑设计原则和模式版本

  • 直接在角色类内部实现
class PlayerCharacter:public Character
{
    //这是一个逐帧调用的函数
    void tick()
    {
        if(isPressed(button_X)) attack(character);
        else if(isPressed(button_Y)) specialAttack(character);
        else if(isPressed(button_A)) dash(character);
        else if(isPressed(button_B)) useItem(character);
    }
}

/// 直接实现的方式很明显给角色类添加了额外的职责。
/// 特别是对于需要支持多种输入设备时
/// PC平台可能要支持手柄,键鼠。
/// 手柄又分为Playstation和XBox等。
/// 高耦合的代码对编码工作带来了不小的挑战。

SRP原则(单一职责)

  • 将键盘输入的处理分离为一个控制器类
///玩家角色
class PlayerCharacter:public Character
{
private:    
  Controler* controler;
public: 
  tick()
  {
      controler->controlerInput(*this);
  }
}

class Controler
{
public:
    virtual `Controler(){}
    virtual void controlerInput(Character& character)=0;
}

///玩家控制器
class PlayerControler:public Controler
{
public:
    virtual void controlerInput(Character& character)
    {
        if(isPressed(button_X)) attack(character);
        else if(isPressed(button_Y)) specialAttack(character);
        else if(isPressed(button_A)) dash(character);
        else if(isPressed(button_B)) useItem(character);
    }
}

/// 为Controler基类是因为NPC可能需要使用自动化输入的AI控制器,。
/// 将处理键盘输入的代码分离出来之后,角色的职责也更加明确。
/// 输入处理交给控制器,角色只负责执行设定的指令。

///还可以更彻底分离两个类,
///控制器处理输入并转化为信号(可以简单的实现为public成员)。
///在角色的脚本上只要去判断输入信号即可。

命令模式

  • 试着让玩家能够配置键位映射
///命令基类
class Command
{
public:
    virtual ~Command() {}
    virtual void execute(Character& character) = 0;
};

///命令派生类--攻击
class AttackCommand:public Command
{
public:
    virtual void execute(Character& character) {...}
}

///命令派生类--冲刺
class DashCommand:public Command
{
public:
    virtual void execute(Character& character) {...}
}

///命令派生类--特殊攻击
class SpecialAttackCommand:public Command
{
public:
    virtual void execute(Character& character) {...}
}

///命令派生类--使用道具
class UseItemCommand:public Command
{
public:
    virtual void execute(Character& character) {...}
}


///玩家控制器
class PlayerControler:public Controler
{
private:
    Command* buttonX;
    Command* buttonY;
    Command* buttonA;
    Command* buttonB;
public:
    virtual void controlerInput(Character& character)
    {
        if(isPressed(button_X)) buttonX->execute(character);
        else if(isPressed(button_Y)) buttonY->execute(character);
        else if(isPressed(button_A)) buttonA->execute(character);
        else if(isPressed(button_B)) buttonB->execute(character);
    }
}

/// 通过设计命令类,并在控制器中添加命令的基类指针。
/// 使得编译时不会把命令与相应的键位静态地绑定。
/// 通过多态实现按键配置与行为改变
/// 按键输入也被翻译成为相应的命令。

  • 学习心得
    使用设计模式比较好的方法是重构的模式。
    使用设计模式需要精妙的设计,这意味着需要花费更多的时间思考软件的结构,也需要编写更多的模式代码。同时也很难在一个项目的开头就对模式有很深的感觉,在没有充分开发经验的情况下容易误用模式。
posted @ 2021-03-15 22:48  我的小耗子  阅读(188)  评论(0)    收藏  举报