设计模式笔记

总览#

设计模式原则:

  1. 开闭原则

对扩展开放,对修改关闭。

  1. 单一职责原则

一个类只负责一个功能领域中的相应职责。

  1. 里氏替换原则

所有使用基类的地方必须能透明地使用其子类对象。

  1. 依赖倒置原则

面向抽象编程,而不要面向具体对象编程。

  1. 接口隔离原则

类之间的依赖关系应该建立在最小的接口上。

  1. 迪米特法则

一个实体应尽可能少得与其他实体发生相互作用。

  1. 合成复用原则

尽可能使用合成,而不是继承来达到复用的目的。

设计模式 可变的方面
Abstract Factory(抽象工厂) 产品对象家族
Builder(生成器) 如何创建一个组合对象
Factory Method(工厂方法) 被实例化的子类
Prototype(原型) 被实例化的类
Singleton(单例模式) 一个类的唯一实例
Adapter(适配器) 对象的接口
Bridge(桥接) 对象的实现
Composite(组合) 一个对象的结构和组成
Decorator(装饰) 对象的职责,不生成子类
Facade(外观) 一个子系统的接口
Flyweight(享元) 对象的存储开销
Proxy(代理) 如何访问一个对象;该对象的位置
Chain of Responsibility(职责链) 满足一个请求的对象
Command(命令) 何时、怎样满足一个请求
Interpreter(解释器) 一个语言的文法及解释
Iterator(迭代器) 如何遍历、访问一个聚合的各元素
Mediator(中介者) 对象间如何交互、和谁交互
Memento(备忘录) 一个对象中哪些私有信息存放在该对象之外,以及在什么时候进行存储
Observer(观察者) 多个对象依赖于另外一个对象,而这些对象又如何保持一致
State(状态) 对象的状态
Strategy(策略) 算法
Template Method(模板方法) 算法中的某些步骤
Visitor(访问者) 某些可作用于一个对象(组)对象上的操作,但不修改这些对象的类

创建型模式#

创建型设计模式抽象了实例化过程。
两个特征:第一,将使用哪些具体的类的信息封装起来。第二,隐藏了这些类的实例是如何被创建和放在一起的。
因此,创建型模式在什么被创建、创建了它、它是怎样被创建的,以及何时创建等方面给予了很大的灵活性。

/*
一个不使用任何设计模式的迷宫代码实现
*/
enum Direction {North, South, East, West};
//MapSite是所有迷宫构件的公共抽象类,
//为简化例子,只定义了一个enter操作,它的含义取绝于你进入哪里,
//如果你进入一个房间,那么你的位置就会改变。如果你试图进入一扇门,会进行判断:门是开着的,你进入另一个房间,门是关着的,你会撞墙。
class MapSite {
public:
    virtual void Enter() = 0;
};

class Room : public MapSite {
public:
    Room(int roomNo);
    MapSite* GetSide(Direction) const;
    void SetSide(Direction, MapSite*);
    virtual void Enter();
private:
    MapSite* _sides[4];
    int _roomNumber;
};

class Wall : public MapSite {
public:
    Wall();
    virtual void Enter();
};

class Door : public MapSite {
public:
    Door(Room* = nullptr, Room* = nullptr);
    virtual void Enter();
    Room* OtherSideFrom(Room*);
private:
    Room* _room1;
    Room* _room2;
    bool _isOpen;
};

class Maze {
public:
    Maze();
    void AddRoom(Room*);
    Room* RoomNo(int) const;
private:
    //...
};

//另外定义了一个MazeGame类来创建迷宫。
Maze* MazeGame::CreateMaze() {
    Maze* aMaze = new Maze;
    Room* r1 = new Room(1);
    Room* r2 = new Room(2);
    Door* theDoor = new Door(r1, r2);

    aMaze->AddRoom(r1);
    aMaze->AddRoom(r2);

    r1->SetSide(North, new Wall);
    r1->SetSide(East, theDoor);
    r1->SetSide(South, new Wall);
    r1->SetSide(West, new Wall);
    r2->SetSide(North, new Wall);
    r2->SetSide(East, new Wall);
    r2->SetSide(South, new Wall);
    r2->SetSide(West, theDoor);
    
    return aMaze;
}

Abstract Factory(抽象工厂)#

意图:
提供一个接口以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。
适用性

  • 一个系统要独立于它的产品的创建、组合和表示。
  • 一个系统要由多个产品系列中的一个来配置。
  • 要强调一系列相关的产品对象的设计以便进行联合使用。
  • 提供一个产品类库,但只想显示它们的接口而不是实现。
class MazeFactory {
public:
    MazeFactory();
    //假定返回的对象都是创建过的组件
    virtual Maze* MakeMaze() const {    //创建迷宫
        return new Maze;
    }
    virtual Wall* MakeWall() const {    //创建墙壁
        return new Wall;
    }
    virtual Room* MakeRoom(int n) const {   //创建房间
        return new Room(n);
    }
    virtual Door* MakeDoor(Room* r1, Room* r2) const {  //创建门
        return new Door(r1, r2);
    }
};

//通过工厂来创建迷宫的各个组件
Maze* Mazegame::CreatMaze(MazeFactory& factory) {
    Maze* aMaze = factoty.MakeMaze();
    Room* r1 = factory.MakeRoom(1);
    Room* r2 = factory.MakeRoom(2);
    Door* aDoor = factory.MakeDoor(r1, r2);

    aMaze->AddRoom(r1);
    aMaze->AddRoom(r2);

    r1->SetSize(North, factory.MakeWall());
    r1->SetSide(East, aDoor);
    r1->SetSize(South, factory.MakeWall());
    r1->SetSize(West, factory.MakeWall());
    r2->SetSize(North, factory.MakeWall());
    r2->SetSize(East, factory.MakeWall());
    r2->SetSize(South, factory.MakeWall());
    r2->setSide(West, aDoor);
    
    return aMaze;
};

//现在我们要创建一个被施了魔法的迷宫,就需要重新定义下迷宫工厂中的成员函数,并返回迷宫的各个组件
class EnchantedMazeFactory : public MazeFactory {
public:
    EnchantedMazeFactory();
    
    virtual Room* MakeRoom(int n) override const{
        return new EnchantedRoom(n, CastSpell());   //返回一个存放了钥匙的房间
    }
    virtual Door* MakeRoom(int n) override const {
        return new DoorNeedingSpell(r1, r2);    //返回一个需要钥匙才能打开的门
    }

private:
    Spell* CastSpell() const;
};

int main()
{
    Mazegame game;
    EnchantedMazeFactory factroy;
    game.CreateMaze(factory);   //通过类的多态就可以直接创建一个有魔法的迷宫

    //假如我们又要创建个新的迷宫,只需要再写一个迷宫工程的子类,注释掉上面迷宫的生成,再在主程序中实例化新的类对象就行了,而无需对迷宫工厂进行修改
    NewFactory nfactory;
    game.CreateMaze(nfactory);

    return 0;
}

协作:

  • 通常在运行时创建一个具体工厂类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,需要使用不同的具体工厂。
  • 抽象工厂类将产品对象的创建延迟到了它的具体工厂子类。

效果:
优点

  • 分离了具体的类。
  • 使得易于交换产品系列。
  • 有利于产品的一致性。

缺点

难以支持新种类的产品:能够创建的产品在抽象类中都定义好了接口,如果扩展接口就势必要修改抽象类。可以通过Prototype模式来解决。

Builder(生成器)#

意图
将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。
适用性

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造对象有不同的表达时。

协作

  • 客户创建Director对象,并用它所想要的Builder对象进行配置。
  • 一旦生成了产品部件,导向器就会通知生成器。
  • 生成器处理导向器的请求,并将部件添加到该产品中。
  • 客户从生成器中检索产品。

效果

  1. 它使你可以改变一个产品的内部展示。
  2. 他将构造代码和表示代码分开。
  3. 它使你可对构造过程进行更精细的控制。
//MazeBuilder定义了一系列的接口,它的子类将重定义这些接口,返回它们创建的迷宫
class MazeBuilder {
public:
    virtual void BuildMaze() {}
    virtual void BuildRoom(int n) {}
    virtual void BuildDoor(int roomFrom, int roonTo) {}

    virtual Maze* GetMaze() {return 0;}

protected:
    MazeBuilder();
};

//这样在建造迷宫时,就隐藏了内部实现
Maze* MazeGame::CreateMaze(MazeBuilder& builder) {
    builder.BuildMaze();
    builder.BuildRoom(1);
    builder.BuildRoom(2);
    builder.BuildDoor(1, 2);
    
    return builder.GetMaze();
}

