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;
}