设计模式--C++ 学习笔记

单例模式

一、定义

在一个项目中,全局范围内,某个类的实例有且只有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式叫做单例模式。单例模式的典型应用就是任务队列。

涉及一个类多对象的操作:
  • 构造函数:创建一个新的对象
  • 拷贝构造函数:根据已有对象拷贝出一个新的对象
  • 拷贝赋值操作符重载函数:两个对象之间的赋值
实现单例模式的方法
  • 构造函数私私有化
    • 在类内部只调用一次,可控;
    • 由于使用者在类外部不能使用构造函数,所以在类内创建这个唯一的对象必须是静态的,这样就可以通过类名访问了,为了不破坏类的封装,我们会把这个静态对象的访问权限设为私有。
    • 在类中只有它的静态成员函数才能访问其静态成员变量,所以可以给这个单例类提供一个静态函数用于得到这个静态的单例对象。
  • 拷贝构造函数私有化或禁用(使用=delete)
  • 拷贝赋值操作符重载函数私有化或禁用

二、单例模式代码

// 定义一个单例模式的类
class Singleton {
public:
    // = delete 代表函数禁用,也可以将其访问权限设置为私有
    Singleton(const Singleton& obj) = delete;
    Singleton& operator=(const Singleton& obj) = delete;
    static Singleton* getInstance();
private:
    Singleton() = default;
    static Singleton* m_obj;
};

两种处理模式 – 只考虑单例

饿汉式

在类加载的时候立刻进行实例化,这样就得到了一个唯一的可用对象

// 饿汉模式
class TaskQueue {
pubilc:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        return m_taskQ;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
};
// 静态成员函数初始化放在类外部处理
// 定义这个单例类的时候,就把这个静态的单例对象创建出来了
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

int main() {
    // 当使用者使用时,只需要调用getInstance()即可,对象已经被创建好了
    TaskQueue* obj = TaskQueue::getInstance();
}
懒汉式

在类加载时不去创建这个唯一的实例,而是在使用时进行实例化

// 懒汉模式
class TaskQueue {
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        if(m_taskQ == nullptr) {
            m_taskQ = new TaskQueue;
        }
        return m_taskQ;
    }
privtate:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_task() = nullptr;

懒汉 – 线程安全

饿汉模式没有线程安全问题(因为在这个模式下访问单例对象时该对象已经被创建好了)
使用互斥锁解决懒汉模式的线程安全问题:

class TaskQueue
{
public:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance()
    {
        if (m_taskQ == nullptr)
        {
            m_mutex.lock();
            if (m_taskQ == nullptr)
            {
                m_taskQ = new TaskQueue;
            }
            m_mutex.unlock();
        }
        return m_taskQ;
    }
private:
    TaskQueue() = default;
    static TaskQueue* m_taskQ;
    static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;
原子变量解决线程安全问题

C++11中引入了原子变量atomic,可以实现一种更安全的懒汉模式的单例

class TaskQueue {
public:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        TaskQueue* queue = m_taskQ.load();
        // 使用load()方法来加载单例对象
        if(queue == nullptr) {
            lock_guard<mutex> locker(m_mutex);
            // m_mutex.lock()
            queue = m_taskQ.load();
            if(queue == nullptr) {
                queue = new TaskQueue;
                m_taskQ.store(queue);
            }
            // m_mutex.unlock()
        }
        return queue;
    }
private:
    TaskQueue() = default;
    static atomic<TaskQueue*> m_taskQ;
    static mutex m_mutex;
};
atomic<TaskQueue*> TaskQueue::m_taskQ;
mutex TaskQueue::m_mutex;

int main() {
    TaskQueue* queue = TaskQueue::getInstance();
    return 0;
}

atomic类是C++11中引入的一个模板类,用于实现无锁编程中的原子操作。
原子操作是指不会被线程调度机制打断的操作,即该操作一旦开始,就一直运行到完成,中间不会有任何线程切换。

静态局部对象解决线程安全问题

在C++11中规定:如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。

class TaskQueue {
pubulic:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        static TaskQueue taskQ;
        return &taskQ;
    }
private:
    TaskQueue() = default;
};

int main() {
    TaskQueue* queue = TaskQueue::getInstance();
    return 0;
}

三、实战:写一个任务队列

属性:

  • 存储任务的容器(可以使用STL的队列)
  • 互斥锁
    方法:
  • 任务队列中任务是否为空
  • 添加一个任务
  • 删除一个任务
  • 取出一个任务
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
using namespace std

class TaskQueue {
pubulic:
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance() {
        return &m_obj;
    }
    // 队列是否为空
    bool isEmpty() {
        lock_guard<mutex> locker(m_mutex);
        bool flag = m_taskQ.empty();
        return flag;
    }
    // 添加任务
    void addTask(int data) {
        lock_guard<mutex> locker(m_mutex);
        m_taskQ.pus(data);
    }
    // 取出任务
    int takeTask() {
        lock_guard<mutex> locker(m_mutex);
        if(!m_taskQ.empty()) {
            return m_maskQ.front();
        }
        return -1;
    }
    // 删除任务
    bool popTask() {
        lock_guard<mutex> locker(m_mutex);
        if(!taskQ.empty()) {
            m_taskQ.pop();
            return true;
        }
        return false;
    }
private:
    TaskQueue() = default;
    static TaskQueue m_obj;
    queue<int> m_taskQ;
    mutex m_mutex;
};
TaskQueue TaskQueue::m_obj;

int main() {
    thread t1( []() {
        TaskQueue* taskQ = TaskQueue::getInstance();
        for(int i = 0; i < 100; ++i) {
            taskQ->addTask(i + 100);
            cout << "++push task: " << i + 100 << ", threadID: " << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::milliseconds(500));
        }
    });
    thread t2( [] () {
       TaskQueue* taskQ = TaskQueue::getInstance();
       this_thread::sleep_for(chrono::milliseconds(100));
       while(!taskQ->isEmpty()) {
           int data = taskQ->takeTask();
           cout << "---take task: " << data << ", threadID: " << this_thread::get_id() << endl;
           taskQ->popTask();
           this_thread::sleep_for(chrono::second(1));
       }
    });
    t1.join();
    t2.join();
}

说明:
· 正常情况下任务队列中的任务应该是一个函数指针,此处简化成整型数组;
· 任务队列中的互斥锁保护的是单例对象中的数据,和创建单例对象保证的线程安全是两回事;
· lock_guard是C++11中新增的一个类模版,可以简化互斥锁。使用了RAII技术;

:::info
· main函数中使用了chrono库,用于处理日期和时间。主要包括时间间隔duration、时钟clock、时间点time point三种类型的类;
:::

· join() – 主要作用是阻塞调用它的线程(通常是主线程),直到被调用的线程(在这个例子中是 t1 和 t2)完成其执行。这确保了程序的同步性,即主线程不会在被调用的线程完成之前继续执行下去。


工厂模式(简单工厂、工厂模式、抽象工厂)

工厂模式一共有三种:简单工厂模式、工厂模式、抽象工厂模式。
目的:实现类与类之间的解耦合,这样我们在创建对象的时候就变成拿来主义,使程序更加便于维护。

简单工厂模式

创建对象时,需要提供一个工厂类,专门用于生产需要的对象,这样关于对象的创建操作就被剥离出去了。

步骤:

  • 创建一个新的类,可以将这个类称为工厂类。对于简单工厂模式来说,需要的工厂类只有一个。
  • 在这个工厂类中添加一个公共的成员函数,通过这个函数来创建我们需要的对象,关于这个函数一般称为工厂函数。
  • 关于使用,首先创建一个工厂类对象,然后通过这个对象调用工厂函数,这样就可以生产出一个指定类型的实例对象了。
产品

产品:人造恶魔果实SMILE – 吃下失败品没能成功获得果实能力的人会被剥夺除了笑以外的一切表情,所以人造恶魔果实也被称为smile。

实现SMILE工厂的恶魔果实的三种:

// 人造恶魔果实——绵羊形态
class SheepSmile {
public:
    void transform() {
        cout << "SheepMan" << endl;
    }
    void ability() {
        cout << "SSSSSheep" << endl;
    }
};
// 人造恶魔果实——狮子形态
class LionSmile {
public:
    void transform() {
        cout << "LionMan" << endl;
    }
    void ability() {
        cout << "LLLLLion" << endl;
    }
};
// 人造恶魔果实——蝙蝠形态
class BatSmile {
public:
    void transform() {
        cout << "BatMan!" << endl;
    }
    void ability() {
        cout << "BBBBBat" << endl;
    }
};

不论吃了哪种果实,获得相应能力后做到的事情大体相同(transform(),ability())
生产恶魔果实省略(构造函数、析构函数)

生产
原生态

先创建一个工厂类,然后再给这个工厂类添加一个工厂函数

enum class Type:
    char{SHEEP, LION, BAT};