//MazeBUilder自己并不创建迷宫,而只是定义了接口,实际工作交给子类完成
class StandardMazeBuilder : public MazeBuilder {
public:
    StandardMazeBuilder();
    
    virtual void BuildMaze() override;
    virtual void BuildRoom(int) override;
    virtual void BuildDoor(int, int) override;

    virtual Maze* GetMaze() override;
private:
    Dirction CommonWall(Room*, Room*);  //一个功能性操作,决定两个房间之间公共墙壁的方位
    Maze* _currentMaze; //将创建的迷宫放在这个变量中
};

StandardMazeBuilder::StandarMazeBuilder() {
    _currentMaze = nullptr;
}

void StandardMazeBuilder::BuildMaze() {
    _currentMaze = new Maze;
}

void StandardMazeBuilder::BuildRoom(int n) {
    if(!_currentMaze->RoomNo(n)) {
        Room* room = new Room(n);
        _currentMaze->AddRoom(room);
        room->SetSide(North, new Wall);
        room->SetSide(East, new Wall);
        room->SetSide(South, new Wall);
        room->SetSide(West, new Wall);
    }
}

void StandardMazeBuilder::BuildDoor(int n1, int n2) {
    Room* r1 = _currentMaze->RoomNo(n1);
    Room* r2 = _currentMaze->RoomNo(n2);
    Door* d = new Door(r1, r2);
    r1->SetSide(CommonWall(r1, r2), d);
    r2->SetSide(CommonWall(r2, r1), d);
}

//现在客户就可以使用CreateMaze和StandardMazeBuilder来创建迷宫了
int main()
{
    Maze* maze;
    MazeGame game;
    StandardMazeBuilder builder;
    game.CreateMaze(builder);
    maze = builder.GetMaze();
}

相关模式
Abstract Factory和Builder模式看起来是非常相似的,都可以用来创建复杂的对象。区别之处在于,Builder模式着重于一步一步构建一个复杂对象,而Abstract Factory着重于多个系列的产品对象。Builder在最后一步返回产品,而Abstract Factory的产品是立即返回的。
Composite模式通常是用Builder生成的。

Factory Method(工厂方法)#

意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
适用性

  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

协作
Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct实例。

class MazeGame {
public:
    Maze* CreateMaze();
    //使用工厂方法,创建迷宫、房间、墙壁和门对象
    virtual Maze* MakeMaze() const {
        return new Maze;
    }
    virtual Room* MakeRoom(int n) const {
        return new Room(n);
    }
    virtual Wall* MakeWall() const {
        return new Wall;
    }
    virtual Door* MakeDoor(Room* r1, Room* r2) const {
        return new Door(r1, r2);
    }
};

//现在可以用工厂方法重写CreateMaze
Maze* MazeGame::CreateMaze() {
    Maze* aMaze = MakeMaze();
    Room* r1 = MakeRoom(1);
    Room* r2 = MakeRoom(2);
    Door* theDoor = MakeDoor(r1, r2);
    aMaze->AddRoom(r1);
    aMaze->AddRoom(r2);
    r1->SetSide(North, new Wall);
    r1->SetSide(East, theDoor);
    r1->SetSide(South, new Wall);
    r1->SetSide(West, new Wall);
    r2->SetSide(North, new Wall);
    r2->SetSide(East, new Wall);
    r2->SetSide(South, new Wall);
    r2->SetSide(West, theDoor);
    
    return aMaze;
}

//不同游戏可以创建MazeGame的子类来特别指明一些迷宫的部件
//子类可以重定义一些或所有工厂方法来指明产品中的变化
//例如一个EnchantedMazeGame
class EnchantedMazeGame : public MazeGame {
public:
    EnchantedMazeGame();
    virtual Room* MakeRoom(int n) const {
        return new EnchantedRoom(n, CastSpell());
    }
    virtual Door* MakeDoor(Room* r1, Room* r2) const {
        return new DoorNeedingSpell(r1, r2);
    }
protected:
    Spell* CastSpell() const;
};

工厂方法可以理解为不要将对象的各种一大坨实现都放在构造函数中,而是要拆分成多个部分的函数来组合实现。
相关模式
Abstract Factory经常用工厂方法来实现。
工厂方法通常在Template Method中被调用。
Prototype不需要创建Creator的子类。但是它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象,而Factory Method不需要这样 的操作。

Prototype(原型)#

意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
适用性

  • 当一个系统应该独立于它的产品创建、构成和表示时。
  • 当要实例化的类是在运行时指定时,例如,通过动态装载。
  • 为了避免创建一个与产品类层次平行的工厂类层次时。
  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

效果

  1. 运行时增加和删除产品。
  2. 改变值以指定新对象。
  3. 改变结构以指定新对象。
  4. 减少子类的构造。
  5. 用类动态配置应用。
class MazePrototypeFactory : public MazeFactory {
public:
    MazePrototypeFactory(Maze*, Wall*, Room*, Door*);

    virtual Maze* MakeMaze() override const;
    virtual Room* MakeRoom(int) override const;
    virtual Wall* MakeWall() override const;
    virtual Door* MakeDoor(Room*, Room*) override const;

private:
    Maze* _prototypeMaze;
    Room* _prototypeRoom;
    Wall* _prototypeWall;
    Door* _prototypeDoor;
};

//新的构造器只初始化它的原型
MazePrototypeFactory::MazePrototypeFactory(Maze* m, Wall* w, Room* r, Door* d) : 
    _prototypeMaze(m), _prototypeRoom(r), _prototypeWall(w), _prototypeDoor(d) {}
//用于创建墙壁、房间和门的成员函数都相似:克隆原型,然后初始化
Wall* MazePrototypeFactory::MakeWall() override const {
    return _prototypeWall->Clone();     //原型必须是能够进行克隆操作的
}
Door* MazePrototypeFactory::MakeDoor(Room* r1, Room* r2) override const {
    Door* door = _prototypeDoor->Clone();
    door->Initialize(r1, r2);
    return door;
}
//...

//如此就可以只使用迷宫构件的原型初始化出来一个迷宫
int main()
{
    MazeGame game;
    MazePrototypeFactory simpleMazeFactory(new Maze, new Wall, new Room, new Door);
    Maze* maze = game.CreateMaze(simpleMazeFactory);
}

Singleton(单例模式)#

意图
保证一个类仅有一个实例。并提供一个访问它的全局访问点。
适用性

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
    实现
    将创建唯一实例的操作隐藏在一个类操作(即一个静态成员函数或者一个类方法)后面,由它来保证只有一个实例被创建。
    在C++中可以用Singleton类的静态成员函数Instance来定义这个类操作。Singleton还定义了一个静态成员变量_instance,它包含了一个指向它的唯一实例的指针。
//Maze应用应该只需要一个迷宫工厂的一个实例,而且这个实例对创建迷宫任何部件的代码都是可用的。
class MazeFactory {
public:
    static MazeFactory* Instance();
    //existing interface goes here
protected:
    MazeFactory();
private:
    static MazeFactory* _instance;
};

MazeFactory* MazeFactory::_instance = nullptr;
MazeFactory* MazeFactory::Instance() {
    if(_instance == nullptr) {
        _instance = new MazeFactory;
    }
    return _instance;
}

//假如MazeFactory存在多个子类,而且应用必须决定使用哪个子类
//Instance操作可以改成如下形式
MazeFactory* MazeFactory::Instance() {
    if(_instance == nullptr) {
        const char* mazeStyle = getenv("MAZESTYLE");
        if(strcmp(mazestyle, "bombed") == 0) {
            _instance = new BombedMazeFactory;
        }
        else if(strcmp(mazestyle, "enchanted") == 0) {
            _instance = new EnchantedMazeFactory;
        }
        //...other possible subclasses
        else {
        _instance = new MazeFactory;
        }
    }
    return _instance;
}

结构性模式#

结构性模式涉及如何组合类和对象以获得更大的结构。结构类模式采用继承机制来组合接口或实现。
结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。因为可以在运行时改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。

Adapter(适配器)#

意图
将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用性

  • 你想使用一个已经存在的类,而它的接口不符合你的需求。
  • 你想创建一个已经复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

协作
Client在Adapter实例上调用一些操作,接着适配器调用Adaptee的操作实现这个请求。

class Shape {
public:
    Shape();
    virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
    virtual Manipulator* CreateManipulator() const;
    
};

