1.一个具体实现范例的逐步重构
1.1 功能策划
补血道具(药品):
- 补血丹:补充200点生命值
- 大还丹:补充300点生命值
- 守护丹:补充500点生命值
角色:(父类Fighter)
- 战士:Warrior
- 法师:Mage
1.2 示例代码
```cpp // 道具种类 enum class ItemAddLife { LF_BXD, // 补血单 LF_DHD, // 大还丹 LF_SHD // 守护丹 };
// 战斗者父类 class Fighter { public: // 构造函数 Fighter(int life, int magic, int attack) : m_life(life) , m_magic(magic) , m_attack(attack) { }
// 做父类时析构函数应该为虚函数virtual ~Fighter() { }
public: void useItem(ItemAddLife itemType) { if (itemType == ItemAddLife::LF_BXD) { m_life += 200; } else if (itemType == ItemAddLife::LF_DHD) { m_life += 300; } else if (itemType == ItemAddLife::LF_SHD) { m_life += 500; } } // 其它逻辑略 protected: int m_life; // 生命值 int m_magic; // 魔法值 int m_attack; // 攻击力 };
// 战士类 class Warrior : public Fighter { public: // 构造函数 Warrior(int life, int magic, int attack) : Fighter(life, magic, attack) { } };
// 战士类 class Mage : public Fighter { public: // 构造函数 Mage(int life, int magic, int attack) : Fighter(life, magic, attack) { } };
void trategy1() { // 角色很多时可以使用工厂模式 Fighter* warrior = new Warrior(1000, 0, 200); warrior->useItem(ItemAddLife::LF_BXD); delete warrior; }
道具目前只有一个补血道具,如果增加其他道具比如增加魔法值、体力的或者角色的一些状态,就需要在Fighter类的`void useItem(ItemAddLife itemType)`函数中新增逻辑,例如:```cppvoid UseItem(ItemAddlife djtype) //吃药补充生命值{if (djtype == LF_BXD) //道具类型:补血丹{m_life += 200; //补充200点生命值//if (主角中毒)//{// 停止中毒状态,也就是主角吃药后不再中毒//}//if (主角处于狂暴状态)//{// m_life += 400; //额外再补充400点生命值// m_magic += 200; //魔法值也再补充200点//}} else if (djtype == LF_DHD) //道具类型:大还丹{m_life += 300; //补充300点生命值} else if (djtype == LF_SHD) //道具类型:守护丹{m_life += 500; //补充500点生命值}//.......其他的一些判断逻辑,略。。。。。。}
2.使用策略模式
2.1 定义:
设计模式的定义:定义一系列算法(策略类),将每个算法封装起来,让它们可以相互替换。换句话说,策略模式通常把一些列算法封装到一系列具体策略类中来作为抽象策略类的子类,然后根据实际需要使用这些子类。
2.2 代码
将UseItem()成员函数中的if…else…提取出来封装成类(算法)。
#pragma once#include <iostream>using namespace std;class ItemStrategy;// 角色// 战斗者父类class Fighter {public:// 构造函数Fighter(int life, int magic, int attack): m_life(life), m_magic(magic), m_attack(attack){}// 做父类时析构函数应该为虚函数virtual ~Fighter() { }// 设置道具使用的策略void setItemStrategy(ItemStrategy* itemStrategy) {m_itemStrategy = itemStrategy;}// 使用道具void useItem() {m_itemStrategy->useItem(this);}// 获取人物生命值int getLife() {return m_life;}// 设置人物生命值void setLife(int life) {m_life = life;}protected:int m_life; // 生命值int m_magic; // 魔法值int m_attack; // 攻击力ItemStrategy* m_itemStrategy = nullptr;};// 战士类class Warrior : public Fighter {public:// 构造函数Warrior(int life, int magic, int attack) : Fighter(life, magic, attack) { }};// 战士类class Mage : public Fighter {public:// 构造函数Mage(int life, int magic, int attack) : Fighter(life, magic, attack) { }};// 道具// 道具策略类的父类class ItemStrategy {public:virtual void useItem(Fighter* fighter) = 0;virtual ~ItemStrategy() {}};// 补血丹策略类class ItemStrategyBXD :public ItemStrategy {public:void useItem(Fighter* fighter) override {fighter->setLife(fighter->getLife() + 200);}};// 大还丹策略类class ItemStrategySHD :public ItemStrategy {public:void useItem(Fighter* fighter) override {fighter->setLife(fighter->getLife() + 300);}};// 守护丹策略类class ItemStrategyDHD :public ItemStrategy {public:void useItem(Fighter* fighter) override {fighter->setLife(fighter->getLife() + 500);}};void trategy2() {// 角色很多时可以使用工厂模式Fighter* warrior = new Warrior(1000, 0, 200);// 吃一颗大还丹ItemStrategy* stategy = new ItemStrategyDHD();warrior->setItemStrategy(stategy);warrior->useItem();cout << warrior->getLife() << endl;// 吃一颗补血丹ItemStrategy* stategy2 = new ItemStrategyDHD();warrior->setItemStrategy(stategy2);warrior->useItem();cout << warrior->getLife() << endl;delete stategy;delete warrior;}
2.3 类图
策略类中的三种角色
- Context(环境类):该类中维持着一个对抽象策略类的指针或引用。这里指Fighter类。
- Stategy(抽象策略类):定义所支持的算法的公共接口,是所有策略类的父类。这里指ItemStrategy类。
- ConcreteStrategy(具体策略类):抽象策略类的子类,实现抽象策略类中声明的接口。这里指ItemStrategyBXD、ItemStrategyDHD、ItemStrategySHD。
2.4 策略类的优缺点
优点:
- 以扩展的方式支持对未来的变化,符合开闭原则。
- 遇到大量不稳定的if条件分支 或者switch分支,就要优先考虑是否可以通过策略模式来解决。策略模式是if,switch条件分支的杀手
- 算法可以被复用
- 策略模式可以看成是类继承的一种替代方案。通过为环境类对象指定不同的策略,就可以改变环境类对象的行为
缺点:
- 导致引入许多新策略类;
- 使用策略时,调用者(main主函数)必须熟知所有策略类的功能并根据实际需要自行决定使用哪个策略类。
3.依赖倒置原则
3.1 定义
依赖倒置原则:是面向独享设计的主要实现方法,同时也是实现开闭原则的重要实现途径
解释:高层组件不应该依赖底层(具体实现类),两者都应该依赖于抽象层
范例:工厂模式时,亡灵类Undead、元素类Element、机械类Mechanic,如果这三类怪物被战士击杀时,例如下面的代码:
#pragma once#include <iostream>using namespace std;// 亡灵类怪物class Undead {public:void getInfo() {cout << "this is a undead monster." << endl;}// 其他代码略..};// 元素类怪物class Element {public:void getInfo() {cout << "this is a element monster." << endl;}// 其他代码略..};// 机械类怪物class Mechanic {public:void getInfo() {cout << "this is a mechanic monster." << endl;}// 其他代码略..};// 战士主角class Warrior {public:// 攻击亡灵类怪物void stackEnemyUndead(Undead* undead) {// 进行攻击处理/调用亡灵类怪物相关的成员函数undead->getInfo();}// --------------------------------------------------------// 添加新的被攻击的怪物时, 都要在该类增加对应公有成员函数 ------// --------------------------------------------------------// 攻击元素类怪物void stackEnemyElement(Element* element) {// 进行攻击处理/调用元素类怪物相关的成员函数element->getInfo();}// 其他代码略};void trategy3() {Warrior* warrior = new Warrior();// 攻击一只亡灵类怪物Undead* undead = new Undead();warrior->stackEnemyUndead(undead);// 攻击一只元素类怪物Element* element = new Element();warrior->stackEnemyElement(element);delete warrior;delete undead;delete element;}
需要在Warrior这个类中添加击杀对应怪物的成员函数。
这里面 Element Undead Mechanic这三个类属于底层组件,trategy3()成员函数的逻辑代码属于高层组件,对应UML图如下:
高层组件直接依赖底层组件示意图
3.2 优化
将所有怪物抽象抽象出一个父类:
#pragma once#include <iostream>using namespace std;// 作为所有怪物类的父类(抽象层)class Monster {public:virtual void getInfo() = 0;virtual ~Monster() { }};// 亡灵类怪物class Undead : public Monster {public:void getInfo() override{cout << "this is a undead monster." << endl;}// 其他代码略..};// 元素类怪物class Element : public Monster {public:void getInfo() override{cout << "this is a element monster." << endl;}// 其他代码略..};// 机械类怪物class Mechanic : public Monster {public:void getInfo() override{cout << "this is a mechanic monster." << endl;}// 其他代码略..};// 战士主角class Warrior {public:// 攻击亡灵类怪物void stackEnemy(Monster* monster){// 进行攻击处理/调用亡灵类怪物相关的成员函数monster->getInfo();}// 其他代码略};void strategy4(){Warrior* warrior = new Warrior();// 攻击一只亡灵类怪物Undead* undead = new Undead();warrior->stackEnemy(undead);// 攻击一只元素类怪物Element* element = new Element();warrior->stackEnemy(element);delete undead;delete element;delete warrior;}
3.3 UML图
高层组件和底层组件都依赖于抽象层示意图