// 恶魔果实工厂类
class SmileFactory {
public:
    enum class Type:char{SHEEP, LION, BAT};
    SmileFactory() {}
    ~SmileFactory() {}
    void* creatSmile(Type type) {
        void* ptr = nullptr;
        switch(type) {
            case Type::SHEEP:
                ptr = new SheepSmile;
                break;
            case Type::LION:
                ptr = new LionSmile;
                break;
            case Type::BAT:
                ptr = new BatSmile;
                break;
            default:
                break;
        }
        return ptr;
    }
};

int main() {
    SmileFactory* factory = new SmileFactory;
    BatSmile* batObj = (BatSmile*)factory->creatSmile(Type::BAT);
    return 0;
}
  • 关于恶魔果实的类型,上面的类中使用了强类型枚举(C++11新特性),增强类代码的可读性,并将枚举元素设置为char类型,节省了内存。
  • 函数createSmile(Type type)的返回值是void*类型,这样处理主要是因为每个case语句创建的对象类型不同,为了实现兼容,故这样处理。
  • 得到函数createSmile(Type type)的返回值后,还需要将其转换成实际的类型,这样处理比较繁琐。
加强版

使用多态

多态的使用需要满足三个条件:

  1. 类和类之间有继承关系;
  2. 父类中有虚函数,并且在子类中重写这些虚函数;
  3. 使用父类指针或引用指向子类对象。
AbstractSmile
+transform()
+ability()
+AbstractSmile()
SheepSmile
+transform() : void
+ability() : void
LionSmile
+transform() : void
+ability() : void
BatSmile
+transform() : void
+ability() : void
SmileFactory
+createSmile(Type type)

代码如下:

#include <iostream>
using namespace std;

class AbstractSmile {
public:
    virtual void transform() {}
    virtual void ability() {}
    virtual ~AbstractSmile() {}
};
class SheepSmilee : public AbstactSmile {
public:
    void transform() override {
        cout << "SheepMan" << endl;
    }
    void ability() override {
        cout << "SSSSSheep" << endl;
    }
};
class LionSmilee : public AbstactSmile {
public:
    void transform() override {
        cout << "LionMan" << endl;
    }
    void ability() override {
        cout << "LLLLLion" << endl;
    }
};
class BatSmilee : public AbstactSmile {
public:
    void transform() override {
        cout << "BatMan" << endl;
    }
    void ability() override {
        cout << "BBBBBat" << endl;
    }
};

// 恶魔果实工厂类
enum class Type:char{SHEEP, LION, BAT};
class SmileFactory {
public:
    SmileFactory() {}
    ~SmileFactory() {}
    AbstractSmile* createSmile(Type type) {
        AbstractSmile* ptr = nullptr;
        switch(type){
        case Type::SHEEP:
            ptr = new SheepSmile;
            break;
        case Type::LION:
            ptr = new LionSmile;
            break;
        case Type::BAT:
            ptr = new BatSmile;
            break;
        default:
            break;
        }
        return ptr;
    }
};

int main() {
    SmileFactory* factory = new SmileFactory;
    AbstractSmile* obj = factory->createSmile(Type::BAT)
    obj->transform();
    obj->ability();
    return 0;
}
  • 由于人造恶魔果实类有继承关系,并且实现了多态,所以父类的虚构函数也是虚函数。这样才能通过父类指针或引用析构子类对象。
  • 工厂函数 createSmile(Type type) 的返回值改成了 AbstractSmile*类型(之前是void *),这个是人造恶魔果实的基类,通过这个指针保存的是子类对象的地址,这样就实现了多态,所以在 main 函数中,通过 obj 对象调用的实际上是子类BatSmile中的函数。

缺点

违反了设计模式原则中的“开放-封闭”原则:增加子类时还要去工厂函数createSmile中增加case。

工厂模式

工厂模式有很多工厂类:

  • 一个基类,包含一个虚工厂函数,用于实现多态;
  • 多个子类,重写父类的工厂函数。每个子工厂类的职责再次拆分、细化,如果要生产新品种的恶魔果实,就添加对应的工厂。
uml图
AbstractSmile
+transform()
+ability()
+AbstractSmile()
SheepSmile
+transform() : void
+ability() : void
LionSmile
+transform() : void
+ability() : void
BatSmile
+transform() : void
+ability() : void
AbstractFactory
+createSmile(Type type)
+~AbstractFactory()
SheepFactory
+createSmile()
LionFactory
+createSmile()
BatFactory
+createSmile()
代码实现
// 恶魔果实工厂类 -- 基类
class AbstractFactory {
public:
    virtual AbstractSmile* createSmile() = 0;
    virtual ~AbstractFactory() {}
};

class SheepFactory : public AbstactFactory {
public:
    AbstractSmile* createSmile() override {
        return new SheepSmile;
    }
    ~SheepFactory() {
        cout << "release resource SheepFactory" << endl;
    }
};
class LionFactory : public AbstractFactory {
public:
    AbstractSmile* createSmile() override {
        return new LionSmile;
    }
    ~LionFactory() {
        cout << "release resource LionFactory" << endl;
    }
}
class BatFactory : public AbstractFactory {
public:
    AbstractSmile* createSmile() override {
        return new BatSmile;
    }
    ~BatFactory() {
        cout << "release resource BatFactory" << endl;
    }
}

// 和简单工厂一样(AbstractSmile)
class AbstractSmile {
public:
    virtual void transform() = 0;
    virtual void ability() = 0;
    virtual ~AbstractSmile() {}
};
class SheepSmilee : public AbstactSmile {
public:
    void transform() override {
        cout << "SheepMan" << endl;
    }
    void ability() override {
        cout << "SSSSSheep" << endl;
    }
};
class LionSmilee : public AbstactSmile {
public:
    void transform() override {
        cout << "LionMan" << endl;
    }
    void ability() override {
        cout << "LLLLLion" << endl;
    }
};
class BatSmilee : public AbstactSmile {
public:
    void transform() override {
        cout << "BatMan" << endl;
    }
    void ability() override {
        cout << "BBBBBat" << endl;
    }
};

int main() {
    // 实例化一个蝙蝠对象
    AbstractFactory* factory = new BatFactory;
    AbstractSmile* obj = factory->createSmile();
    obj->transform();
    obj->ability();
    return 0;
}

不论是恶魔果实的基类还是工厂类的基类,它们的虚函数可以是纯虚函数,也可以是非纯虚函数。这样的基类在设计模式中就可以称之为抽象类。

抽象工厂模式

制作海贼船:

基础标准旗舰
船体木头钢铁合成金属
动力手动内燃机核能
武器射速炮激光
// 船体
class ShipBody {
public:
    virtual string getShipBody() = 0;
    virtual ~ShipBody() {}
};

class WoodBody : public ShipBody {
public:
    string getShipBody() override {
        return string("use Wood -- Body");
    }
};

class IronBody : public ShipBody {
public:
    string getShipBody() override {
        return string("use Iron -- Body");
    }
};

class MetalBody : public ShipBody {
public:
    string getShipBody() override {
        return string("use Metal -- Body");
    }
};

// 动力
class Engine {
public:
    virtual string getEngine() = 0;
    ~Engine() {}
};

class Human : public Engine {
    string getEngine() override {
        return string("use Human -- Engine");
    }
};

class Diesel : public Engine {
    string getEngine() override {
        return string("use Diesel -- Engine");
    }
};

class Nuclear : public Engine {
    string getEngine() override {
        return string("use Nuclear -- Enginie");
    }
};

// 武器
class Weapon
{
public:
    virtual string getWeapon() = 0;
    virtual ~Weapon() {}
};

class Gun : public Weapon
{
public:
    string getWeapon() override
    {
        return string("use Gun -- Weapon");
    }
};

class Cannon : public Weapon
{
public:
    string getWeapon() override
    {
        return string("use Cannon -- Weapon");
    }
};

class Laser : public Weapon
{
public:
    string getWeapon() override
    {
        return string("use Laser -- Weapon");
    }
};

/******************/

// 轮船
class Ship {
public:
    Ship(ShipBody* body, Weapon* weapon, Eginie* egnie):
    m_body(body), m_weapon(weapon),m_engine(engine) {
        
    }
    string getProperty() {
        string info = m_body->getShipBody() + m_weapon->getWeapon() + m_engine->getEngine;
        return info;
    }
    ~Ship() {
        delete m_mody;
        delete m_weapon;
        delete m_engine;
    }
private:
    ShipBody* m_body = nullptr;
    ShipWeapon* m_weapon = nullptr;
    ShipEngine* m_engine = nullptr;
};

/*****添加工厂类*****/

// 工厂类
class AbstractFactory {
public:
    virtual Ship* creatShip() = 0;
    virtual ~AbstractFactory() {}
};