class TextView {
public:
    TextView();
    void GetOrigin(Coord& x, Coord& y) const;
    void GetExtent(Coord& width, Coord& height) const;
    virtual bool IsEmpty() const;
};
//Shape假定有一个边框,这个边框由它相对的两角定义。
//而TextView则由原点,宽度和高度定义。
//Shape同时定义了CreateManipulator操作用于创建一个Manipulator对象。当用户操作一个图形时,Manipulator对象知道如何驱动这个图形。
//TextView没有等同的操作。
//TextShape类是这些不同接口间的适配器
class TextShape : public Shape, private TextView {
public:
    TextShape();
    virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
    virtual bool IsEmpty() const;
    virtual Manipulator* CreateManipulator() const;
};

void TextShape::BoundingBox(Point& bottomLeft, Point& tpRight) const {
    Coord bottom, left, width, height;
    GetOrigin(bottom, left);
    GetExtent(width, height);
    bottomLeft = Point(bottom, left);
    topRight = Point(bottom + height, left + width);
}

void TextShape::IsEmpty() const {
    return TextView::IsEmpty();
}

Manipulator* TextShape::CreateManipulator() const {
    return new TextManipulator(this);   //假设实现了支持TextShape操作的类
}

/*
对象适配器采用对象组合的方法将具有不同接口的类组合在一起
*/
class TextShape : public Shape {
public:
    TextShape(TextView*);
    virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
    virtual bool IsEmpty() const;
    virtual Manipulator* CreateManipulator() const;

private:
    TextView* _text;    //维护一个指向TextView的指针
};

TextShape::TextShape(TextView* t) {
    _text = t;
}

void TextShape::BoundingBox(Point& bottomLeft, Point& topRight) const {
    Coord Bottom, left, width, height;
    _text->GetOrigin(bottom, left);
    _text->GetExtent(width, height);
    bottomLeft = Point(bottom, left);
    topRight = Point(bottom + height, left + width);
}

bool TextShape::IsEmpty() const {
    return _text->IsEmpty();
}

Manipulator* TextShape::CreateManipulator() const {
    return new TextManipulator(this);
}

//对象适配器比起类适配器更麻烦一些,但也比较灵活

相关模式

  • Bridge模式的结构与对象适配器类似,但是Bridge模式的出发点不同:Bridge模式的目的是将接口部分和实现部分分离,从而可以对它们较为容易也相对独立地加以改变。而Adaptor则意味着改变一个已有对象的接口。
  • Decorator模式增强了其他对象的功能而同时又不改变它的接口,因此Decorator对应用程序的透明性比适配器要好。结果是Decorator支持递归组合,而纯粹使用适配器是不可能实现这一点的。
  • Proxy模式在不改变它的接口的条件下,为另一个对象定义了一个代理。

Bridge(桥接)#

意图
将抽象部分与它的实现部分分离,使它们可以独立地变化。
适用性
以下情况使用Bridge模式:

  • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如,这种情况可能是因为,在程序运行时实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
  • (C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。
  • 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。

效果

  1. 分离接口及其实现部分。
  2. 提高可扩充性。
  3. 实现细节对客户透明。
//Window类为客户应用程序定义了窗口抽象类
class Window {
public:
    Window(View* contents);

    //通过window类解决的请求
    virtual void DrawContents();
    virtual void Open();
    virtual void Close();
    virtual void Iconify();
    virtual void Deiconify();

    //将请求传递给imp类
    virtual void SetOrigin(const Point& at);
    virtual void SetExtent(const Point& extent);
    virtual void Raise();
    virtual void Lower();

    virtual void DrawLine(const Point&, const Point&);
    virtual void DrawRect(const Point&, const Point&);
    virtual void DrawPolygon(const Point[], int n);
    virtual void DrawText(const char*, const Point&);

protected:
    WindowImp* GetWindowImp();
    View* GetView();
private:
    WindowImp* _imp;
    View* _contents;    //窗口的内容
};

//Window维护一个对WindowImp的引用,WindowImp抽象定义了一个对底层窗口系统的接口
class WindowImp {
public:
    virtual void ImpTop() = 0;
    virtual void Impbottom() = 0;
    virtual void ImpSetExtent(const Point&) = 0;
    virtual void ImpSetOrigin(const Point&) = 0;
    virtual void DeviceRect(Coord, Coord, Coord, Coord) = 0;
    virtual void DeviceText(const char*, Coord, Coord) = 0;
    virtual void DeviceBitmap(const char*, Coord, Coord) = 0;
    //...

protected:
    WindowImp();
};

//Window的子类定义了应用程序可能用到的不同类型的窗口
//Application Window类将实现DrawContents操作以绘制它所储存的View实例:
class ApplicationWindow : public Window {
public:
    //...
    virtual void DrawContents();
};

void ApplicationWindow::DrawContents() {
    GetView()->DrawOn(this);
};

//IconWindow中存储了它所显示的图标对应的位图名,并是心啊你DrawContents操作将这个位图绘制在窗口上
class IconWindow : public Window {
public:
    //...
    virtual void DrawContents();
private:
    const char* _bitmapName;
};

void IconWindow::DrawContents() {
    WindowImp* imp = GetWindowImp();
    if(imp != nullptr) {
        imp->DeviceBitmap(_bitmapName, 0.0, 0.0);
    }
}

//Window的操作由WindowImp的接口定义
void Window::DrawRect(const Point& p1, const Point& p2) {
    WindowImp* Imp = GetWindowImp();
    imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());
}

//具体的WindowImp子类可支持不同的窗口系统,如XWindowImp子类支持X window系统
class XWindowImp : public WindowImp {
public:
    XWindowImp();
    virtual void DeviceRect(Coord, Coord, Coord, Coord);
    //...
private:
    //许多X Window系统独有的标志
    Display* _dpy;
    Drawable _winid; //window id
    GC _gc;     //
};

//对于X Window系统就可以这样实现DeviceRect
void XWindowImp::DeviceRect(Coord x0, Coord y0, Coord x1, Coord y1) {
    int x = round(min(x0, x1));
    int y = round(min(y0, y1));
    int w = round(abs(x0 - x1));
    int h = round(abs(y0 - y1));
    XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);
}

//那么一个窗口如何正确得到WindowImp子类的实例?
//可以假设GetWindowImp操作负责从一个抽象工厂得到正确的实例,这个抽象工厂封装了所有窗口系统的细节
WindowImp* Window::GetWindowImp() { 
    //方便起见,写成单例模式
    if(_imp == 0) {
        _imp = WindowSystemFactory::Instance()->MakeWindowImp();
    }
    return _imp;
}

相关模式

  • Abstract Factory模式可以用来创建和配置一个特定的Bridge模式。
  • Adapter模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然而,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

理解
Bridge模式是将一个复杂对象的功能和其实现分离开了,可能是因为功能在不同平台或环境有不同的实现形式。分离之后就不用指定固定的实现方式,可以根据不同情况选择不同的实现。

Composite(组合)#

意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
适用性

  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象。
class Equipment {
public:
    virtual ~Equipment();
    const char* Name() {
        return _name;
    }
    virtual Watt Power();
    virtual Currency NetPrice();
    virtual Currency DiscountPrice();
    virtual void Add(Quipment*);
    virtual void Remove(Equipment*);
    virtual Iterator<Equipment*>* CreateIterator();
protected:
    Equipment(const char*);
private:
    const char* _name;
};

//Equipment的子类包括表示磁盘驱动器、集成电路和开关的一些Leaf类
class FloppyDisk : public Equipment {
public:
    FloppyDisk(const char*);
    virtual ~FloppyDisk();
    virtual Watt Power();
    virtual Currency NetPrice();
    virtual Currency DiscountPrice();
};

//CompositeEquipment是包含其他设备的基类,它也是Equipment的子类
class CompositeEquipment : public Equipment {
public:
    virtual ~CompositeEquipment();
    virtual Watt Power();
    virtual Currency NetPrice();
    virtual Currency DiscountPrice();
    virtual void Add(Equipment*);
    virtual void Remove(Equipment*);
    virtual Iterator<Equipment*>* CreateIterator();
protected:
    CompositeEquipment(const char*);
private:
    List<Equipment*> _equipment;
};
//CompositeEquipment为访问和管理子设备定义了一些操作。

Currency CompositeEquipment::NetPrice() {
    Iterator<Equipment*>* i = CreateIterator();
    Curency total = 0;
    for(i->First(); i->IsDone(); i->Next()) {
        total += i->CurrentItem()->NetPrice();
    }
    delete i;
    return total;
}

