3.7 装饰器模式(代码见vs)
装饰器又叫做包装模式,允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法完整性的前提下,提供了额外的功能。
如下图:装饰器类ConDecorator想给汽车类增加新的功能,并且不能改变原来的代码,怎么做?
可以把Car基类作为装饰器类的属性,然后通过继承重写Car的原来的show方法,并且在装饰器的show方法内部,通过属性Car类对象(这个对象传过来的是一个具体的子类对象,比如Audi或者Bmw)调用具体子类对象的show方法,在这个方法基础上,增加新的方法和功能。这样就实现了不改变原来代码的基础上,增加了新的功能。然后我们通过使用装饰器对象,就可以使用装饰后的新功能。
装饰器模式,既有继承又有合成关系。
#include <iostream>
using namespace std;//装饰器模式
class Car
{
public:virtual void show() = 0;
};
//具体车型:奥迪
class Audi :public Car
{
public:virtual void show() { cout << "这是一辆奥迪车,标配。"; }
};
//具体车型:宝马
class Bmw :public Car
{
public:virtual void show() { cout << "这是一辆宝马车,标配。"; }
};
//第一个装饰器,加上定速巡航
class ConDecorator01 :public Car
{Car* car;
public:ConDecorator01(Car* c) :car(c) {};void show(){//先调用原来的show方法,通过属性car来调用car->show();//在原来的基础上加装饰,装定速巡航cout << "装饰了定速巡航" << endl;}
};
//第二个装饰器,加上自动刹车
class ConDecorator02 :public Car
{Car* car;
public:ConDecorator02(Car* c) :car(c) {};void show(){//先调用原来的show方法,通过属性car来调用car->show();//在原来的基础上加装饰,装自动刹车cout << "装饰了自动刹车" << endl;}
};
//第三个装饰器,加上自动泊车
class ConDecorator03 :public Car
{Car* car;
public:ConDecorator03(Car* c) :car(c) {};void show(){//先调用原来的show方法,通过属性car来调用car->show();//在原来的基础上加装饰,装自动泊车cout << "装饰了自动泊车" << endl;}
};
void test01()
{Car* c1 = new Bmw();//标配的宝马c1->show();cout << endl;Car* d1 = new ConDecorator01(c1);d1->show();//此时有了定速巡航功能Car* c2 = new Audi();//标配的奥迪c2->show();cout << endl;Car* d2 = new ConDecorator02(c2);d2->show();//此时有了自动刹车功能delete c1;delete c2;delete d1;delete d2;
}
装饰器模式练习:
大家知不知道QQ秀这个游戏,80后应该知道, 给动画人物搭配不同服饰。比如穿T恤,衬衫,外套,皮鞋,运动鞋,靴子...,根据下面的类图完成这个练习。
注意:这个练习跟上面的汽车例子不同,汽车例子是车有抽象层和具体层的类,装饰器只有一层,每个装饰器直接实现装饰。这个作业是被装饰的人只有一层,装饰器有两层,抽象层定义接口,不负责具体的装饰,具体装饰由具体层的装饰器完成。
//未装饰的人
class Person
{string name;
public:Person() {};//无参构造需要有,因为子类构造的时候要用Person(string na) :name(na) {};virtual void show() { cout << "我是" << name << endl; }
};
//装饰类父类,抽象类
class Finery:public Person
{
protected:Person* per;
public:Finery(Person* p) :per(p) {};//这里用到了Person的无参构造virtual void show() = 0;
};
//具体装饰:长裤
class LongTrouser :public Finery
{
public:LongTrouser(Person* p) :Finery(p) {};void show(){per->show();//调用原来未装饰的show方法//接下来加装饰cout << "穿上长裤" << endl;}
};
//具体装饰:T恤
class Tshirts :public Finery
{
public:Tshirts(Person* p) :Finery(p) {};void show(){per->show();//调用原来未装饰的show方法//接下来加装饰cout << "穿上T恤" << endl;}
};
void test02()
{Person* xm = new Person("小明");xm->show();//没装饰cout << "装饰后:" << endl;Finery* ts_xm = new Tshirts(xm);//穿上T恤ts_xm->show();Finery* lt_xm = new LongTrouser(xm);//穿上长裤lt_xm->show();delete xm;delete ts_xm;delete lt_xm;
}
3.8 代理模式(代码见vs)
代理模式也称为委托模式,作用就是为其他对象提供一种代理以控制对这个对象的访问。它允许你在不直接访问对象的情况下,通过一个代理对象来控制对该对象的访问。这个代理对象可以作为客户端和实际对象之间的中介,从而实现一些特定的控制功能,比如限制访问、记录访问日志等。
代理模式和装饰器模式很像:
1)相同点:都是继承了目标抽象类,都将目标抽象类关联到本类中作为属性。
2)代理强调的是对目标对象的控制权(强迫用户使用代理,不用就无法访问目标对象);装饰器模式强调的是在不修改源代码的基础上添加新的功能。
//代理模式
//抽象层,房东
class Landlord
{
public:virtual void rentHouse() = 0;
};
//具体的房东:Tom
class Tom :public Landlord
{
public:virtual void rentHouse() { cout << "Tom出租一套房子" << endl; }
};
//代理类
class Proxy :public Landlord
{Landlord* landlord;
public:Proxy(Landlord* land):landlord(land){}//接下来对房东出租房子的行为加限制,必须给中介交钱后,才能出租房子virtual void rentHouse(){cout << "中介先收取佣金" << endl;landlord->rentHouse();//然后才可以使用出租房子的方法}
};
void test03()
{Landlord* tom = new Tom();Landlord* proxy = new Proxy(tom);//中介代理了tom的房子proxy->rentHouse();//通过中介租房子,必须先交钱delete tom;delete proxy;
}
总结:
优点:职责清晰:真实角色就是实现实际的业务逻辑,不关心其他非本职的事务,通过后期的代理完成非本质事务,编程简单清晰。 高扩展性:具体主题角色可变。
缺点:由于在客户端和真实主题之间增加了代理,因此可能会造成请求的处理速度变慢(因为代理加了控制)。实现代理模式需要额外的工作,有些实现非常复杂。
代理模式练习:
//送礼者抽象类,某个人
class SomeOne
{
public:virtual void giveFlowers() = 0;virtual void giveDolls() = 0;virtual void giveChoc() = 0;
};
//具体送礼的人
class One :public SomeOne
{string name;
public:One(string n) :name(n) {};virtual void giveFlowers() { cout << name << "送您鲜花" << endl; }virtual void giveDolls(){ cout << name << "送您洋娃娃" << endl; }virtual void giveChoc(){ cout << name << "送您巧克力" << endl; }
};
//代理类
class Proxy_for :public SomeOne
{SomeOne* m_one;
public:Proxy_for(SomeOne* one) :m_one(one) {};virtual void giveFlowers() { cout << "送鲜花需要收取佣金100" << endl; m_one->giveFlowers(); }virtual void giveDolls() { cout << "送洋娃娃需要收取佣金150" << endl; m_one->giveDolls(); }virtual void giveChoc() { cout << "送巧克力需要收取佣金80" << endl; m_one->giveChoc(); }
};
void test04()
{SomeOne* jerry = new One("jerry");SomeOne* p = new Proxy_for(jerry);p->giveFlowers();p->giveDolls();p->giveChoc();delete jerry;delete p;
}
3.9 观察者模式(代码见vs)
观察者模式又叫做发布-订阅模式,定义了一种一对多的依赖关系,一对多,一是发布者,多是订阅者。让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新。观察者往往定义一个抽象观察者,多个具体观察者。
被观察主题对象应该包含一个容器来存放观察者对象,当被观察者发生改变时通知容器内所有的观察者对象。这样才能实现一对多。
观察者对象加入到主题的容器中,相当于订阅了主题,然后就可以接收被观察者的通知。观察者也可以被删除掉,停止订阅这个主题。
需求:主题作为发布者群发消息,多个观察者订阅主题,主题有消息时通知所有观察者。
//观察者模式
//抽象观察者
class AbsObserver
{
public:virtual void update(string content) = 0;//更新的接口,参数content是更新的内容
};
//主题类,发布者
class Subject
{string title;//标题list<AbsObserver*> obs;//容器中存放抽象观察者的地址
public:Subject(string t):title(t){}~Subject(){//发布者析构的时候,需要将全部的订阅者析构掉if (obs.size()==0){return;}//如果容器中有订阅者,逐个回收for (auto o:obs){delete o;//回收每个观察者对象obs.remove(o);//将观察者对象地址从链表中移除}}void attach(AbsObserver* someone)//绑定,即将某个观察者加入订阅,加入容器中{obs.push_back(someone);}void detach(AbsObserver* someone)//解绑,将某个观察者解除订阅,从容器中移除{obs.remove(someone);delete someone;}string getTitle() { return title; }void notify(string content)//通知,将content通知给订阅者{if (obs.size()==0){return;}for (auto o:obs)//逐个通知容器内的订阅者{o->update(content);//将content传递给订阅者}}
};
//具体观察者
class ConsObserver :public AbsObserver
{string name;//观察者姓名Subject* subject;//订阅的主题
public:ConsObserver(string n, Subject* s) :subject(s), name(n) {};void update(string content)//将发布者传过来的content内容进行展示{cout << "标题:[" << subject->getTitle() << "],内容:" << content << "," << name << "已收到" << endl;}
};
void test05()
{//准备主题和观察者对象Subject* subject = new Subject("天气预报");AbsObserver* ob1 = new ConsObserver("观察者1号", subject);AbsObserver* ob2 = new ConsObserver("观察者2号", subject);//加入订阅subject->attach(ob1);subject->attach(ob2);//发布通知subject->notify("最近天气炎热并且伴有大风");//解除订阅subject->detach(ob2);//再次通知,此时ob2就收不到消息了subject->notify("中秋节期间天气很好,大家可以轻松出行");//回收的时候,只需要回收主题对象即可,主题的析构中回收了所有订阅者delete subject;
}
观察者模式总结
观察者模式的优势:主题(Subject)无需耦合某个具体的观察者(如ConsObserver),而只需要知道其抽象接口AbsObserver即可。观察者模式解除了主题和具体观察者的耦合,依赖于抽象,而不是依赖具体。从而使得观察者的变化不会影响主题。
观察者模式的缺点:性能损耗,即在函数调用前遍历观察者列表的开销。
应用场景:通知,群发的场景。
注意:在销毁观察者对象前,必须取消订阅此观察者对象,否则通知一个已销毁的观察者可能导致程序崩溃。
观察者模式练习:
在此基础上,改造主题,主题是默认的新闻主页,主题下面还有具体的频道:经济、体育、娱乐(选择一个即可)。这样主题也分为两个层,观察者可以订阅新闻主页,也可以订阅具体的频道。
//抽象观察者
class Observer
{
public:virtual void update(string content) = 0;//更新的接口,参数content是更新的内容
};
//主题类,发布者,新闻主页
class MainSubject
{string title;//标题list<Observer*> obs;//容器中存放抽象观察者的地址
public:MainSubject(){}MainSubject(string t) :title(t) {}virtual ~MainSubject(){//发布者析构的时候,需要将全部的订阅者析构掉if (obs.size() == 0){return;}//如果容器中有订阅者,逐个回收for (auto o : obs){delete o;//回收每个观察者对象obs.remove(o);//将观察者对象地址从链表中移除}}virtual void attach(Observer* someone)//绑定,即将某个观察者加入订阅,加入容器中{obs.push_back(someone);}virtual void detach(Observer* someone)//解绑,将某个观察者解除订阅,从容器中移除{obs.remove(someone);delete someone;}virtual string getTitle() { return title; }//需要写成虚函数,子类才可以实现多态virtual void notify(string content)//通知,将content通知给订阅者{if (obs.size() == 0){return;}for (auto o : obs)//逐个通知容器内的订阅者{o->update(content);//将content传递给订阅者}}
};
//具体的主题:经济主题
class Subject_jingji :public MainSubject
{string title;//标题list<Observer*> obs;//容器中存放抽象观察者的地址
public:Subject_jingji(string t) :title(t) {}~Subject_jingji(){//发布者析构的时候,需要将全部的订阅者析构掉if (obs.size() == 0){return;}//如果容器中有订阅者,逐个回收for (auto o : obs){delete o;//回收每个观察者对象obs.remove(o);//将观察者对象地址从链表中移除}}void attach(Observer* someone)//绑定,即将某个观察者加入订阅,加入容器中{obs.push_back(someone);}void detach(Observer* someone)//解绑,将某个观察者解除订阅,从容器中移除{obs.remove(someone);delete someone;}string getTitle() { return title; }void notify(string content)//通知,将content通知给订阅者{if (obs.size() == 0){return;}for (auto o : obs)//逐个通知容器内的订阅者{o->update(content);//将content传递给订阅者}}
};
//具体观察者
class ConcreteObserver :public Observer
{string name;//观察者姓名MainSubject* subject;//订阅的主题
public:ConcreteObserver(string n, MainSubject* s) :subject(s), name(n) {};void update(string content)//将发布者传过来的content内容进行展示{cout << "标题:[" << subject->getTitle() << "],内容:" << content << "," << name << "已收到" << endl;}
};
void test06()
{MainSubject* sub = new MainSubject("今日新闻");//首页MainSubject* sub_jingji = new Subject_jingji("经济新闻");//经济频道主题Observer* ob1 = new ConcreteObserver("观察者1号", sub);//观察者1号订阅了首页Observer* ob2 = new ConcreteObserver("观察者2号", sub_jingji);//观察者2号订阅了经济Observer* ob3 = new ConcreteObserver("观察者3号", sub_jingji);//观察者3号订阅了经济//加入订阅sub->attach(ob1);sub_jingji->attach(ob2);sub_jingji->attach(ob3);//发布信息sub->notify("各类新闻汇聚于此");sub_jingji->notify("中指研究院发布了2024年1-8月份全国房价走势,同比和环比均下架");//取消订阅sub_jingji->detach(ob2);sub_jingji->notify("中国汽车出口份额世界第一");delete sub;delete sub_jingji;
}