// 做基础船体的工厂
class BasicFactory {
public:
    Ship* createShip() {
        Ship* ship = new Ship(new WoodBody, new Gun, new Human);
        return ship;
    }
};
// 做标准船体的工厂
class StandardFactory : public AbstractFacory {
public:
    Ship* createShip() {
        Ship* ship = new Ship(new IronBody, new Cannon, new Diesel);
        return ship;
    }
};
// 做旗舰船体的工厂
class UltimateFactory : public AbstractFacory {
public:
    Ship* createShip() {
        Ship* ship = new Ship(new MetalBody, new Laser, new Nuclear);
        return ship;
    }
};

int main() {
    AbstractFactory* factory = new StandradFactory;
    Ship* ship = factory->creatFactory;
    cout << ship->getProperty();
    delete ship;
    delete factory;
    return 0;
}

观察者模式

观察者模式允许我们定义一种订阅机制,可在对象事件发生时通知所有的观察者对象,使它们能够自动更新。观察者模式还有另一个名字叫做“发布-订阅”模式

发布者 <–> 订阅者/观察者

使用场景:

  • 使用的社交软件,当关注的博主更新了内容,会收到提示
  • 购买的商品到达菜鸟驿站,会收到翼展发来的提示信息
  • 订阅了报刊,每天/每月都会收到新的报纸或杂志

送报鸟

发布者
  • 添加订阅者,将所有的订阅者都存储起来
  • 删除订阅者,将其从订阅者列表中删除
  • 将消息发送给订阅者(发通知)
头文件 NewsAgency.h
// 声明订阅者类(只是做了声明,并没有包含这个类的头文件)
class Observer;
// 新闻社 类
class NewsAgency {
public:
	// 添加一个订阅者
	void attach(Observer* ob);
	// 删除一个订阅者
	void detach(Observer* ob);
	// 给所有订阅者发送通知信息
	virtual void notify(string msg) = 0;
	virtual ~NewsAgency() {}
protected:
	// 订阅者列表
	list<Observer*> m_list;
};
源文件 NewsAgency.cpp
#include "NewsAgency.h"
#include "Observer.h"
#include <iostream>
void NewsAgency::attach(Observer* ob) {
	m_list.push_back(ob);
}
void NewsAgency::detach(Observer* ob) {
	m_list.remove(ob);
}

发布者uml图

NewsAgency
# m_list list
attach() : void
detach() : void
notify() : void
~NewsAgency()
Morgans
notify() : void
Gossip
notify() : void
摩根斯新闻 & 八卦新闻
头文件 NewAgency.h
// 摩根斯新闻
class Morgans : public NewsAgency {
public:
	void notify(string msg) override;
};

// 八卦新闻
class Gossip : public NewsAgency {
	void notify(string msg) override;
};
源文件 NewAgency.cpp
void Morgans::notify(string msg) {
	cout << "摩根斯新闻社报纸订阅者一共有" << m_list.size() << "人" << endl;
	for(const auto& item : m_list) {
		item->update(msg);
	}
}

void Gossip::notify(string msg) {
	cout << "八卦新闻社报纸订阅者一共有" << m_list.size() << "人" << endl;
	for(const auto& item : m_list) {
		item->update(msg);
	}
}
订阅者

观察者这个角色可能不是一个人,可能是几个或者一个群体,但行为一致。所以我们可以给所有的观察者定义一个抽象的基类。

#pragma once
#include <string>
#include <iostream>
#include "NewsAgency.h"
using namespace std;

// 抽象订阅者类
class Observer {
public:
	// 为观察者提供一个信息的发布者
	Observer(string name, NewsAgency* news): m_name(name), m_news(news) {
		m_news->attach(this); // 通过发布者对象将观察者对象存储起来,这样就可以收到发布者推送的信息了
	}
	// 取消订阅发布之,不再接受订阅信息
	void unsubscribe() {
		m_news->deteach(this);
	}
	// 观察者得到最新消息后,用于更新自己当前的状态
	virtual void update(string msg) = 0;
	virtual ~Observer() {}
protected:
	string m_name;
	NewsAgency* m_news;
};

// 订阅者子类
// 蒙奇·D·龙
class Dragon : public Observer {
public:
	using Observer::Observer;
	void update(string msg) override {
		cout << "@@@路飞老爸革命军龙收到消息:" << msg << endl;
	}
}

class Shanks : public Observer
{
public:
    using Observer::Observer;
    void update(string msg) override
    {
        cout << "@@@路飞的引路人红发香克斯收到消息: " << msg << endl;
    }
};

class Bartolomeo : public Observer
{
public:
    using Observer::Observer;
    void update(string msg) override
    {
        cout << "@@@路飞的头号粉丝巴托洛米奥收到消息: " << msg << endl;
    }
};

起飞