//将计算机底盘表示为CompositeEquipment的子类
class Chassis : public CompositeEquipment {
public:
    Chassis(const char*);
    virtual ~Chassis();
    virtual Watt Power();
    virtual Currency NetPrice();
    virtual Currency DiscountPrice();
};
//同样我们可以用相似的方法定义其他设备容器,这样我们就得到了组装一台计算机的所有设备
int main()
{
    Cabinet* cabinet = new Cabinet("PC Cabinet");
    Chassis* chassis = new Chassis("PC Chassis");
    Bus* bus = new Bus("MCA Bus");
    bus->Add(new Card("16Mbs Token Ring"));
    chassis->Add(bus);
    chassis->Add(new FloppyDisk("3.5in FloppyDisk"));
    
    cout << "The net price is " << chassis->NetPrice() << endl;
}

相关模式

  • 通常,部件-父类连接用于Responsibility of Chain模式。
  • Decorator模式经常与Composite模式一起使用。当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰必须支持具有Add、Remove和GetChild操作的Component接口。
  • FlyWeight让你共享组件,但不再能引用其父部件。
  • Iterator可用来遍历Composite。
  • Vistor将本来应该分布在Composite和Leaf类中的操作和行为局部化。

Decorator(装饰)#

意图
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
适用性

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤销的职责。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是,类定义被隐藏,或类定义不能用于生成子类。
//假定已经存在了一个Component类
class VisualComponent { 
public:
    VisualComponent();
    virtual void Draw();
    virtual void Resize();
    //...
};

//定义一个子类Decorator,将生成Decorator的子类来获得不同的装饰
class Decorator : public VisualComponet {
public:
    Decorator(VisualComponent*);
    virtual void Draw();
    virtual void Resize();
    //...
private:
    VisualComponent* _component;    //要装饰的对象
};

//对于VisualComponent接口中定义的每一个操作,Decorator类都定义了一个缺省的实现,这一实现将请求转发给_component:
void Decorator::Draw() {
    _component->Draw();
}
void Decorator::Resize() {
    _component->Resize();
}

//Decorator的子类定义了特殊的装饰功能,如BorderDecorator类为它所包含的组件添加了一个边框。
class BorderDecorator : public Decorator {
public:
    BorderDecorator(VisualComponent*, int borderWidth);
    virtual void Draw();
private:
    void DrawBorder(int);
private:
    int _width;
};

void BorderDecorator::Draw() {
    Decorator::Draw();
    DrawBorder(_width);
}
//类似可以实现其他装饰功能
int main()
{
    Window* window = new Window;    //假设Window类有一个SetContents操作SetContents(VisualComponent* contents)    
    TextView* TextView = new TextView;
    //我们想要一个有边界和滚动条的TextView,因此可以在将他放入窗口之前对其进行装饰
    window->SetContents( 
        new BorderDecorator (
            new ScrollDecorator(textView), 1
    ));
}

相关模式

  • Adapter:Decorator模式不同于Adapter模式,因为装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。
  • Composite:可以将装饰视为一个退化的、仅有一个组件的组合。然而,装饰仅给对象添加一些额外的职责——它的目的不在于对象聚集。
  • Strategy:用一个装饰可以改变对象的外表;而Strategy模式使得你可以改变对象的内核。这是改变对象的两种途径。

Facade(外观)#

意图
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂,大多数模式使用时都会产生更多更小的类。这使得子系统更具可复用性,也更容易对子系统进行定制,但也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,可以让它们仅通过Facade进行通信,从而简化了它们之间的依赖关系。

协作

  • 客户程序通过发送请求给Facade的方式与子系统通信,Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。
  • 使用Facade的客户程序不需要直接访问子系统对象。
//在一个编译子系统中使用Facade
//接收字符流并产生一个标识符流,一次产生一个标识符
class Scanner {
public:
    Scanner(istream&);
    virtual ~Scanner();
    virtual Token& Scan();
private:
    istream& _inputStream;
};

//用ProgramNodeBuilder,Parser类由Scanner生成的标识符构建一棵语法分析树
class Parser {
public:
    Parser();
    virtual ~Parser();
    virtual void Parse(Scanner&, ProgramNodeBuilder&);
};

//Parser回调ProgramNodeBuilder逐步建立语法分析树
class ProgramNodeBuilder {
public:
    ProgramNodeBuilder();
    virtual ProgramNode* NewVariable (const char* variableName) const;
    virtual ProgramNode* NewAssignment(ProgramNode* variable, ProgramNode* expression) const;
    virtual ProramNode* NewReturnStatement(ProgramNode* value) const;
    virtual ProgramNode* NewCondition(ProgramNode* condition, ProgramNode* truePart, ProgramNode* falsePart) const;
    //...
    ProgramNode* GetRootNode();
private:
    ProgramNode* _node;
};

//语法分析树由ProgramNode子类(如StatementNode和ExpressionNode等)的实例构成。
//ProgramNode定义了一个接口用于操作程序结点和它的子节点
class ProgramNode {
public:
    // program node manipulation
    virtual void GetSourcePosition(int& line, int& index);
    //...

    //child manipulation
    virtual void Add(ProgramNode*);
    virtual void Remove(ProgramNode*);
    //...

    virtual void Traverse(CodeGenerator&);
protected:
    ProgramNode();
};

//Traverse操作以一个CodeGenerator对象为参数,ProgramNode子类使用这个对象产生机器代码,机器代码格式为BytecodeStream中的ByteCode对象。
//其中的CodeGenerator类是一个访问者
class CodeGenerator {
public:
    virtual void Visit(StatementNode*);
    virtual void Visit(ExpressionNode*);
    //...
protected:
    CodeGenerator(BytecodeStream&);
protected:
    BytecodeStream& _output;
};
//例如CodeGenerator类有两个子类StackMachineCodeGenerator和RISCCodeGenerator,分别为不同的硬件体系结构生成机器代码
// ProgramNode的每个子类在实现Traverse时,对它的ProgramNode子对象调用Traverse。这样一直递归下去。
//例如ExpressionNode像这样定义Traverse:
void ExpressionNode::Traverse(CodeGenerator& cg) {
    cg.Visit(this);
    ListIterator<ProgramNode*> i(_children);
    for(i.First(); !i.IsDone(); i.Next()) {
        i.CurrentItem()->Traverse(cg);
    }
}

//现在引入Compiler类,它是一个Facade,将所有部件集成在一起。
//提供一个简单的接口用于为特定的机器编译源代码并生成可执行代码
class Compiler {
public:
    Compiler();
    virtual void Compile(istream&, BytecodeStream&);
};

void Compiler::Compile(istream& input, BytecodeStream& output) {
    Scanner scanner(input);
    ProgramNodeBuilder builder;
    Parser parser;
    parser.Parse(scanner, builder);
    RISCCodeGenerator generator(output);
    ProgramNode* parseTree = builder.GetRootNode();
    parseTree->Traverse(generator);
}

相关模式

  • Abstract Factory模式可以与Facade模式一起使用来提供一个 接口,这一接口可以来以一种子系统独立的方式创建子系统对象。Abstract Facotry也可以替代Facade模式隐藏那些与平台相关的类。
  • Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通信进行抽象,通常集中不属于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道Facade的存在。
  • 通常来讲,仅需要一个Facade对象,因此Facade对象通常属于Singleton模式。

Flyweight(享元)#

意图
运用共享技术有效地支持大量细粒度的对象。
适用性
Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用Flyweight模式:

  • 一个应用使用了大量的对象。
  • 完全由于使用大量的对象造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,因此对于概念上明显有别的对象,标识检测将返回真值。

协作

  • flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于Concrete-Flyweight对象中,而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。
  • 用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享。
class Glyph {
public:
    virtual ~Glyph();
    virtual void Draw(Window*, GlyphContext&);
    virtual void SetFont(Font*, GlyphContext&);
    virtual Font* GetFont(GlyphContext&);
    virtual void First(GlyphContext&);
    virtual void Next(GlyphContext&);
    virtual bool IsDone(GlyphContext&);
    virtual Glyph* Current(GlyphContext&);
    virtual void Insert(Glyph*, GlyphContext&);
    virtual void Remove(GlyphContext&);
protected:
    Glyph();
};

//存储一个字符代码
class Character : public Glyph {
public:
    Character(char);
    virtual void Draw(Window*, GlyphContext&);
private:
    char _charcode;
};

