( 0 ) 这里补充面向对象设计的几个原则:
开闭原则 OCP : 面向增补开放,面向代码修改关闭。其实反映到代码设计上就是类的继承,通过继承与多态,可以不修改原代码,又增加新的类似的功能。
依赖倒置原则 Dependency Inversion Principle DIP : 面向父类的虚函数编程,可以节省代码量与减少重复。
最少知识原则 Least Knowledge Principle LKP ; 一个类对另一个类了解的越少越好,降低耦合度。由访问接口进行连接。
单一职责原则 Single Responsibility Principle SRP : 一个类的职责应该单一,只对外提供一种功能。
(1) 框架设计模式 model mode : 算法的框架不变,算法的细节可以改变。主要依赖多态。
class Player
{
protected:
int life;
int magic;
int attack;
virtual void effect_self() {}
virtual void effect_enemy() {}
virtual bool can_burn() = 0;
public:
Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack) {}
virtual Player(){}
void play_effect_burn() { cout << "play_effect_burn\n"; }
void burn() // 模板模式:算法的框架不变,细节可以变
{
if (can_burn())
{
effect_enemy();
effect_self();
play_effect_burn();
}
}
};
class Fighter : public Player
{
public:
Fighter() : Player(100, 100, 50) {}
void effect_self() override { this->life -= 30; }
void effect_enemy() override { cout << "敌人被燃烧 40 血\n"; }
bool can_burn() override
{
if (this->life >= 40) return true;
else return false;
}
};
(2)简单工厂模式:不要直接使用 new A() 创建对象,一旦对类 A 增删和修改参数,会紧耦合,牵一发动全身,用一个函数集中使用 new A ,返回创建好的对象,如同工厂批量生产产品一样。对构造对象时 的修改也限制在工厂方法里。
class Player // 角色父类
{
protected:
int life;
int magic;
int attack;
public:
Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack) {}
virtual Player(){}
};
class Fighter : public Player // 战士
{
public:
Fighter() : Player(100, 100, 50) {}
};
class Master : public Player // 法师
{
public:
Master() : Player(50, 300, 150) {}
};
class Create // 把 new 语句集中在产生对象的函数里,减小代码以后升级时需要修改的范围
{
public:
static Player* createPlayer(string str)
{
if(str == "fighter")
return new Fighter();
else if(str == "master")
return new Master();
}
};
int main()
{
auto pFighter = Create::createPlayer("fighter");
auto pMaster = Create::createPlayer("master");
delete pFighter;
delete pMaster;
return 0;
}
但工厂函数里的 if 选择,如果要创建新类,会修改原代码。面向对象的 OCP 原则:更新代码时,尽量追加新代码,而不是修改原代码,向增加开放,向修改关闭。如此引出工厂模式。
(3) 工厂模式:符合 OCP 规则的 用工厂方式生产对象:
class Player // 角色父类
{
protected:
int life;
int magic;
int attack;
public: Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack) {}
virtual Player(){}
};
class Fighter : public Player // 战士
{
public: Fighter(int life, int magic, int attack) : Player(life , magic , attack) {}
};
class Master : public Player // 法师
{
public: Master(int life, int magic, int attack) : Player(life, magic, attack) {}
};
class Create // 制造生产对象的虚基类
{
public: virtual Player* createPlayer() = 0;
virtual Create(){}
};
class Create_Fighter : public Create // 对应对象的工厂类
{
public: Player* createPlayer() override { return new Fighter(50,50,50); }
};
class Create_Monster : public Create
{
public: Player* createPlayer() override { return new Master(60,60,60); }
};
int main()
{
auto pFactFight = new Create_Fighter();
auto pFighter = pFactFight->createPlayer();
auto pFactMaster = new Create_Monster();
auto pMastr = pFactMaster->createPlayer();
delete pFactFight;
delete pFactMaster;
delete pFighter;
delete pMastr;
return 0;
}
(4) 抽象工厂模式,比工厂模式密度更高的生产对象的模式:一个工厂类包含了多个生产对象的函数:
class Player // 角色父类
{
protected:
int life;
int magic;
int attack;
public: Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack) {}
virtual Player(){}
};
class Fighter_Land : public Player // 陆上战士
{
public: Fighter_Land(int life, int magic, int attack) : Player(life , magic , attack) {}
};
class Fighter_Sea : public Player // 海洋战士
{
public: Fighter_Sea(int life, int magic, int attack) : Player(life, magic, attack) {}
};
class Master_Land : public Player // 陆上法师 ,游戏新版本,不同的游戏场景
{
public: Master_Land (int life, int magic, int attack) : Player(life, magic, attack) {}
};
class Master_Sea : public Player // 海洋法师
{
public: Master_Sea(int life, int magic, int attack) : Player(life, magic, attack) {}
};
class Create // 制造生产对象的虚基类
{
public:
virtual Player* createPlayer() = 0;
virtual Player* createMaster() = 0;
virtual Create(){}
};
class Create_Land : public Create // 不同场景下的角色生产工厂
{
public:
Player* createPlayer() override { return new Fighter_Land(10,10,10); }
Player* createMaster() override { return new Master_Land(20,20,20); }
};
class Create_Sea : public Create
{
public:
Player* createPlayer() override { return new Fighter_Sea(10,10,10); }
Player* createMaster() override { return new Master_Sea(20, 20, 20); }
};
int main()
{
auto pCreate_Land = new Create_Land();
auto pFighter_Land = pCreate_Land->createPlayer();;
auto PMaster_Land = pCreate_Land->createMaster();
auto pCreate_Sea = new Create_Sea();
auto pFighter_Sea = pCreate_Sea->createPlayer();
auto pMaster_Sea = pCreate_Sea->createMaster();
delete pCreate_Land; delete pFighter_Land; delete PMaster_Land;
delete pCreate_Sea; delete pFighter_Sea; delete pMaster_Sea;
return 0;
}
工厂模式不要忘记 delete 这些指针,包括在堆区创建的工厂对象和工厂生产的类对象,都要在最后 delete 掉,释放掉。
(5) 原型模式:通过对象原型来产生新的对象。主要是把工厂类里生产对象的方法转移到了对象类里。 clone() 函数
class Player // 角色父类
{
protected:
int life;
int magic;
int attack;
public:
Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack) {}
Player(const Player& p) :life(p.life), magic(p.magic), attack(p.attack) {}
~Player() {} // 父类应有的虚析构函数
virtual Player* clone() = 0;
};
class Fighter_Land : public Player // 陆上战士
{
public:
Fighter_Land(int life, int magic, int attack) : Player(life , magic , attack) {}
Fighter_Land(const Fighter_Land& p) : Player(p) {}
Player* clone() override { return new Fighter_Land(* this); }
};
(6) 建造者模式( builder ):与工厂模式的区别是:工厂模式生产的对象简单,可以直接交付。若生产的对象复杂,比如还要组装游戏角色,加工后再返回对象 ; 或者把前端页面里的报表(有头部,主体和尾部)组合转换为 txt 、 xmL、json 格式交给后端处理。只有经过对对象的加工处理,才可以得到复杂的对象,就是建造者模式,建造二字突出其是要创建复杂对象,突出建造的复杂性。而且可以把加工建造对象的过程单独拎出来实现批量建造。
class Player // 角色父类
{
protected:
int life;
int magic;
int attack;
public:
virtual ~Player() {} // 父类应有的虚析构函数
Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack) {}
};
class Fighter : public Player // 战士
{
public:
Fighter(int life, int magic, int attack) : Player(life, magic, attack) {}
};
class Create // 制造生产对象的虚基类
{
protected:
Player* ptrPlayer;
public:
virtual ~Create() { if (ptrPlayer) delete ptrPlayer; } // 这里析构函数要释放指针
Player* getPlayer() { return std::exchange(ptrPlayer, nullptr); }
virtual void loadHead() = 0;
virtual void loadTrunk() = 0;
virtual void loadFeet() = 0;
};
class Create_Fight : public Create // 制造战士的工厂
{
public:
Create_Fight() :Create() { ptrPlayer = new Fighter(10, 10, 10); }
void loadHead() override { cout << "load head\n"; }
void loadTrunk() override { cout << "load trunk\n"; }
void loadFeet() override { cout << "load feet\n"; }
};
class Assemble
{
private:
Create* ptrCreate;
public:
Assemble(Create* t) : ptrCreate(t) {}
Player* assemble()
{
ptrCreate->loadHead();
ptrCreate->loadTrunk();
ptrCreate->loadFeet();
return ptrCreate->getPlayer();
}
};
int main()
{
auto pFighterCreate = new Create_Fight();
auto pAssemble = new Assemble(pFighterCreate);
auto pFight = pAssemble->assemble();
delete pFighterCreate; // 经测试没有内存泄露
delete pAssemble;
delete pFight;
return 0;
}
本模式另举一例:根据前端页面的报表来构造完整的数据报表,供后端使用:
相关代码如下:
class Head // 日报头
{
private:
string department;
string date;
public:
Head(const string& dep, const string& time) : department(dep), date(time) {}
string& getDepart() { return department; }
string& getDate() { return date; }
};
class Content // 日报内容
{
private:
string content;
double totalTime;
public:
Content(const string& cont, const double& time) : content(cont), totalTime(time) {}
string& getContent() { return content; }
double& getTotalTime() { return totalTime; }
};
class Foot // 日报尾
{
private:
string name;
public:
Foot(const string& name) :name(name) {}
string& getName() { return name; }
};
class Builder // 建造完整的日报
{
protected:
string result;
public:
virtual ~Builder() { }
virtual void buildHead( Head * ) = 0;
virtual void buildCont(vector<Content* >& vec) = 0;
virtual void buildFoot(Foot*) = 0;
string& getRes() { return result; } // 返回左值引用
};
class Txt_builder : public Builder // 建造 txt 型日报
{
public:
void buildHead(Head* pHead) override
{
result += pHead->getDepart() + " , " + pHead->getDate() + "\n";
}
void buildCont(vector<Content* >& vec) override
{
for (auto iter = vec.begin(); iter != vec.end(); iter++)
{
ostringstream oss; // 此对象在每次 for 循环中都会被创建和释放,
oss << (*iter)->getTotalTime(); // 所以输入到 oss 中的内容并不会积累。
result += (*iter)->getContent() + " ,花费小时:" + oss.str() + '\n';
}
}
void buildFoot(Foot* pFoot) override
{
result += "报告人:" + pFoot->getName() + '\n';
}
};
class Assemble // 本类可以组装各种日报: txt 、 xmL 、 Json
{
private:
Builder* builder;
public:
Assemble(Builder* builder) : builder(builder) {}
string& assemble(Head * head , vector<Content * >& vec , Foot * foot)
{
builder->buildHead(head);
builder->buildCont(vec);
builder->buildFoot(foot);
return builder->getRes();
}
};
int main()
{
Head head("研发部" , "7.13");
Content conA("分析文档" , 3.5);
Content conB("选定语言" , 0.5);
Foot foot("zhangwuji");
vector<Content*> vecCont{&conA , &conB};
Txt_builder txtBuilter;
Assemble assemble(&txtBuilter);
auto strResult = assemble.assemble(&head , vecCont , &foot);
cout << strResult << endl;
return 0;
}
这里给出测试结果:
(7) 策略模式 Strategy 。类似于框架模式:策略的框架不变,实现细节会变,但比框架模式更复杂一点。还是以上面的游戏角色为例,战士和法师都可以采用回血策略,回血策略又可以分为小药回小血,中药回中血,大药回大血的不同具体策略,甚至随着游戏扩展,还有新的策略,或修订原有的策略。
class Strategy; // 前置声明,否则编译报错
class Player;
class Strategy
{
public:
virtual ~Strategy() {}
virtual void heal(Player*) = 0;
};
class Player // 角色父类
{
protected:
int life;
int magic;
int attack;
Strategy* pStrategy ;
public:
virtual ~Player() {} // 父类应有的虚析构函数
Player(int life, int magic, int attack) : life(life), magic(magic), attack(attack), pStrategy(nullptr){}
int getLife() { return life; }
void setLife(int t) { life = t; }
void setStrategy(Strategy* p) { pStrategy = p; }
void heal() { pStrategy->heal(this); }
};
class Fighter : public Player // 战士
{
public:
Fighter(int life, int magic, int attack) : Player(life , magic , attack) {}
};
class Master : public Player // 法师
{
public: Master (int life, int magic, int attack) : Player(life, magic, attack) {}
};
class Strategy_Small_Medi : public Strategy
{
public:
void heal(Player* ptr) { ptr->setLife( ptr->getLife() + 200 ); }
};
class Strategy_Middle_Medi : public Strategy
{
public:
void heal(Player* ptr) { ptr->setLife(ptr->getLife() + 300); }
};
int main()
{
Fighter fight(100, 100, 100);
Strategy_Small_Medi small;
fight.setStrategy(&small);
fight.heal();
return 0;
}
(8) 观察者模式observer , 也叫 发布–订阅模式,Publish – Subscribe 。 订阅者就是观察者。因为发布者的信息要群发,所以就要注意提升遍历数据库时的效率,尽可能快速的把消息发布给所有的订阅者。重在良好组织所有对象的存储方式,加快对订阅者的遍历速度: 比如 map 容器的遍历就比 list 要快。
class Player;
class Manager // 管理,充当数据库的角色,管理玩家角色的组信息
{
public:
virtual ~Manager() {}
virtual void joinFamily(Player *) = 0;
virtual void leaveFamily(Player*) = 0;
virtual void notify(Player* , const string & ) = 0;
};
class Player // 角色父类
{
protected:
int id;
int familyID;
string name;
public:
virtual ~Player() {} // 父类应有的虚析构函数
Player(int id, const string& name) : id(id), name(name) { familyID = -1; }
void setFamily(int t) { familyID = t; }
int getFamily() { return familyID; }
void speak(const string& str, Manager* maga) { maga->notify(this, str); }
void getNotice( Player * player , const string & str )
{
cout << name << " 的电脑显示 " << player->name << " 的信息: " << str << "\n\n";
}
};
class Fighter : public Player // 战士
{
public: Fighter(int id, const string & name) : Player(id, name) {}
};
class Master : public Player // 法师
{
public: Master(int id, const string& name) : Player(id, name) {}
};
class Manager_Fighter : public Manager // 采用默认的构造函数
{
private:
map<int, list<Player*>> allFamily; // 管理所有的有家族的角色,以家族的方式管理角色
public:
virtual void joinFamily(Player* player) override
{
int familyID = player->getFamily();
if (familyID != -1) // 要插入家族树的角色必须具有有效的家族 id
{
auto iter = allFamily.find(familyID);
if (iter != allFamily.end()) // 该家族已在家族树中
iter->second.push_back(player);
else // 出现了新的家族,要先把家族链表 list 创建并插入 map 中
{
list<Player*> list;
allFamily.insert(make_pair(familyID , list));
allFamily[familyID].push_back(player); // 理解难度在于 STL 库容器中 map 的成员方法的使用
}
}
}
virtual void leaveFamily(Player * player) override
{
int familyID = player->getFamily();
if (familyID != -1)
{
auto iter = allFamily.find(familyID);
if (iter != allFamily.end())
allFamily[familyID].remove(player); // list 允许插入相同的节点。也会被全部删除。当然指针是不会相同的
}
}
virtual void notify(Player* speaker, const string& str) override
{
int familyID = speaker->getFamily();
if (familyID != -1)
{
auto iter = allFamily.find(familyID);
if (iter != allFamily.end())
for (auto iterList = iter->second.begin(); iterList != iter->second.end(); iterList++)
(*iterList)->getNotice(speaker, str);
}
}
};
int main()
{
Fighter zhang(10, "张三");
Fighter zhao(11, "赵四");
Fighter wang(12, "王五");
Fighter ma(13, "马虎");
Manager_Fighter managerFight;
zhang.setFamily(100);
zhao.setFamily(100);
wang.setFamily(100);
ma.setFamily(200);
managerFight.joinFamily(&zhang);
managerFight.joinFamily(&zhao);
managerFight.joinFamily(&wang);
managerFight.joinFamily(&ma);
zhang.speak("Hello , i love you !!" , &managerFight);
return 0;
}
以下给出测试结果:
(9) 装饰器模式 decorator 。比如显示器绘图,绘制控件 ,比如带框的列表框,就要先绘制列表框,再给其加框;绘制带滚动条的列表框,就要先绘制列表框,再绘制滚动条。有绘图的先后顺序。类继承不恰当时候,比如子类不需要父类的所有功能,就可以考虑类组合,一个类的数据成员是另一个类,以获得更灵活强大的功能。
class Control // 控件基类
{
public:
~Control() {}
virtual void draw() = 0 ;
};
class Control_List : public Control // 列表控件
{
public:
virtual void draw() override { cout << " 列表框绘制\n\n"; }
};
class Decorator : public Control // 装饰器基类 : 其先要绘制被修饰的控件。但带修饰的控件依然可以再被修饰。
{
private:
Control* ptrControl; // 被修饰的控件对象
public:
Decorator(Control* p) : ptrControl(p) {}
virtual void draw() override { ptrControl->draw() ; } // 作为装饰类的基类的 draw()方法
};
class Decorator_border : public Decorator // 装饰器:边框
{
public:
Decorator_border(Control* p) : Decorator(p) {}
virtual void draw() override
{
Decorator::draw(); // 此处可以出现递归调用,直到调用完所有父类的 draw()方法
drawBorder();
}
private:
void drawBorder() { cout << " 边框绘制\n\n"; }
};
class Decorator_Horizontal_scroll_bar : public Decorator // 装饰器:水平滚动条
{
public:
Decorator_Horizontal_scroll_bar(Control* p) : Decorator(p) {}
virtual void draw() override
{
Decorator::draw();
draw_Horizontal_scroll_bar();
}
private:
void draw_Horizontal_scroll_bar() { cout << " 水平滚动条绘制\n\n"; }
};
int main()
{
Control_List ctrlList;
Decorator_border decoBorder(&ctrlList);
Decorator_Horizontal_scroll_bar decoBorderHSB(&decoBorder);
decoBorderHSB.draw();
return 0;
}
以下给出测试结果:
这里再给出装饰者模式的另一范例(上一例是电脑控件绘图),加佐料的饮料售卖:饮料最初售价 10 元,加了糖再加 2 元, 加了牛奶 再加 2 元。以下是代码,可见与电脑绘图出奇的像 :
class Drink // 饮料的基类
{
public:
virtual ~ Drink() {}
virtual int getPrice() = 0;
};
class Drink_Fruit : public Drink // 水果饮料,可以往里再加糖、牛奶
{
public:
virtual int getPrice() override { return 10; } // 啥也不加的水果饮料,价格是 10 元
};
class Decorator : public Drink
{
private:
Drink* ptrDrink;
public:
virtual ~Decorator() {}
Decorator(Drink* p) : ptrDrink(p) {}
virtual int getPrice() override { return ptrDrink->getPrice() ; }
};
class Decorator_Sugar : public Decorator // 饮料里再加糖,加 2 元
{
public:
Decorator_Sugar(Drink* p) : Decorator(p) {}
virtual int getPrice() override { return Decorator:: getPrice() + 2 ; }
};
class Decorator_Milk : public Decorator // 饮料里再加牛奶,加 3 元
{
public:
Decorator_Milk(Drink* p) : Decorator(p) {}
virtual int getPrice() override { return Decorator::getPrice() + 3; }
};
int main()
{
Drink_Fruit drinkFruit;
Decorator_Sugar decoSugar(&drinkFruit);
Decorator_Milk decoMilk(&decoSugar);
cout << " 水果饮料,加糖,加牛奶后的价格: " << decoMilk.getPrice() << "\n\n";
return 0;
}
测试结果如下:
谢谢