Observer
#m_name :string
#m_news :NewsAgency = nullptr
+Observer(name:string, news:NewsAgency*)
+unsubscribe() : void
+update(msg:string)
+~Observer()
NewsAgency
# m_list list
+attach() : void
+detach() : void
+notify() : void
~NewsAgency()
Morgans
notify() : void
Gossip
notify() : void
Dragon
+update(msg:string) : void
Shanks
+update(msg:string) : void
Bartolomeo
+update(msg:string) : void
int main() {
	Morgans* ms = new Morgans;
	Gossip* gossip = new Gossip;
	Dragon* dragon = new Dragon("蒙奇·D·龙", ms);
	Shanks* shanks = new Shannks("香克斯", ms);
	Bartolomeo* barto = new Bartolomeo("巴托洛米奥", gossip);
	
	ms->notify(""蒙奇·D·路飞成为新世界的新的四皇之一, 赏金30亿贝里!!!");
    cout << "======================================" << endl;
    gossip->notify("女帝汉库克想要嫁给路飞, 给路飞生猴子, 哈哈哈...");

	delete ms;
	delete gossip;
	delete dragon;
	delete shanks;
	delete barto;

	return 0;
}

输出结果:

摩根斯新闻社报纸的订阅者一共有<2>人
@@@路飞的老爸革命军龙收到消息: 蒙奇·D·路飞成为新世界的新的四皇之一, 赏金30亿贝里!!!
@@@路飞的引路人红发香克斯收到消息: 蒙奇·D·路飞成为新世界的新的四皇之一, 赏金30亿贝里!!!
======================================
八卦新闻社报纸的订阅者一共有<1>人
@@@路飞的头号粉丝巴托洛米奥收到消息: 女帝汉库克想要嫁给路飞, 给路飞生猴子, 哈哈哈...

观察者模式的使用场景:

  • 当一个对象的状态发生变化,并且需要改变其他对象的时候;
  • 当应用中一些对象必须观察其他对象的时候。

(订阅者和发布者也可以没有子类,因此就不需要继承了。-- 具体任务具体分析)


策略模式

策略模式需要我们定义一系列的算法,并将每种算法都放入独立的类中,在实际操作时使这些算法对象可以互相替换。

百变路飞

作为橡胶人路飞,平时白痴但战斗时头脑异常清醒,会根据敌我双方的形式做出正确的判断:

对手战力弱,使用1档并根据实际情况使用相应的招式
对手战力一般,切换2档并根据实际情况使用相应的招式
对手战力强,切换3档并根据实际情况使用相应的招式
对手战力无敌,切换4档并根据实际情况使用相应的招式
对手战斗逆天,切换5档并根据实际情况使用相应的招式

如果将所有的策略都写到一个类中就会使得路飞这个类过于复杂,而且不易维护,如果基于策略模式处理路飞类,可以把关于在什么场景下使用什么策略的判断去除,把处理逻辑分散到多个不同的策略类中,这样就可以将复杂的逻辑简化。

路飞的形态
// 定义一个抽象的策略类
class AbstractStrategy {
public:
	virtual void fight(bool isfar = false) = 0;
	virtual ~AbstracStrategy() {}
};

这个抽象类中的fight()函数有一个布尔类型的参数,表示在当前状态下时要进行近距攻击还是远程攻击,参数不同,在这种状态下使用的招式也不同。基于这个抽象的基类,可以定义出一系列子类:

// 一档
class YiDang : public AbstractStrategy {
	void fight(bool isfar = false) override {
		cout << "*** 现在使用的是一档: ";
		if(isfar) {
			cout << "橡胶机关枪" << endl;
		} else {
			cout << "橡胶·攻城炮" << endl;
		}
	}
};

// 二档
class ErDang : public AbstractStrategy {
	void fight(bool isfar = false) override {
		cout << "*** 切换成二档: " << endl;
		if(isfar) {
			cout << "橡胶Jet火箭" endl;
		} else {
			cout << "橡胶Jet铳乱打" endl;
		}
	}
};

// 三档
class SanDang : public AbstractStrategy {
	void fight(bool isfar = false) override {
		cout << "*** 切换成三档: " << endl;
		if(isfar) {
			cout << "橡胶巨人回旋弹" endl;
		} else {
			cout << "橡胶巨人战斧" endl;
		}
	}
};

// 四档
class SiDang : public AbstractStrategy {
	void fight(bool isfar = false) override {
		cout << "*** 切换成四档: " << endl;
		if(isfar) {
			cout << "橡胶狮子火箭炮" endl;
		} else {
			cout << "橡胶犀牛榴弹炮" endl;		
		}
	}
};

// 五档
class WuDang : public AbstractStrategy {
	void fight(bool isfar = false) override {
		cout << "*** 切换成五挡: 变成尼卡形态可以把物体变成橡胶, 并任意改变物体的形态对其进行攻击!!!"  << endl;
	}
};
路飞

路飞和这几个技能之间是组合关系。定义路飞类:

// 难度级别
enum class Level: char {Easy, Normal, Hard, Experts, Professional};

// 路飞
class Luffy {
public:
	void fight(Level level, bool isfar = false) {
		if (m_strategy) {
			delete m_strategy;
			m_strategy = nullptr;
		}
		switch (level) {
		case Level::Easy:
			m_strategy = new* YiDang;
			break;
		case Level::Normal:
			m_strategy = new* ErDang;
			break;
		case Level::Hard:
			m_strategy = new* SanDang;
			break;
		case Level::Experts:
			m_strategy = new* SiDang;
			break;
		case Level::Professional:
			m_strategy = new* WuDang;
		default:
			break;
		}
		m_strategy->fight(isfar);
	}
	~Luffy() {
		delete m_strategy;
	}
private:
	AbstractStrategy* m_strategy = nullptr;
};

在 Luffy 类中的 fight() 方法里根据参数传递进来的难度级别,路飞在战斗的时候就可以选择开启对应的档位使用相关的招式来击败对手。

对症下药

战斗画面:

int main()
{
    Luffy* luffy = new Luffy;
    cout << "--- 在香波地群岛遇到了海军士兵: " << endl;
    luffy->fight(Level::Easy);
    cout << "--- 在魔谷镇遇到了贝拉米: " << endl;
    luffy->fight(Level::Normal);
    cout << "--- 在司法岛遇到了罗布·路奇: " << endl;
    luffy->fight(Level::Hard);
    cout << "--- 在德雷斯罗萨遇到了多弗朗明哥: " << endl;
    luffy->fight(Level::Experts);
    cout << "--- 在鬼岛遇到了凯多: " << endl;
    luffy->fight(Level::Professional);

    delete luffy;
    return 0;
}

战斗场景:

--- 在香波地群岛遇到了海军士兵:
*** 现在使用的是一档: 橡胶·攻城炮
--- 在魔谷镇遇到了贝拉米:
*** 切换成二挡: 橡胶Jet·铳乱打
--- 在司法岛遇到了罗布·路奇:
*** 切换成三挡: 橡胶巨人战斧
--- 在德雷斯罗萨遇到了多弗朗明哥:
*** 切换成四挡: 橡胶犀牛榴弹炮
--- 在鬼岛遇到了凯多:
*** 切换成五挡: 变成尼卡形态可以把物体变成橡胶, 并任意改变物体的形态对其进行攻击!!!

策略模式中的若干个策略对象之间是完全独立的,它们不知道其他对象的存在。当我们想使用对象中各种不同的算法变体,并希望能够在运行的时候切换这些算法时,可以选择使用策略模式来处理这个问题


享元模式

享元模式就是摒弃了在每个对象中都所有数据的这种方式,通过数据共享(缓存)让有限的内存可以加载更多的对象。

类似于线程池。线程池可以复用线程,有效避免了线程的频繁创建和销毁,减少了性能的消耗并提高了工作效率。享元模式中的共享内存也可以将其称之为缓存,这种模式中共享的是对象。

对象的常量数据通常被称为内在状态,其位于对象中,其他对象只能读取不能修改。而对象的其他状态常常能被其他对象“从外部”改变,因此被称为外在状态。使用享元模式一般建议将内在状态和外在状态分离,将内在状态单独放到一个类中,这种类我们可以将其称之为享元类。

设计炮弹

炸弹弹体

// 共享数据类
class ShareBombBody {
public:
	ShareBombBody(string sprite): m_sprite(sprite) {
		cout << "正在创建 <" << m_sprite << ">..." << endl;
	}
	void move(int x, int y, int speed) {
		cout << "炸弹以每小时" << speed << "速度飞到了(" << x << ", " << y << ")的位置" << endl;
	}
	void draw(int x, int y) {
		cout << "在(" << x << ", " << y << ")的位置重绘炸弹弹体..." << endl;
	}

private:
	string m_sprite;
	string m_color = string("black");
};

通过构造函数得到精灵图片后,该对象中的数据就不会再发生任何变化了。

炸弹

// 发射弹炮
class LauchBomb {
public:
	LaunchBomb(SharedBombBody* body) : m_bomb(body) {}
	int getX() {
		return m_x;
	}
	int getY() {
		return m_y;
	}
	void setSpeed(int speed) {
		m_speed = speed;
	}
	int getSpeed() {
		return m_speed;
	}
	void move(int x, int y) {
		m_x = x;
		m_y = y;
		m_bomb->move(m_x, m_y, m_speed);
		draw();
	}
	void draw() {
		m_bomb->draw(m_x, m_y);
	}

private:
	int m_x = 0;
	int m_y = 0;
	int m_speed = 100;
	SharedBombBody* m_bomb = nullptr;
};

彩蛋

连续接住10个炸弹并反弹,这个时候卡普投出的某一个炸弹就会变成一个彩蛋

  • 这个彩蛋拥有和炸弹不一样的外观(使用的精灵图不同)
  • 不论是炸弹还是彩蛋,对卡普来说处理它们的动作都是一样的
  • 炸弹爆炸造成伤害,彩蛋爆炸会给玩家提供奖励或其他。

所以炸弹和彩蛋有相同的处理动作,只不过在细节处理上略有不同。对于这种情况,我们一般会提供一个抽象的基类并在这个类中提供一套虚操作函数,这样在子类中就可以重写父类提供的虚函数,提供不同的处理动作。

// 享元基类
class FlyweightBody {
public:
	FlyweightBody(string sprite): m_sprite(sprite) {}
	virtual void move(int x, int y, int speed) = 0;
	virtual void draw(int x, int y) = 0;
	virual ~FlyweightBody() {}
protected:
	string m_sprite;
	string m_color = string("Black");
};

// 炸弹弹体(共享数据)
class SharedBombBody: public FlyweightBody {
public:
	using FlyweightBody::FlyweightBody;
	void move(int x, int y, int speed) {
		cout << "炸弹以每小时" << speed << "速度飞到了(" << x << ", " << y << ")的位置" << endl;
	}
	void draw(int x, int y) {
		cout << "在(" << x << ", " << y << ")的位置重绘炸弹弹体..." << endl;
	}
};

// 炸弹彩蛋(唯一)
class UniqueBomb: public FlyweightBody {
public:
	using FlyweightBody::FlyweightBody;
	void move(int x, int y, int speed) {
		cout << "炸弹以每小时" << speed << "速度飞到了(" << x << ", " << y << ")的位置" << endl;
	}
	void draw(int x, int y) {
		cout << "在(" << x << ", " << y << ")的位置重绘炸弹弹体..." << endl;
	}
};

一般来说,享元数据都是共享的,但是这里的 UniquBomb 类,虽然是享元类的子类,但是这个类的实例对象不共享数据(假设背个彩蛋的外观和用途都不同),尽管我们大部分时间都需要共享对象来降低内存的损耗,但在个别时候也可能不需要共享数据,此时 UniqueBomb 子类就有存在的必要了,它可以帮助我们解决那些不需要共享对象场景下的问题,使用这种处理方式对应的操作流程是无需做出任何改变的。如果有以上需求,可以给享元类提供一个基类。

享元工厂

如果有很多种炮弹型号,那就需要很多张精灵图,也就是 SharedBombBody 类型的对象对应也应该有很多个,此时我们就可以再添加一个享元工厂类,专门用来生产这些共享的享元数据。

// 享元工厂类
class BombBodyFactory {
public:
	SharedBombBody* getSharedData(string name) {
		SharedBombBody* data = nullptr;
		// 遍历map容器
		for(auto item: m_body) {
			if(item.first == name) {
				// 找到了
				data = item.second;
				cout << "正在复用 <" << name << ">..." << endl;
				break;
			}
		}
		if(data == nullptr) {
			data = new SharedBombBody(name);
			cout << "正在创建 <" << name << ">..." << endl;
			m_bodyMap.insert(make_pair(name, data));
		}
		return data;
	}
	~BombBodyFactory() {
		for(auto item : m_bodyMap) {
			delete item.second;
		}
	}
private:
	map<string, SharedBombBody*> m_bodyMap;
};

在享元工厂内部有一个 map 容器,用于存储各种型号的炮弹的享元数据,这个享元工厂就相当于一个对象池,当调用了 getSharedData(string name) 函数后,如果能从 map容器 找到 name 对应的享元对象就返回该对象,如果找不到就创建一个新的享元对象并存储起来,这样就可以实现对象的复用了。

发射炮弹

int main() {
	// 发射炮弹
	BombBodyFactory* factory = new BombBodyFactory;
	vector<LaunchBomb*> ballList;
	vector<string> namelist = {"撒旦-1", "撒旦-1", "撒旦-2", "撒旦-2", "撒旦-2", "撒旦-3"};
	for(auto name : namelist) {
		int x = 0, y = 0;
		LaunchBomb* ball = new LaunchBomb(factory->getSharedData(name));
		for(int i = 0; i < 3; ++i) {
			x += rand() % 100;
			y += rand() % 50;
			ball->move(x, y);
		}
		cout << "======================" << endl;
		ballList.push_back(ball);
	}
	//彩蛋
	UniqueBomb* unique = new UniqueBomb("大彩蛋");
	LaunchBomb* bomb = new LaunchBomb(unique);
	int x = 0, y = 0;
	for(int i = 0; i < 3; ++i) {
		x += rand() % 100;
		y += rand() % 50;
		bomb->move(x, y);
	}

	for(auto ball : ballList) {
		delete ball;
	}
	delete factory;
	delete unique;
	delete bomb;
	return 0;
}
/*
	上面的测试程序相当于在游戏中,卡普扔出了6个炸弹和1个彩蛋,不论是炸弹还是彩蛋都是可以通过 LaunchBomb 类进行处理,这个类的构造函数在接收实参的时候实际上就是一个多态的应用。
*/

完整代码

// 享元基类
class FlyweightBody
{
public:
    FlyweightBody(string sprite) : m_sprite(sprite) {}
    virtual void move(int x, int y, int speed) = 0;
    virtual void draw(int x, int y) = 0;
    virtual ~FlyweightBody() {}
protected:
    string m_sprite;    // 精灵图片
    string m_color = string("black");     // 渲染颜色
};

// 炸弹弹体
class SharedBombBody : public FlyweightBody
{
public:
    using FlyweightBody::FlyweightBody;
    void move(int x, int y, int speed) override
    {
        cout << "炸弹以每小时" << speed << "速度飞到了(" 
            << x << ", " << y << ") 的位置..." << endl;
    }
    void draw(int x, int y) override
    {
        cout << "在 (" << x << ", " << y << ") 的位置重绘炸弹弹体..." << endl;
    }
};

// 唯一的炸弹彩蛋
class UniqueBomb : public FlyweightBody
{
public:
    using FlyweightBody::FlyweightBody;
    void move(int x, int y, int speed) override
    {
        // 此处省略对参数 x, y, speed的处理
        cout << "彩蛋在往指定位置移动, 准备爆炸发放奖励..." << endl;
    }
    void draw(int x, int y) override
    {
        cout << "在 (" << x << ", " << y << ") 的位置重绘彩蛋运动轨迹..." << endl;
    }
};

// 发射炮弹
class LaunchBomb
{
public:
    LaunchBomb(FlyweightBody* body) : m_bomb(body) {}
    int getX()
    {
        return m_x;
    }
    int getY()
    {
        return m_y;
    }
    void setSpeed(int speed)
    {
        m_speed = speed;
    }
    int getSpeed()
    {
        return m_speed;
    }
    void move(int x, int y)
    {
        m_x = x;
        m_y = y;
        m_bomb->move(m_x, m_y, m_speed);
        draw();
    }
    void draw()
    {
        m_bomb->draw(m_x, m_y);
    }

private:
    int m_x = 0;
    int m_y = 0;
    int m_speed = 100;
    FlyweightBody* m_bomb = nullptr;
};


// 享元工厂类
class BombBodyFactory
{
public:
    SharedBombBody* getSharedData(string name)
    {
        SharedBombBody* data = nullptr;
        // 遍历容器
        for (auto item : m_bodyMap)
        {
            if (item.first == name)
            {
                // 找到了
                data = item.second;
                cout << "正在复用 <" << name << ">..." << endl;
                break;
            }
        }
        if (data == nullptr)
        {
            data = new SharedBombBody(name);
            cout << "正在创建 <" << name << ">..." << endl;
            m_bodyMap.insert(make_pair(name, data));
        }
        return data;
    }
    ~BombBodyFactory()
    {
        for (auto item : m_bodyMap)
        {
            delete item.second;
        }
    }
private:
    map<string, SharedBombBody*> m_bodyMap;
};

int main()
{
    // 发射炮弹
    BombBodyFactory* factory = new BombBodyFactory;
    vector<LaunchBomb*> ballList;
    vector<string> namelist = { "撒旦-1", "撒旦-1", "撒旦-2", "撒旦-2", "撒旦-2", "撒旦-3"};
    for (auto name : namelist)
    {
        int x = 0, y = 0;
        LaunchBomb* ball = new LaunchBomb(factory->getSharedData(name));
        for (int i = 0; i < 3; ++i)
        {
            x += rand() % 100;
            y += rand() % 50;
            ball->move(x, y);
        }
        cout << "=========================" << endl;
        ballList.push_back(ball);
    }
    // 彩蛋
    UniqueBomb* unique = new UniqueBomb("大彩蛋");
    LaunchBomb* bomb = new LaunchBomb(unique);
    int x = 0, y = 0;
    for (int i = 0; i < 3; ++i)
    {
        x += rand() % 100;
        y += rand() % 50;
        bomb->move(x, y);
    }

    for (auto ball : ballList)
    {
        delete ball;
    }
    delete factory;
    delete unique;
    delete bomb;
    return 0;
}
正在创建 <撒旦-1>...
炸弹以每小时100速度飞到了(41, 17) 的位置...
在 (41, 17) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(75, 17) 的位置...
在 (75, 17) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(144, 41) 的位置...
在 (144, 41) 的位置重绘炸弹弹体...
=========================
正在复用 <撒旦-1>...
炸弹以每小时100速度飞到了(78, 8) 的位置...
在 (78, 8) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(140, 22) 的位置...
在 (140, 22) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(145, 67) 的位置...
在 (145, 67) 的位置重绘炸弹弹体...
=========================
正在创建 <撒旦-2>...
炸弹以每小时100速度飞到了(81, 27) 的位置...
在 (81, 27) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(142, 68) 的位置...
在 (142, 68) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(237, 110) 的位置...
在 (237, 110) 的位置重绘炸弹弹体...
=========================
正在复用 <撒旦-2>...
炸弹以每小时100速度飞到了(27, 36) 的位置...
在 (27, 36) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(118, 40) 的位置...
在 (118, 40) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(120, 43) 的位置...
在 (120, 43) 的位置重绘炸弹弹体...
=========================
正在复用 <撒旦-2>...
炸弹以每小时100速度飞到了(92, 32) 的位置...
在 (92, 32) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(113, 48) 的位置...
在 (113, 48) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(131, 93) 的位置...
在 (131, 93) 的位置重绘炸弹弹体...
=========================
正在创建 <撒旦-3>...
炸弹以每小时100速度飞到了(47, 26) 的位置...
在 (47, 26) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(118, 64) 的位置...
在 (118, 64) 的位置重绘炸弹弹体...
炸弹以每小时100速度飞到了(187, 76) 的位置...
在 (187, 76) 的位置重绘炸弹弹体...
=========================
彩蛋在往指定位置移动, 准备爆炸发放奖励...
在 (67, 49) 的位置重绘彩蛋运动轨迹...
彩蛋在往指定位置移动, 准备爆炸发放奖励...
在 (102, 93) 的位置重绘彩蛋运动轨迹...
彩蛋在往指定位置移动, 准备爆炸发放奖励...
在 (105, 104) 的位置重绘彩蛋运动轨迹...
  • 享元模式中的享元类可以有子类,也可以没有;
  • 享元模式中可以添加享元工厂,也可以不添加;
  • 享元工厂的作用和单例模式类似,但二者的关注点略有不同:单例模式关注的是类的对象有且只有一个,享元工厂关注的是某个实例对象是否可以共享

原型模式

原型模式就是能够复制已有对象,而又无需使代码依赖它们所属的类。即通过已有对象克隆另一个新对象,并且不需要使用构造函数。

克隆可能会在父类和子类之间进行,并且可能是动态的,很明显通过父类的拷贝构造函数无法实现对子类对象的拷贝,其实这就是一个多态,我们需要给父类提供一个克隆函数并且是一个虚函数。

#include <iostream>
using namespace std;

// 士兵父类
class GermaSoldier {
	virtual GermaSoldier* clone() = 0;
	virtual string whoAmI() = 0;
	virtual ~GermaSoldier() {}
};

class Soldier66 : public GermaSoldier {
public:
	GermaSoldier* clone() override {
		return new Soldier66(*this);
	}
	string whoAmI() override {
		return string("我是杰尔马66的超级士兵!!!");
	}
};

class Soldier67 : public GermaSoldier {
public:
	GermaSoldier* clone() {
		return new Soldier67(*this);
	}
	string whoAmI() override {
		return string("我是杰尔马67的超级士兵!!!");
	}
};

int main() {
	GermaSoldier* obj = new Soldier66;
	GermaSoldier* soldier = obj->clone();    //通过父类指针克隆了一个子类 Soldier66 的对象
	cout << soldier->whoAmI() << endl;
	delete soldier;
	delete obj;

	obj = new Soldier67;
	solider = obj->clone();    //通过父类指针克隆了一个子类 Soldier67 的对象
	cout << soldier->whoAmI() << endl;
	delete soldier;
	delete obj;

	// 在这两个士兵子类的clone()函数体内部是通过当前子类的拷贝构造函数复制出了一个新的子类对象。

	return 0;
}
我是杰尔马66的超级士兵!!!
我是杰尔马67的超级士兵!!!

适配器模式

  • STL有六大组件(容器、算法、迭代器、仿函数、适配器、空间适配器)
  • 适配器又能分为:容器适配器、函数适配器、迭代器适配器

适配器模式相当于寻找了一个翻译 – 乔巴适配器

人类

class Foreigner {
public:
	virtual string confession() = 0;
	void setResult(string msg) {
		cout << "Panda say: " << msg << endl;
	}
	virtual ~Foreigner() {}
};

// 美国人
class American : public Foreigner {
public:
	string confession() override{
		return string("美国人忏悔");
	}
};

// 法国人
class French : public Foreigner {
public:
	string confession() override{
		return string("法国人忏悔");
	}
};

动物类

class Panda {
public:		
	void recvMessage(string msg) {
		cout << msg << endl;
	}
	string sendMessage() {
		return string("never");
	}
};

适配器–乔巴

class AbstractChopper {
public:
	AbstractChopper(Foreigner* foreigner) : m_foreigner(foreigner) {}
	virtual void translationToPanda() = 0;
	virtual void translationToHuman() = 0;
	virtual ~AbstractChopper() {}
protected:
	Foreigner* m_foreigner = nullptr;
	Panda m_panda;
};

// 英语乔巴适配器
class EnglishChopper {
public:
	// 继承构造函数
	using AbstractChopper::AbstractChopper;
	void translationToPanda() override {
		string msg = m_foreigner->confession();
		m_panda->recvMessage("美国人说:" + msg);
	}
	void translationToHuman() override {
		string msg = m_panda->sendMessage();
		m_foregner->setResult("美国人," + msg);
	}
};

// 法语乔巴适配器
class EnglishChopper {
public:
	// 继承构造函数
	using AbstractChopper::AbstractChopper;
	void translationToPanda() override {
		string msg = m_foreigner->confession();
		m_panda->recvMessage("法国人说:" + msg);
	}
	void translationToHuman() override {
		string msg = m_panda->sendMessage();
		m_foregner->setResult("法国人," + msg);
	}
};

测试函数

int main()
{
    Foreigner* human = new American;
    EnglishChopper* american = new EnglishChopper(human);
    american->translateToPanda();
    american->translateToHuman();
    delete human;
    delete american;

    human = new French;
    FrenchChopper* french = new FrenchChopper(human);
    french->translateToPanda();
    french->translateToHuman();
    delete human;
    delete french;

    return 0;
}

使用适配器类为相关的类提供适配服务的时候,如果这个类没有子类就可以让适配器类继承这个类,如果这个类有子类,此时使用继承就不太合适了,建议将适配器类和要被适配的类设置为关联关系。


装饰模式

动态的给一个对象绑定额外的属性(如,能力、职责、功能)。

装饰模式也称为封装模式,即在原有行为上拓展,并不会改变该行为。

解构

黑胡子的能力分析

  • 与生俱来加上后天努力练就的本领
  • 来自自然系·暗暗果实的能力
  • 来自超人系·震震果实的能力
战魂
// 战士抽象类
class Soldier {
public:
	Soldier() {}
	Soldier(string name): m_name(name) {}
	string getName() {
		return m_name;
	}
	virtual void fight() = 0;
	virtual ~Soldier() {}
protected:
	string m_name = string();
};
黑胡子
// 黑胡子Marshall·D·Teach
class Teach : public Soldier {
	using Soldier::Soldier;
	void fight() override {
		cout << m_name << "依靠惊人的力量和高超的体术战斗..." << endl;
	}
};
附魔

吃恶魔果实对战力进行装饰

// class DevilFruit : public Soldier {
public:
	// 指定给谁吃
	void enchantment(Soldier* sloldier) {
		m_human = soldier;
		m_name = m_human->getName();
	}
	virtual ~DevilFruit() {}
protected:
	Soldier* m_human = nullptr;
};
/*
说明:
	1. 在 DevilFruit 类中没有重写父类 Soldier 中的纯虚函数 fight(),所以它还是抽象类;
	2. 恶魔果实有很多种类,每个果实的能力不同,所以战斗方法也不同,因此需要在恶魔果实的子类中根据每种果实能力去重写作战函数 fight() 的行为;
	3. 恶魔果实 DevilFruit 类的作用是给某个 Soldier 的子类对象附魔,所以在类内部提供了一个附魔函数 enchantment(Soldier* soldier),参数就是即将要得到恶魔果实能力的那个战士。
*/
果实
// 暗暗果实
class DarkFruit : public DevilFruit {
public:
    void fight() override {
        m_human->fight();
        // 使用当前恶魔果实的能力
        cout << m_human->getName() 
            << "吃了暗暗果实, 可以拥有黑洞一样的无限吸引力..." << endl;
        warning();
    }
private:
    void warning() {
        cout << m_human->getName() 
            << "你要注意: 吃了暗暗果实, 身体元素化之后不能躲避攻击,会吸收所有伤害!" << endl;
    }
};