//为避免给每一个Glyph的字体属性分配存储空间,将该属性外部存储于GlyphContext对象中
//GlyphContext是一个外部状态的存储库,它维持Glyph与字体之间的一种简单映射关系
class GlyphContext {
public:
    GlyphContext();
    virtual ~GlyphContext();
    virtual void Next(int step = 1);
    virtual void Insert(int quantity = 1);
    virtual Font* GetFont();
    virtual void SetFont(Font*, int span = 1);
private:
    int _index;
    BTree* _fonts;
};

const int NCHARCODES = 128;
//GlyhFactory负责创建Glyph并确保对它们进行合理共享。
class GlyphFactory {
public:
    GlyphFactory();
    virtual ~GlyphFacotry();
    virtual Character* CreateCharacter(char);
    virtual Row* CreateRow();
    virtual Column* CreateColumn();
    //...
private:
    Character* _character[NCHARCODES];
};

GlyphFactory::GlyphFactory() {
    for(int i = 0; i < NCHARCODES; ++i) {
        _character[i] = 0;
    }
}

//在字母符号数组中查找一个字符,如果存在,返回相应的Glyph;如果不存在,就创建一个Glyph,将其放入数组,并返回它
Character* GlyphFactory::CreateCharacter(char c) {
    if(!_character[c]) {
        _character[c] = new Character(c);
    }
    return _character[c];
}

相关模式

  • Flyweight模式通常和Composite模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。
  • 通常,最好用flyweight实现State和Strategy对象。

Proxy(代理)#

意图
为其他对象提供一种代理以控制对这个对象的访问。
适用性
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。

  1. 远程代理 为一个对象在不同的地址空间提供局部代表。
  2. 虚代理 根据需要创建开销很大的对象。
  3. 保护代理 控制对原始对象的访问。
  4. 智能引用 取代了简单的指针,它在访问对象时执行一些附加操作。典型用途包括:
    • 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它。
    • 当第一次引用一个持久对象时,将它装入内存。
    • 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
class Image;
extern Image* LoadAnImageFile(const char*); //extern function

class ImagePtr {
public:
    ImagePtr(const char* imageFile);
    virtual ~ImagePtr();

    virtual Image* operator->();
    virtual Image& operator*();
private:
    Image* LoadImage();
private:
    Image* _image;
    const char* _imageFile;
};

ImagePtr::ImagePtr(const char* theImageFile) {
    _imageFile = theImageFile;
    _image = 0;
}

Image* ImagePtr::LoadImage() {
    if(_image == 0) {
        _image = LoadAnImageFile(_imageFile);
    }
    return _image;
}

//重载->操作符和*操作符,使用LoadImage将_image返回给它的调用者
Image* ImagePtr::operator->() {
    return LoadImage();
}

Image& ImagePtr::operator*() {
    return *LoadImage();
}

int main()
{
    ImagePtr  image = ImagePtr("anImageFileName");
    image->Draw(Point(50, 100));    //(image.operator->())->Draw(Point(50, 100))
    /*
    有如下两条作用规则:

(a)【类对象指针->member】如果point是指针,则按照箭头运算符的内置用法去处理:point->member等价于(*point).member
首先解引用该指针,然后从所得的对象中获取指定的成员。如果point所指的类没有名为member的成员,则编译器报错
(b)【类对象->member】如果point是一个重载了operator->() 的类对象,则:point->member等价于point.operator->() ->member
其中,如果operator->()的返回结果是一个指针,则转(a);
如果operator->()的返回结果仍然是一个对象,且该对象本身也重载了operator->(),则重复调用(b),否则编译器报错。
最终,过程要么结束在(a),要么无限递归(b),要么报错。
    */
}

相关模式
Adapter:适配器为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。
Decorator:尽管装饰的实现部分与代理相似,但装饰的目的不一样。装饰为对象添加一个或多个功能,而代理则控制对对象的访问。
代理的实现与装饰的实现类似,但是在相似的程度上有所差别。Protection Proxy的实现可能与装饰的实现差不多。另外,Remote Proxy不包含对实体的直接引用,而只是一个间接引用,如“主机ID,主机上的局部地址”。Virtual Proxy开始的时候使用一个间接引用,例如一个文件名,但最终将获取并使用一个直接引用。

结构型模式总结#

结构型模式看起来似乎都大差不差,原因可能是都是依赖于一个语言机制:单继承和多继承机制用于基于类的模式,而对象组合机制用于对象模式。

Adapter与Bridge#

适配器和桥接模式都是为对象提供一层间接的联系,从而增大了灵活性。但两者是有区别的。
适配器主要是为了解决两个已有接口不兼容的问题;桥接模式是对抽象接口和它的实现部分进行桥接。
两者通常被用在软件生命周期的不同阶段。如果你发现两个不兼容的类必须要同时工作的话,就必须用适配器模式;相反,桥接模式使用时必须要知道:一个抽象将有多个实现部分,并且抽象和实现两者是独立演化的。适配器是在类已经设计好后实施,桥接模式是在设计类之前实施。
另外还有外观模式(facade)可能会与适配器混淆,facade是定义了一个新的接口,但adapter则复用一个原有的接口。

Composite、Decorator与Proxy#

组合模式和装饰模式都是基于递归组合来组织可变数目的对象。不同在于,装饰模式是为了使你在不需要生成子类的情况下给对象添加职责,这避免了静态实现所有功能组合,造成子类急剧增大,浪费资源;组合模式旨在构造类,使多个相关的对象能够以统一的方式处理,而多个对象可以被当作一个对象来处理,它的重点不在于修饰,而是表示。
代理模式和装饰模式相似的地方在于它们都保留了一个指向另一个对象的指针,它们向这个对象发送请求。
不同在于,代理模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需求时,为这个实体提供一个替代者。

行为型模式#

行为型模式涉及算法和对象间职责的分配。行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式。

Chain of Responsibility(职责链)#

意图
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
适用性

  • 有多个对象可以处理一个请求,哪个对象处理该请求运行时自动确定。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可处理一个请求的对象集合应被动态指定。
//打开一个帮助文档,肯定是从点击的对象内容开始显示,如果没有就把请求传递给链上的后继
typedef int Topic;
const Topic NO_HELP_TOPIC = -1;

class HelpHandler {
public:
    HelpHandler(HelpHandler* = nullptr, Topic = NO_HELP_TOPIC);
    virtual bool HasHelp();
    virtual void SetHandler(HelpHandler*, Topic);
    virtual void HandlerHelp();
private:
    HelpHandler* _successor;
    Topic _topic;
};

HelpHandler::HelpHandler (HelpHandler* h, Topic t): _successor(h), _topic(t) { }

bool HelpHandler::HasHelp() {
    return _topic != NO_HELP_TOPIC;
}

void HelpHandler::HandleHelp() {
    if(_successor != 0) {
        _successor->HandleHelp();
    }
}

//所有窗口组件都是Widget的子类,Widget是HelpHandler的子类,这样所有用户界面元素都可以有相关的帮助
class Widget : public HelpHandler {
protected:
    Widget(Widget* parent, Topic t = NO_HELP_TOPIC);
private:
    Widget* _parent;
};

Widget::Widget(Widget* w, Topic t) : HelpHandler(w, t) {
    _parent = w;
}

//按钮是链上的第一个处理者
class Button : public Widget {
public:
    Button(Widget* d, Topic t = NO_HELP_TOPIC);
    virtual void HandleHelp();
};

Button::Button(Widget* h, Topic t) : Widget(h, t) { }

//首先检查自身是否有帮助主题,如果有就显示;如果没有就用HelpHandler中的HandleHelp操作将请求转发给它的后继者。
void HandleHelp() {
    if(HasHelp()) {
        //offer help on the button
    }
    else {
        HelpHandler::HandleHelp();
    }
}

相关模式
职责链常与组合模式一起使用。这种情况下,一个构件的父构件可作为它的后继。

Command(命令)#

意图
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
适用性
当你有如下需求时,可使用Command模式

  • 抽象出待执行的动作以参数化某对象。你可用过程语言中的回调函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command模式是回调机制的一个面向对象的替代品。
  • 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那里实现该请求。
  • 支持取消操作。Command的Execute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“撤销”和“重做”。
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
  • 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。

协作

  • Client创建一个ConcreteCommand对象并指定它的Receiver对象。
  • 某Invoker对象存储该ConcreteCommand对象。
  • 某Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用来取消该命令。
  • ConcreteCommand对象调用它的Reciver的一些操作以执行该请求。

效果

  1. Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
  2. Command是头等的对象。它们可像其他的对象一样被操纵和扩展。
  3. 你可将多个命令装配成一个组合命令。
  4. 增加新的Command很容易,因为这无须改变已有的类。
//实现一个Command类的大致框架,并将定义OpenCommand、PasteCommand和MacroCommand
//首先是抽象的Command类
class Command {
    virtual ~Command();
    virtual void Execute() = 0;
protected:
    Command();
};

//打开一个名字由用户指定的文档
class OpenCommand : public Command {
public:
    OpenCommand(Application*);
    virtual void Execute();
protected:
    virtual const char* AskUser();
private:
    Application* _application;
    char* _response;
};

OpenCommand::OpenCommand(Application* a) {
    _application = a;
}

void OpenCommand::Execute() {
    const char* name = AskUser();
    if(name != 0) {
        Document* document = new Document(name);
        _application->Add(document);
        document->Open();
    }
}

//PasteCommand需要一个Document对象作为其接收者
class PasteCommand : public Command {
public:
    PasteCommand(Document*);
    virtual void Execute();
private:
    Document* _document;
};

PasteCommand::PasteCommand(Document* doc) {
    _document = doc;
}

void PasteCommand::Execute() {
    _document->Paste();
}

//对于不能撤销和不需要参数的简单命令,可以用一个类模板来参数化该命令的接收者。
template <class Receiver>
class SimpleCommand : public Command {
public:
    typedef void (Receiver::* Action)();    //函数指针
    SimpleCommand(Receiver* r, Action a): _receiver(r), _action(a) { }
    virtual void Execute();
private:
    Action _action;
    Receiver* _receiver;
};

template <class Receiver>
void SimpleCommand<Receiver>::Execute() {
    (_receiver->*_action)();
}

//创建一个调用MyClass类的一个实例上的Action的Command对象
MyClass* receiver = new MyClass;
Command* aCommand = new SimpleCommand<MyClass>(receiver, &MyClass::Action);
aCommand->Execute();
//上述方案仅能用于简单命令。更复杂的命令不仅要维护它们的接收者,还要登记参数,有时还要保存用于取消操作的状态。
//这里就需要定义一个Command的子类
//MacroCommand管理一个子命令序列,它提供了增加和删除子命令的操作。这里不需要显式的接收者,因为这些子命令已经定义了它们各自的接收者
class MacroCommand : public Command {
public:
    MacroCommand();
    virtual void Add(Command*);
    virtual void Remove(Command*);
    virtual void Execute();
private:
    List<Command*>* _cmds;
};

void MacroCommand::Execute() {
    List<Command*>* i(_cmds);
    for(i.First(); !i.IsDone(); i.Next()) {
        Command* c = i.CurrentItem();
        c->Execute();
    }
}

void MacroCommand::Add(Command* c) {
    _cmds->Append(c);
}

void MacroCommand::Remove(Command* c) {
    _cmds->Remove(c);
}

相关模式
Composite可用来实现宏命令。
Memento可用来保持某个状态,命令用这一状态来取消它的效果。
在被放入历史列表前必须被拷贝的命令起到一种原型的作用。

思考
没太懂这个模式,待补充思考

Interpreter(解释器)#

意图
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

适用性
当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

  • 文法简单。对于复杂的文法,文法的类曾次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无须构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
  • 效率不是关键问题。最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器也可用解释器模式实现,该模式仍是可用的。
//对布尔表达式进行操作和求值的系统
class BooleanExp {
public:
    BooleanExp();
    virtual ~BooleanExp();
    virtual bool Evaluate(Context&) = 0;
    virtual BooleanExp* Replace(const char*, BooleanExp&) = 0;
    virtual BooleanExp* Copy() const = 0;
};

//Context类定义从变量到布尔值的一个映射
class Context {
public:
    bool Lookup(const char*) const;
    void Assign(VariableExp*, bool);
};

class VariableExp : public BooleanExp {
public:
    VariableExp(const char*);
    virtual ~VariableExp();
    virtual bool Evaluate(Context&);
    virtual BooleanExp* Replace(const char*, BooleanExp&);
    virtual BooleanExp* Copy();
private:
    char* _name;
};

VariableExp::VariableExp(const char* name) {
    _name = strdup(name);
}

bool VariableExp::Evaluate(Context& aContext) {
    return aContext.Lookup(_name);
}

BooleanExp* Variable::Copy() const {
    return new VariableExp(_name);
}

BooleanExp* Variable::Replace(const char* name, BooleanExp& exp) {
    if(strcmp(name, _name) == 0) {
        return exp.Copy();
    }
    else {
        return new VariableExp(_name);
    }
}

class AndExp : public BooleanExp {
public:
    AndExp(BooleanExp*, BooleanExp*);
    virtual ~AndExp();
    virtual bool Evaluate(Context&);
    virtual BooleanExp* Replace(const char*, BooleanExp&);
    virtual BooleanExp* Copy() const;
private:
    BooleanExp* _operand1;
    BooleanExp* _operand2;
};

AndExp::AndExp(BooleanExp* op1, BooleanExp* op2) {
    _operand1 = op1;
    _operand2 = op2;
}

bool AndExp::Evaluate(Context& aContext) {
    return _operand1->Evaluate(aContext) && operand2->Evaluate(aContext);
}

BooleanExp* AndExp::Copy() const {
    return new AndExp(_operand1->Copy(), _operand2->Copy());
}

BooleanExp* AndExp::Replace(const char* name, BooleanExp& exp) {
    return new AndExp(_operand1->Replace(name, exp), _operand2->Replace(name, exp));
}

//现在可以定义布尔表达式 (true and x) or (y and (not x))
int main()
{
    BooleanExp* expression;
    Context context;
    VariableExp* x = new VariableExp("X");
    VariableExp* x = new VariableExp("Y");
    expression = new OrExp(new AndExp(new  Constant(true), x), new AndExp(y, new NotExp(x)));
    context.Assign(x, false);
    context.Assign(y, true);
    
    //通过对x和y赋值,可以求得该表达式值为true
    bool result = expression->Evaluate(context);
    //要对其他赋值情况求该表达式值,仅需改变上下文对象。
    //用一个新的表达式替换y,
    VariableExp* z = new VariableExp("Z");
    NotExp not_z(z);
    BooleanExp* replacement = expression->Replace("Y", not_z);
    context.Assin(z, true);
    result = replacement->Evaluate(context);
}

相关模式
Composite:抽象语法树是一个组合模式的实例。
Flyweight:说明了如何在抽象语法树中共享终结符。
Iterator:解释器可用一个迭代器遍历该结构。
Visitor:可用来在一个类中维护抽象语法树中各结点的行为。

Iterator(迭代器)#

意图
提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示。
适用性

  • 访问一个聚合对象的内部而无须暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)。
//一个从前往后遍历list的迭代器实现

template <typename Item>
class List {
public:
    List(long size = DEFAULT_LIST_CAPACITY);
    long Count() const;
    Item& Get(long index) const;
};

template <typename Item>
class Iterator {
public:
    virtual void First() = 0;
    virtual void Next() = 0;
    virtual bool IsDone() const = 0;
    virtual Item CurrentItem() const = 0;
protected:
    Iterator();
};

template <typename Item>
class ListIterator : public Iterator<Item> {
public:
    ListIterator(const List<Item>* aList);
    virtual void First();
    virtual void Next();
    virtual bool IsDone() const;
    virtual Item CurrentItem() const;
private:
    const List<Item>* _list;
    long _current;
};

template <typename Item>
ListIterator<Item>::ListIterator(const List<Item>* aList) : _list(aList), _current(0) { }

template <typename Item>
void ListIterator<Item>::First() {
    _current = 0;
}

template <typename Item>
void ListIterator<Item>::Next() {
    _current++;
}

template <typename Item>
bool ListIterator<Item>::IsDone() const {
    return _current >= _list->Count();
}

template <typename Item>
Item ListIterator<Item>::CurrentItem() const {
    if(IsDone()) {
        throw IteratorOutOfBounds;
    }
    return _list->Get(_current);
}

//使用迭代器
void PrintEmployees(Iterator<Employee*>& i) {
    for(i.First(); !i.IsDone(); i.Next()) {
        i.CurrentItem()->Print();
    }
}

相关模式
Composite:迭代器常被应用到像组合这样的递归结构上。
Factory Method:多态迭代器靠Factory Method来实例化适当的迭代器子类。
Memento:常与迭代器模式一起使用。迭代器可使用memento来捕获一个迭代的状态。迭代器在其内部存储memento。

Mediator(中介者)#

意图
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间地交互。
适用性

  • 一组对象以定义良好但复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解。
  • 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
  • 想定制一个分布在多个类中的行为,而又不想生成太多子类。