// 震震果实
class QuakeFruit : public DevilFruit {
public:
    void fight() override {
        m_human->fight();
        cout << m_human->getName() 
            << "吃了震震果实, 可以在任意空间引发震动, 摧毁目标...!" << endl;
    }
};

// 大饼果实
class PieFruit : public DevilFruit {
public:
    void fight() override {
        m_human->fight();
        cout << m_human->getName()
            << "吃了大饼果实, 获得大饼铠甲...!" << endl;
        ability();
    }

    void ability() {
        cout << "最强辅助 -- 大饼果实可以将身边事物变成大饼, 帮助自己和队友回血..." << endl;
    }
};
  • 在重写父类的fight()函数的时候,用当前恶魔果实能力和战士的自身能力进行了加成,调用了战士对象的作战函数 m_human->fight(),在原有基础上提升了其战斗力;
  • 在恶魔果实子类中,可以根据实际需要定义类独有的方法;
  • 这些子类都继承了父类的附魔函数enchantment(Soldier* soldier),这样就可以完成对战士战力的加成(装饰)了。
六边形战士
int main() {
	Teach* teach = new Teach("马歇尔·D·蒂奇");
	DarkFruit* dark = new DarkFruit;
	QuakeFruit* quake = new QuakeFruit;
	PieFruit* pie = new PieFruit;

	dark->enchantment(teach);
	quack->enchantment(dark);
	pie->enchantment(quake);

	pie->fight();

	delete teach;
	delete dark;
	delete quake;
	delete pie;
	
	return 0;
}
马歇尔·D·蒂奇依靠惊人的力量和高超的体术战斗...
马歇尔·D·蒂奇吃了暗暗果实, 可以拥有黑洞一样的无限吸引力...
马歇尔·D·蒂奇你要注意: 吃了暗暗果实, 身体元素化之后不能躲避攻击,会吸收所有伤害!
马歇尔·D·蒂奇吃了震震果实, 可以在任意空间引发震动, 摧毁目标...!
马歇尔·D·蒂奇吃了大饼果实, 获得大饼铠甲...!
最强辅助 -- 大饼果实可以将身边事物变成大饼, 帮助自己和队友回血...
	dark->enchantment(teach);
	quack->enchantment(dark);
	pie->enchantment(quake);

装饰模式就是层层包装,都是Soldier类。所以新的恶魔果实对象可以是旧恶魔果实附魔的,因为在恶魔果实内部绑定了一个实体(这里是黑胡子对象),最终所有的恶魔果实的能力都集中在了这个黑胡子对象身上。

恶魔果实类 DevilFruit 就是装饰模式中的装饰类的基类,并且和恶魔果实类和父类 Soldier 之间还是聚合关系,通过它的派生类 DarkFruit、QuakeFruit、PieFruit最终实现了对 Teach 类的装饰,使黑胡子这个主体有了三种恶魔果实能力,最终是战力 fight() 得到了加成效果。


迭代器模式

遍历xxx – 将集合与它对应的遍历算法解耦

能够顺序访问一个集合对象中的各个元素,而又不暴露该集合底层的表现形式(列表、栈、树、图等)。这种行为设计模式就叫做迭代器模式。

应用场景:点名、报数、火车查票、STL迭代器…

场景应用

检阅凯多的队伍

集结部队 – 定义一个双向链表

使用一个容器将成员存储起来(这里选择链表)

在列表类中定义一个创建迭代器类的方法

头文件 MyList.h
#pragma once
#include <string>
using namespace std;
// 定义一个链表节点
struct Node {
	Node(String n): name(n) {}
	string name = string();
	Node* next = nullptr;
	Node* prev = nullptr;
};