class DialogDirector {
public:
    virtual ~DialogDirector();
    virtual void ShowDialog();
    virtual void WidgetChanged(Widget*) = 0; 
protected:
    DialogDirector();
    virtual void CreateWidgets() = 0; 
};

class Widget {
public:
    Widget(DialogDirector*);
    virtual void Changed();
    virtual void HandleMouse(MouseEvent& event);
private:
    DialogDirector* _director;
};

void Widget::Changed() {
    _director->WidgetChanged(this);
}

class ListBox : public Widget {
public:
    ListBox(DialogDirector*);
    virtual void SetList(List<char*>* listItems);
    virtual void HandleMouse(MouseEvent& event);
};

class EntryField : public Widget {
public:
    EntryField(DialogDirector*);
    virtual void SetText(const char* text);
    virtual const char* GetText();
    virtual void HandleMouse(MouseEvent& event);
};

class Button : public Widget {
public:
    Button(DialogDirector*);
    virtual void SetText(const char* text);
    virtual void HandleMouse(MouseEvent& event);
};

void Button::HandleMouse(MouseEvent& event) {
    Changed();
}

//FontDialogDirector在对话框中的窗口组件间进行中介
class FontDialogDirector : public DialogDirector {
public:
    FontDialogDirector();
    virtual ~FontDialogDirector();
    virtual void WidgetChanged(Widget*);
protected:
    virtual void CreateWidgets();
private:
    Button* _ok;
    Button* _cancel;
    ListBox* _fontList;
    EntryField* _fontName;
};

//重定义CreateWidgets以创建窗口组件并初始化它们的引用
void FontDialogDirector::CreateWidgets() {
    _ok = new Button(this);
    _cancel = new Button(this);
    _fontList = new ListBox(this);
    _fontName = new EntryField(this);
    //...
}

//WidgetChanged保证窗口组件正确地协同工作
void FontDialogDirector::WidgetChanged(Widget* theChangedWidget) {
    if(theChangedWidget == _fontList) {
        _fontName->SetText(_fontList->GetSelection());
    }
    else if(theChangedWidget == _ok) {
        //...
    }
    else if (theChangedWidget == _cancel) {
        //...
    }
}

相关模式

  • Facade与中介者的不同之处在于,它是对一个对象子系统进行抽象,从而提供了一个更为方便的接口。它的协议是单向的,即Facade对象对这个子系统类提出请求,但反之则不行。相反,Mediator提供了各Colleague对象不支持或不能支持的协作行为,而且协议是多向的。
  • Colleague可使用Observer模式与Mediator通信。

Memento(备忘录)#

意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
适用性

  • 必须保存一个对象在某个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
  • 如果一个接口让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
class Graphic;
class MoveCommand {
public:
    MoveCommand(Graphic* target, const Point& delta);
    void Execute();
    void Unexecute();
private:
    ConstraintSolverMemento* _state;
    Point _delta;
    Graphic* _target;
};

class ConstraintSolver {
public:
    static ConstraintSolver* Instance();
    void Solve();
    void AddConstraint(Graphic* startConnection, Graphic* endConnection);
    ConstraintSolverMemento* CreateMmento();
    void SetMemento(ConstraintSolverMemento*);
private:

};

class ConstraintSolverMemento {
public:
    virtual ~ConstraintSolverMemento();
private:
    friend class ConstraintSolver;
    ConstraintSolverMemento();
};

void MoveCommand::Execute() {
    ConstraintSolver* solver = ConstraintSolver::Instance();
    _state = solver->CreateMemento();   //创建一个备忘录
    _target->Move(_delta);
    solver->Solve();
}

void MoveCommand::Unexecute() {
    ConstraintSolver* solver = ConstraintSolver::Instance();
    _target->Move(-_delta);
    solver->SetMemento(_state); //重新存储solver状态
    solver->Solve();
}

相关模式

  • Command:命令可使用备忘录来为可撤销的操作维护状态。
  • Iterator:如前所示,备忘录可用于迭代。

Observer(观察者)#

意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
适用性

  • 一个对象模型有两个方面,其中一个方面依赖于另一方面。将这两者封装在独立的对象中,以使他们可以各自独立地改变和复用。
  • 对一个对象的改变需要改变其他对象,而不知道具体有多少对象有待改变。
  • 一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的。
class Subject;

//定义Observer接口
class Observer {
public:
    virtual ~Observer();
    virtual void Update(Subject* theChangedSubject) = 0;
protected:
    Observer();
};

//定义一个Subject接口
class Subject {
public:
    virtual ~Subject();
    virtual void Attach(Observer*);
    virtual void Detach(Observer*);
    virtual void Notify();
protected:
    Subject();
private:
    List<Observer*>* _observers;    //观察者列表
};

//定义一个ClockTimer存储和维护一天时间,每秒通知一次它的观察者
class ClockTimer : public Subject {
public:
    ClockTimer();
    virtual int GetHour();
    virtual int GetMinute();
    virtual int GetSecond();
    void Tick();
};

void ClockTimer::Tick() {
    //...
    Notify();
}

//定义一个DigitalClock显示时间
class DigitalClock : public Widget, public Observer {
public:
    DigitalClock(ClockTimer*);
    virtual ~DigitalClock();
    virtual void Update(Subject*) override;
    virtual void Draw() override;
private:
    ClockTimer* _subject;
};

DigitalClock::DigitalClock(ClockTimer* s) {
    _subject = s;
    _subject->Attach(this);
}

DigitalClock::~DigitalClock() {
    _subject->Detach(this);
}

void DigitalClock::Update(Subject* theChangedSubject) {
    if(theChangedSubject == _subject) {
        Draw();
    }
}

void DigitalClock::Draw() {
    int hour = _subject->GetHour();
    int minute = _subject->GetMinute();
    //draw
}

相关模式
Mediator:通过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者。
Singleton:ChangeManager可使用Singleton模式来保证它是唯一的并且是可全局访问的。

State(状态)#

意图
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
适用性

  • 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件状态。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
class TCPOctetStream;
class TCPState;
class TCPConnection {
public:
    TCPConnection();
    void ActiveOpen();
    void PassiveOpen();
    void Close();
    void Send();
    void Acknowledge();
    void Synchronize();
    void ProcrssOctet(TCPOctetStream*);
private:
    friend class TCPState;
    void ChangeState(TCPState*);
private:
    TCPState* _state;
};

class TCPState {
public:
    virtual void Transmit(TCPConnection*, TCPOctetStream*);
    virtual void ActiveOpen(TCPConnection*);
    virtual void PassiveOpen(TCPConnection*);
    virtual void Close(TCPConnection*);
    virtual void Synchronize(TCPConnection*);
    virtual void Acknowledge(TCPConnection*);
    virtual void Send(TCPConnection*);
protected:
    void ChangeState(TCPConnection*, TCPState*);
};
/*
TCPConnection将所有与状态有关的请求委托给它的TCPState实例_state。
TCPConnection还提供一个操作用于将这个变量设为一个新的TCPState。
*/
TCPConnection::TCPConnection() {
    _state = TCPClosed::Instance();
}

void TCPConnection::ChangeState(TCPState* s) {
    _state = s;
}

void TCPConnection::ActiveOpen() {
    _state->ActiveOpen(this);
}

void TCPConnection::PassiveOpen() {
    _state->PassiveOpen(this);
}

void TCPConnection::Close() {
    _state->Close(this);
}

void TCPConnection::Ackonwledge() {
    _state->Acknowledge(this);
}

void TCPConnection::Synchronize() {
    _state->Synchronize(this);
}

//TCPState为所有委托给它的请求实现缺省的行为。它也可以调用ChangeState来改变TCPConnection的状态
void TCPState::Transmit(TCPConnection*, TCPOctetStream*) { }
void TCPState::ActiveOpen(TCPConnection*) { }
void TCPState::PassiveOpen(TCPConnection*) { } 
void TCPState::Close(TCPConnection*) { }
void TCPState::Synchronize(TCPConnection*) { }
void TCPState::ChangeState(TCPConnection* t, TCPState* s) {
    t->ChangeState(s);
}

//TCPState的子类实现与状态有关的行为。例如已建立、监听、已关闭等。对于每一个状态,都有一个TCPState子类
class TCPEstablished : public TCPState {
public:
    static TCPState* Instance();
    virtual void Transmit(TCPConnection*, TCPOctetStream*);
    virtual void Close(TCPConnection*);
};

class TCPListen : public TCPState {
public:
    static TCPState* Instance();
    virtual void Send(TCPConnection*);
    //...
};