// 双向链表
class MyList{
public:
	inline int getCount() {
		return m_count;
	}
	inline Node* head() {
		return m_head;
	}
	inline Node* tail() {
		return m_tail;
	}

	Node* insert(Node* item, string data);
	Node* pushFront(string data);
	Node* pushBack(string data);
	// 创建迭代器对象函数
	Iterator* getIterator(bool isReverse = false);

private:
	Node* m_head = nullptr;
	Node* m_tail = nullptr;
	int m_count = 0;
};
源文件 MyList.cpp
#include "MyList.h"
#include "Iterator.h"

Node* MyList::insert(Node* item, string data) {
	Node* node = nullptr;
	if(item == m_head) {
		// 如果item是链表的头节点
		node = pushFront(data);
	} else {
		node = new Node(data);
		node->next = item;
		node->prev = item->prev;
		item->prev->next = node;
		item->prev = node;
		m_count++;
	}
	
	return node;
}

Node* MyList::pushFront(string data) {
	Node* node = new Node(data);
	if(m_head == nullptr) {
		m_head = m_tail = node;
	} else {
		node->next = m_head;
		m_head->prev = node;
		m_head = node;
	}
	m_count++;
	return node;
}

Node* MyList::pushBack(string data) {
	Node* node = new Node(data);
	if(m_head == nullptr) {
		m_head = m_tail = node;
	} else {
		node->pre = m_tail;
		m_tail->next = node;
		m_tail = node;
	}
	m_count++;
	return node;
}

Iterator* MyList::getIterator(bool isReverse) {
	Iterator* iterator = nullptr;
	if(isReverse) {
		iterator = new ReverseIterator(this);
	} else {
		iterator = new ForwardIterator(this);
	}
	return iterator;
}

检阅准备 – 迭代器类

遍历之前的列表,不论是哪种遍历方式(正向遍历、逆向遍历)都对应相同的操作接口,所以需要先提供一个抽象的迭代器基类。

通过迭代器接口访问上面的双向链表的时候,我们只知道它是一个容器,而内部的数据结构都已经被隐藏了。

抽象的迭代器类 Iterator.h
class Iterator {
public:
	Iterator(MyList* mylist): m_list(mylist) {}
	Node* current() {
		return m_current;
	}
	virtual Node* first() = 0;	// 得到链表的头结点/尾结点
	virtual Node* next() = 0;	// 得到当前结点的后继结点/前驱结点
	virtual Node* isDone() = 0;	// 判断遍历是否结束
	virtual ~Iterator() {}
private:
	MyList m_list = nullptr;
	Node* m_current = nullptr;
};
迭代器子类
// 正向迭代器
class ForwordIterator : public Iterator {
	using Iterator::Iterator;
	Node* first() override {
		m_currrent = m_list->head();
		return m_current;
	}
	Node* next() override {
		m_current = m_current->next;
		return m_current;
	}
	bool isDone() override {
		return m_current == m_list->tail()->next;
	}
};

// 逆向迭代器
class ReverseIterator : public Iterator {
	using Iterator::Iterator;
	Node* first() override {
		m_current = m_list->tail();
		return m_current;
	}
	Node* next() override {
		m_current = m_current->prev;
		return m_current;
	}
	bool isDone() override {
		return m_current == m_list->head()-->prev;
	}
};

检阅

main函数
int main()
{
    vector<string> nameList{ 
        "烬", "奎因", "杰克", "福兹·弗", "X·德雷克",
        "黑色玛利亚", "笹木", "润媞", "佩吉万",
        "一美", "二牙", "三鬼", "四鬼", "五鬼",
        "六鬼", "七鬼", "八茶", "九忍","十鬼"
    };
    MyList mylist;
    for (int i = 0; i < nameList.size(); ++i)
    {
        mylist.pushBack(nameList.at(i));
    }
    // 遍历
    Iterator* it = mylist.getIterator(true);
    cout << "检阅开始, 凯多: 同志们辛苦啦~~~~~" << endl;
    for (auto begin = it->first(); !it->isDone(); it->next())
    {
        cout << "   " << it->current()->name << "say: 为老大服务!!! " << endl;
    }
    cout << endl;
    delete it;
    return 0;
}
检阅开始, 凯多: 同志们辛苦啦~~~~~
   十鬼say: 为老大服务!!!
   九忍say: 为老大服务!!!
   八茶say: 为老大服务!!!
   七鬼say: 为老大服务!!!
   六鬼say: 为老大服务!!!
   五鬼say: 为老大服务!!!
   四鬼say: 为老大服务!!!
   三鬼say: 为老大服务!!!
   二牙say: 为老大服务!!!
   一美say: 为老大服务!!!
   佩吉万say: 为老大服务!!!
   润媞say: 为老大服务!!!
   笹木say: 为老大服务!!!
   黑色玛利亚say: 为老大服务!!!
   X·德雷克say: 为老大服务!!!
   福兹·弗say: 为老大服务!!!
   杰克say: 为老大服务!!!
   奎因say: 为老大服务!!!
   烬say: 为老大服务!!!

命令模式

命令模式就是将请求转换为一个包含与请求相关的所有信息的独立对象,通过这个转换能够让使用者根据不同的请求将客户参数化、延迟请求执行或将请求放入队列中或记录请求日志,且能实现可撤销操作。


点餐需求分析:

  • 允许一个顾客点多个菜,点餐完毕后厨房才开始制作;
  • 点餐过程中需要提醒顾客,这个菜现在是不是可以制作(也许原材料用完了)
  • 需要有点餐记录,结账的时候用
  • 顾客可以取消已下单但还未制作的菜

如果想要实现上述的需求,需要在程序中准备如下几个对象:

  • 服务员路飞:替顾客下单(调用者)
  • 厨师折普:给顾客炒菜(执行者,接受者)
  • 点餐列表:待执行的命令列表

代码实现

厨师哲普

class CookerZeff {
public:
	void makeDSX {
		cout << "开始烹饪地三鲜...";
	}
	void makeGBJD()
    {
        cout << "开始烹饪宫保鸡丁...";
    }
    void makeYXRS()
    {
        cout << "开始烹饪鱼香肉丝...";
    }
    void makeHSPG()
    {
        cout << "开始烹饪红烧排骨...";
    }
};

下单

点餐的动作不变,点的食物不同。

// 点餐的命令 -- 抽象类
class AbstractCommand {
public:
	AbstractCommand(CookerZeff* receiver) : m_cooker(receiver) {}
	virtual void excute() = 0;
	virtual string name() = 0;
	~AbstractCommand() {}
protected:
	CookerZeff* m_cooker = nullptr;
};
/*
	在这个抽象类中关联了一个厨师对象 CookerZeff* m_cooker,有了厨师对象后就可以去执行炒菜动作 excute() 了。基于抽象类可以派生出若干个子类,在子类中让厨师去炒菜(重写excute)
*/

// 地三鲜的命令
class DSXCommand : public AbstractCommand
{
public:
    using AbstractCommand::AbstractCommand;
    void excute() override
    {
        m_cooker->makeDSX();
    }
    string name() override
    {
        return "地三鲜";
    }
};

// 宫保鸡丁的命令
class GBJDCommand : public AbstractCommand
{
public:
    using AbstractCommand::AbstractCommand;
    void excute() override
    {
        m_cooker->makeGBJD();
    }
    string name() override
    {
        return "宫保鸡丁";
    }
};

// 鱼香肉丝的命令
class YXRSCommand : public AbstractCommand
{
public:
    using AbstractCommand::AbstractCommand;
    void excute() override
    {
        m_cooker->makeYXRS();
    }
    string name() override
    {
        return "鱼香肉丝";
    }
};

// 红烧排骨的命令
class HSPGCommand : public AbstractCommand
{
public:
    using AbstractCommand::AbstractCommand;
    void excute() override
    {
        m_cooker->makeHSPG();
    }
    string name() override
    {
        return "红烧排骨";
    }
};
/*
	顾客下单就是命令模式中的命令,这些命令的接受者是厨师,命令被分离出来实现了厨师类的解耦合。通过这种方式可以控制命令执行的时机。
*/

服务员路飞

// 服务器路飞 -- 命令的调用者
class WaiterLuffy {
public:
	// 下单
	void setOrder(int index, AbstractCommand* cmd) {
		cout << index << "号桌点了" << cmd->name() << endl;
		if(cmd->name() == "鱼香肉丝") {
			cout << "每天鱼了,点个别的吧" << endl;
			return;
		}
		// 没找到该顾客
		if (m_cmdList.find(index) == m_cmdList.end()) {
			list<AbstractCommand*> mylist{ cmd };
			m_cmdList.insert(make_pair(index, mylist));
		} else {
			m_cmdList[index].push_back(cmd);
		}
	}
	// 取消订单
	void cancelOrder(int index, AbstractCommand* cmd) {
		if(m_cmdList.find(index) != m_cmdList.end()) {
			m_cmdList[index].remove(cmd);
			cout << index << "号桌撤销了" << cmd->name() << endl;
		}
	}
	// 结账
	void checkOut(int index) {
		cout << "第【" << index << "】号桌的顾客点的菜是:【";
		for(const auto& item : m_cmdList[index]) {
			cout << item->name();
			if(item != m_cmdListe[index].back()) {
				cout << ",";
			}
		}
		cout << "】" << endl;
	}
	void notify(int index) {
		for(const auto& item : m_cmdList[index]) {
			item->excute();
			cout << index << "号桌" << endl;
		}
	}
private:
	// 存储顾客的下单信息
	map<int, list<AbstractCommand*>> m_cmdList'
};

大快朵颐

int main() {
	CookerZeff* cooker = new CookerZeff;
	WaiterLuffy* luffy = new WaiterLuffy;

	YXRSCommand* yxrs = new YXRSCommand(cooker);
    GBJDCommand* gbjd = new GBJDCommand(cooker);
    DSXCommand* dsx = new DSXCommand(cooker);
    HSPGCommand* hspg = new HSPGCommand(cooker);

	cout << "=================== 开始点餐 ===================" << endl;
	luffy->setOrder(1, ysrs);
	luffy->setOrder(1, dsx);
	luffy->setOrder(1, gbjd);
    luffy->setOrder(1, hspg);
    luffy->setOrder(2, dsx);
    luffy->setOrder(2, gbjd);
    luffy->setOrder(2, hspg);
	cout << "=================== 撤销订单 ===================" << endl;
	luffy->cancelOrder(1, dsx);
	cout << "=================== 开始烹饪 ==================="
	luffy->notify(1);
	luffy->notify(2);
	cout << "=================== 结账 ==================="
	luffy->checkOut(1);
	luffy->checkOut(2);

	return 0;
}
=================== 开始点餐 ===================
1号桌点了鱼香肉丝
没有鱼了, 做不了鱼香肉丝, 点个别的菜吧...
1号桌点了地三鲜
1号桌点了宫保鸡丁
1号桌点了红烧排骨
2号桌点了地三鲜
2号桌点了宫保鸡丁
2号桌点了红烧排骨
=================== 撤销订单 ===================
1号桌, 撤销了地三鲜
=================== 开始烹饪 ===================
开始烹饪宫保鸡丁...1号桌
开始烹饪红烧排骨...1号桌
开始烹饪地三鲜...2号桌
开始烹饪宫保鸡丁...2号桌
开始烹饪红烧排骨...2号桌
=================== 结账 ===================
第[1]号桌的顾客点的菜是: 【宫保鸡丁, 红烧排骨】
第[2]号桌的顾客点的菜是: 【地三鲜, 宫保鸡丁, 红烧排骨】

优势点

命令模式最大的特点就是松耦合设计。

  • 使用这个模式可以很容易设计出一个命令队列
  • 很容易将命令记录到日志中
  • 允许接受请求方决定是否否决请求
  • 很容易实现对请求的撤销或重做

状态模式就是在一个类的内部会有多种状态的变化,因为状态变化而导致其行为的改变,在类的外部看上去就像这个类自身发生了改变一样。

状态模式和策略模式比较类似,策略模式中的各个策略是独立不关联的,但是状态模式下的对象的各种状态可以独立也可以相互依赖。

山治的一天

厨师工作划分为(上午、中午、下午、晚上)的状态,工作的时间内容都不一样。

开始工作

定义一个基类

// State.h
// 抽象状态
class Sanji;
class AbstractState {
public:
	virtual void working(Sanji* sanji) = 0;
	virtual ~AbstractState() {}
};
/*
	由于这个状态是属于山治的,所以在这个抽象的状态类中提供的工作函数working() 的参数指定了这个状态的所有者,这里只是对 山治类Sanji 做了一个声明,尽量不要在这个头文件中包含山治的头文件,否则会造成头文件重复包含(因为山治类和状态类需要互相引用对方)。
*/

有了上面的抽象状态类,就可以基于这个基类把山治全天对应的状态子类依次定义出来了。

头文件State.h

// 上午状态
class ForenoonState: public AbstractState {
public:
	void working(Sanji* sanji) override;
};

// 中午状态
class NoonState: public AbstractState {
public:
	void working(Sanji* sanji) override;
};

// 下午状态
class AfternoonState: public AbstractState {
public:
	void working(Sanji* sanji) override;
};

// 晚上状态
class EveningState: public AbstractState {
public:
	void working(Sanji* sanji) override;
};

源文件State.cpp

#include <iostream>
#include "Sanji.h"
#include "State.h"
using namespace std;

void ForenoonState::working(Sanji* sanji) {
	int time = sanji->getClock();
	if(time < 8) {
		cout << "当前时间<" << time << ">点,准备早餐,布鲁克得多喝点牛奶..." << endl;
	} else if(time > 8 && time < 11) {
		cout << "当前时间<" << time << ">点,去船头钓鱼,储备食材..." << endl;
	} else {
		sanji->setState(new NoonState);
		sanji->working();
	}
}

void NoonState::working(Sanji* sanji) {
	int time = sanji->getClock();
	if(time < 13) {
		cout << "当前时间<" << time << ">点,去厨房做午饭,多给路飞做点肉.." << endl;
	} else {
		sanji->setState(new AfternoonState);
		sanji->working();
	}
}

void AfternoonState::working(Sanji* sanji) {
	int time = sanji->getClock();
	if(time < 15) {
		cout << "当前时间<" << time << ">点,准备下午茶,给罗宾和娜美制作爱心甜点..." << endl;
	} else if(time > 15 && time < 18) {
		cout << "当前时间<" << time << ">点,和乔巴去船尾钓鱼..." << endl;
	} else {
		sanji->setState(new EveningState);
		sanji->working();
	}
}

void EveningState::working(Sanji* sanji) {
	int time = sanji->getClock();
	if(time < 19) {
		cout << "当前时间<" << time << ">点,去厨房做晚饭,让索隆多喝点汤..." << endl;
	} else {
		cout << "当前时间<" << time << ">今天过的很高兴,累了睡觉了..." << endl;
	}
}

在状态模式下的各个模式之间是可以有依赖关系(else里面基本上都有)的,这一点和策略模式有区别,策略模式下当前策略不知道其他策略的存在。

山治

上面定义的一系列状态都是属于山治这个对象的,只不过通过状态模式来处理山治全天的工作状态变化的时候,把他们都分离出去了,成了独立的个体,从逻辑上说他们是包含与被包含的关系,从UML类图的角度来讲他们之间是组合(整体和部分)关系。

定义山治类

// Sanji.h
#pragma once
#include "State.h"

class Sanji {
public:
	Sanji() {
		m_state = new ForenoonState;
	}
	void working() {
		m_state->working(this);
	}
	void setState(AbstractState* state) {
		if(m_state != nullptr) {
			delete m_state;
		}
		m_state = state;
	}
	void setClock(int time) {
		m_clock = time;
	}
	int getClock() {
		return m_colck;
	}
	~Sanji() {
		delete m_state;
	}
private:
	int m_clock = 0;
	AbstracteState* m_state = nullptr;
};

工作日志

可以修改山治类内部的时钟的值来模拟时间的流逝,这样山治的状态就会不停的发生变化:

int main() {
	Sanjin* sanji = new Sanji;
	vector<int> data{7, 10, 12, 14, 16, 18, 22};
	for(const auto& item : data) {
		sanji->setClock(item);
		sanji->working();
	}
	delete sanji;

	return 0;
} 
当前时间<7>点, 准备早餐, 布鲁克得多喝点牛奶...
当前时间<10>点, 去船头钓鱼, 储备食材...
当前时间<12>点, 去厨房做午饭, 给路飞多做点肉...
当前时间<14>点, 准备下午茶, 给罗宾和娜美制作爱心甜点...
当前时间<16>点, 和乔巴去船尾钓鱼, 储备食材...
当前时间<18>点, 去厨房做晚饭, 让索隆多喝点汤...
当前时间<22>点, 今天过得很高兴, 累了睡觉了...

总结

如果对象需要根据当前自身状态进行不同的行为,同时状态的数量非常多且与状态相关的代码会频繁变更或者类对象在改变自身行为时需要使用大量的条件语句时,可使用状态模式。


笔记来源:爱编程的大丙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值