class TCPClosed : public TCPState {
public:
    static TCPState* Instance();
    virtual void ActiveOpen(TCPConnection*);
    virtual void PassiveOpen(TCPConnection*);
    //...
};

//TCP的子类没有局部状态,因此它们可以共享,并且每个子类只需要一个实例
//在完成与状态相关的工作后,这些操作调用ChangeState来改变TCPConnection的状态
void TCPClosed::ActiveOpen(TCPConnection* t) {
    //发送SYN,接收SYN,ACK等
    ChangeState(t, TCPEstablished::Instance());
}

void TCPClosed::PassiveOpen(TCPConnection* t) {
    ChangeState(t, TCPListen::Instance());
}

void TCPEstablished::Close(TCPConnection* t) {
    //发送FIN,接收ACK和FIN
    ChangeState(t, TCPListen::Instance())
}

void TCPEstablished::Transmit(TCPConnection* t, TCPOctetStream* o) {
    t->ProcessOctet(o);
}

void TCPListen::Send(TCPConnection* t) {
    //发送SYN,接收SYN,ACK
    ChangeState(t, TCPEstablished::Instance());
}

//TCPConnection本身对TCP连接协议一无所知,是由TCPState子类来定义TCP中的每一个状态转换和动作

相关模式
Flyweight解释了何时以及怎样共享状态对象。
状态对象通常是Singleton。

Strategy(策略)#

意图
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
适用性

  • 许多相关的类仅仅是行为有异,“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。
  • 算法使得客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
//Composition使用一个封装了某种换行策略的Compositor子类实例将Component对象进行编排成行
class Composition {
public:
    Composition(Compositor*);
    void Repair();
private:
    Compositor* _compositor;
    Component* _components;
    int _componentCount;
    int _lineWidth;
    int* _lineBreaks;
    int _lineCount;
};

//当需要一个新的布局时,Composition让它的Compositor决定在何处换行
//Compositor接口使得Compositon可传递给Compositor所有他需要的信息,给出一个“将数据传给Strategy”的例子
class Compositor {
public:
    virtual int Compose(Coord natural[], Coord streth[], Coord shrink[], int componentCount, int lineWidth, int breaks[]) = 0;
protected:
    Compositor();
};

//Compositor在其Repair操作中调用它的Compositor。
void Composition::Repair() {
    Coord* natural;
    Coord* stretchability;
    Coord* shrinkability;
    int componentCount;
    int* breaks;
    //...
    int breakCount;
    breakCount = _compositor->Compose(natural, strtchability, shrinkability, componentCount, _lineWidth, breaks);
    //...
}

//SimpleCompositor一次检查一行Component,并决定在哪里换行
class SimpleCompositor : public Compositor {
public:
    SimpleCompositor();
    virtual int Compose(Coord natural[], Coord streth[], Coord shrink[], int componentCount, int lineWidth, int breaks[]);
};

//TeXCompositor每次检查一个段落,并同时考虑各Component的大小和伸展性
class TeXCompositor : public Compositor {
public:
    TeXCompositor();
    virtual int Compose(Coord natural[], Coord streth[], Coord shrink[], int componentCount, int lineWidth, int breaks[]);
};

//实例化Composition时需要把想要使用的Compositor传递给它
Composition* quick = new Composition(new SimpleCompositor);
Composition* slick = new Composition(new TeXCompositor);

相关模式
Flyweight:Strategy对象经常是很好的轻量级对象。

Template Method(模板方法)#

意图
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
适用性

  • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应该被提取出来并集中到一个公共父类中以避免代码重复。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用新的操作的模板方法来替换不同的代码。
  • 控制子类扩展。模板方法只在特定调用钩子操作,这样就只允许在这些点进行扩展。
//Display在DoDisplay前调用SetFocus以设定绘图状态,DisPlay此后调用ReSetFocus以释放绘图状态
//DoDisplay实施真正的绘图功能
void View::Display() {
    SetFocus();
    DoDisplay();
    ResetFocus();
}

//View本身的DoDisplay什么也不做
void View::DoDisplay() { }

//子类重定义DoDisplay以增加特定绘图行为
void MyView::DoDisplay() {
    //渲染视图内容
    //...
}

相关模式
Facotry Method常被模板方法调用。
Strategy:模板方法使用继承来改变算法的一部分,Strategy使用委托来改变整个算法。

Visitor(访问者)#

意图
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
适用性

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象机构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作比较好。
class Equipment {
public:
    virtual ~Equipment();
    const char* Name() {return _name;}
    virtual Watt Power();
    virtual Currency NetPrice();
    virtual Currency DiscountPrice();
    virtual void Accept(EquipmentVisitor&);
protected:
    Equipment(const char*);
private:
    const char* _name;
};

class EquipmentVisitor {
public:
    virtual ~EquipmentVisitor();
    virtual void VisitFloppyDisk(FloppyDisk*);
    virtual void VisitCard(Card*);
    virtual void VisitChassis(Chassis*);
    virtual void VisitBus(Bus*);
    //...
protected:
    EquipmentVisitor();
};

//Equipment的子类以基本相同的方式定义Accept:调用EquipmentVisitor中对应于接收Accept请求的类的操作
void FloppyDisk::Accpet(EquipmentVisitor& visitor) {
    visitor.VisitFloppyDisk(this);
}

void Chassis::Accept(EquipmentVisitor& visitor) {
    for(ListIterator<Equipment*> i(_parts); !i.IsDone(); i.Next()) {
        i.CurrentItem()->Accept(visitor);
    }
    visitor.VisitChassis(this);
}

//EquipmentVisitor的子类在设备结构上定义了特定的算法
class PricingVisitor : public EquipmentVisitor {
public:
    PricingVisitor();
    Currency& GetTotalPrice();
    virtual void VisitFloppyDisk(FloppyDisk*);
    virtual void VisitCard(Card*);
    virtual void VisitChassis(Chassis*);
    virtual void VisitBus(Bus*); 
    //...
private:
    Currency _total;
};

void PricingVisitor::VisitFloppyDisk(FloppyDisk* e) {
    _total += e->NetPrice();
}

void PricingVisitor::VisitChassis(Chassis* e) {
    _total += e->DiscountPrice();
}

//可以这样定义一个存货清单的类
class InventoryVisitor : public EquipmentVisitor {
public:
    InventoryVisitor();
    Inventory& GetInventory();
    virtual void VisitFloppyDisk(FloppyDisk*);
    virtual void VisitCard(Card*);
    virtual void VisitChassis(Chassis*);
    virtual void VisitBus(Bus*); 
    //...
private:
    Inventory _inventory;
};

void InventoryVisitor::VisitFloppyDisk(FloppyDisk* e) {
    _inventory.Accumulate(e);
}

void InventoryVisitor::VisitChassis(Chassis* e) {
    _inventory.Accumulate(e);
}

//使用InventoryVisitor
Equipment* component;
InventoryVisitor visitor;
component->Accept(visitor);
cout << "Inventory " << component->Name() << visitor.GetInventory();

相关模式
Composite:访问者可以用于一个由Composite模式定义的对象结构进行操作。
Interpreter:访问者可以用于解释。

行为型模式总结#

封装变化#

当一个程序某方面的特征经常发生变化时,这些模式就定义一个封装这方面的对象。这样当该程序的其他部分依赖于这方面时,它们都可以与此对象协作。

  • 一个Strategy对象封装了一个算法。
  • 一个State对象封装了一个与状态相关的行为。
  • 一个Mediator对象封装了对象间的协议。
  • 一个Iterator对象封装了访问和遍历一个聚集对象中的各个构件的方法。

但不是所有的对象行为模式都像这样分割功能,例如,职责链模式可以处理任意数量的对象(一条链上),而所有的对象可能已经存在于系统中了。
职责链说明了行为模式间的另一个不同点:并非所有的行为模式都定义类之间的静态通讯关系。职责链提供在数目可变的对象间进行通信的机制。其他模式涉及一些作为参数传递的对象。

对象作为参数#

一些模式引入总是被用作参数的对象,例如访问者模式,一个访问者对象是一个多态的Accept操作的参数,这个操作作用于该访问者对象访问的对象。
其他模式定义一些可作为令牌到处传递的对象,这些对象将在稍后被调用,如命令模式和备忘录模式。

通信应该被封装还是被分布#

中介者模式和观察者模式是相互竞争的模式。它们之间的差别在于,观察者模式通过引入Observer和Subject对象来分布通信,而Mediator对象则封装了其他对象间的通信。

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718208

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示