参考链接:设计模式
overview
行为模式(使得对象间的交互更加高效)里面有10个。(责任链,命令,迭代器,中介者,备忘录,观察者,状态,策略,模板,访问者)但是我常用的(好吧,只是用过的)是观察和策略。下面进行详细论述的就是:
1、观察者模式
2、策略模式
1、观察者模式
1.1 适用情境
- 一个对象的状态的改变与另一个对象的状态的改变有关。
- 应用中的一些对象仅仅在有限时间或者特定范围内必须要观察其他对象。
1.2 起作用方式
我们将拥有值得关注状态的对象称为发布者,将关注特定状态的对象称为订阅者。
订阅者使用发布者的订阅方式,让发布者给自己加到对应的订阅列表上(这个订阅列表是发布者维护的);当然,订阅者也可以使用发布者的取消订阅方式,让发布者给订阅者从相对应的订阅列表上删除。
订阅者需要明确自己被通知的条件,以及通知后需要传递的信息,这样发布者在遍历订阅列表时,确定每个订阅者的条件是否被满足,若满足,则传递订阅者需要的信息即可,若不满足,就不通知。
1.3 具体例子
/**
* Observer Design Pattern
*
* Intent: Lets you define a subscription mechanism to notify multiple objects
* about any events that happen to the object they're observing.
*
* Note that there's a lot of different terms with similar meaning associated
* with this pattern. Just remember that the Subject is also called the
* Publisher and the Observer is often called the Subscriber and vice versa.
* Also the verbs "observe", "listen" or "track" usually mean the same thing.
*/
#include <iostream>
#include <list>
#include <string>
class IObserver {
public:
virtual ~IObserver(){};
virtual void Update(const std::string &message_from_subject) = 0;
};
class ISubject {
public:
virtual ~ISubject(){};
virtual void Attach(IObserver *observer) = 0;
virtual void Detach(IObserver *observer) = 0;
virtual void Notify() = 0;
};
/**
* The Subject owns some important state and notifies observers when the state
* changes.
*/
class Subject : public ISubject {
public:
virtual ~Subject() {
std::cout << "Goodbye, I was the Subject.\n";
}
/**
* The subscription management methods.
*/
void Attach(IObserver *observer) override {
list_observer_.push_back(observer);
}
void Detach(IObserver *observer) override {
list_observer_.remove(observer);
}
void Notify() override {
std::list<IObserver *>::iterator iterator = list_observer_.begin();
HowManyObserver();
while (iterator != list_observer_.end()) {
(*iterator)->Update(message_);
++iterator;
}
}
void CreateMessage(std::string message = "Empty") {
this->message_ = message;
Notify();
}
void HowManyObserver() {
std::cout << "There are " << list_observer_.size() << " observers in the list.\n";
}
/**
* Usually, the subscription logic is only a fraction of what a Subject can
* really do. Subjects commonly hold some important business logic, that
* triggers a notification method whenever something important is about to
* happen (or after it).
*/
void SomeBusinessLogic() {
this->message_ = "change message message";
Notify();
std::cout << "I'm about to do some thing important\n";
}
private:
std::list<IObserver *> list_observer_;
std::string message_;
};
class Observer : public IObserver {
public:
Observer(Subject &subject) : subject_(subject) {
this->subject_.Attach(this);
std::cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\".\n";
this->number_ = Observer::static_number_;
}
virtual ~Observer() {
std::cout << "Goodbye, I was the Observer \"" << this->number_ << "\".\n";
}
void Update(const std::string &message_from_subject) override {
message_from_subject_ = message_from_subject;
PrintInfo();
}
void RemoveMeFromTheList() {
subject_.Detach(this);
std::cout << "Observer \"" << number_ << "\" removed from the list.\n";
}
void PrintInfo() {
std::cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << "\n";
}
private:
std::string message_from_subject_;
Subject &subject_;
static int static_number_;
int number_;
};
int Observer::static_number_ = 0;
void ClientCode() {
Subject *subject = new Subject;
Observer *observer1 = new Observer(*subject);
Observer *observer2 = new Observer(*subject);
Observer *observer3 = new Observer(*subject);
Observer *observer4;
Observer *observer5;
subject->CreateMessage("Hello World! :D");
observer3->RemoveMeFromTheList();
subject->CreateMessage("The weather is hot today! :p");
observer4 = new Observer(*subject);
observer2->RemoveMeFromTheList();
observer5 = new Observer(*subject);
subject->CreateMessage("My new car is great! ;)");
observer5->RemoveMeFromTheList();
observer4->RemoveMeFromTheList();
observer1->RemoveMeFromTheList();
delete observer5;
delete observer4;
delete observer3;
delete observer2;
delete observer1;
delete subject;
}
int main() {
ClientCode();
return 0;
}
1.4 实际运用过程中常见的问题-循环调用
在实际的工程中,为了便于维护,我们肯定需要讲发布者类和观察者类分开写,但是因为观察者需要用发布者的加订阅列表的方法,发布者需要用到观察者的通知方法,所以在包含头文件的时候很容易造成循环调用的问题。
最简单的解决方法就是善用抽象接口+前向声明。
2、策略模式
2.1 适用情境
这个情境得concrete一点才能理解,那么我就举一个比较实际的例子。
假设程序要实现的目标是从a地点到b地点,你可以选择坐公交车或者坐地铁或者走路或者开车,这每一种选择都是一种策略。
你希望提供给程序不同的交通工具的参数,程序给你生成不同的路线。
现在停在这里想一下,你的本能是如何设计这个类图使得后期公交线路的变化,地铁线路的变化以及开车线路的变化互不影响,同时方便后期增加新线路-比如增加云轨线路和高铁线路和轮船线路?
那必然是起点和终点作为上下文类,将对各个线路的规划算法抽象到一组独立的类当中去,这组独立的类就可以被称为策略类。
所以总结下,它十分适合:
- 1.使用对象中不同的算法变体(这些变体的目的是一样的,比如上面的例子中的让你从a到b);
- 2.在运行时再判断使用什么算法(说人话就是实现不同的对象用不同的算法)
- 3.定义的一组算法中,算法差异不多(根据上面的例子来看,都是交通工具,大差不差的)
- 4.这个算法在上下文逻辑中不是特别重要(按照上面的例子来看,就是你瞎走走到终点都可以)
2.2 具体例子
/**
* The Strategy interface declares operations common to all supported versions
* of some algorithm.
*
* The Context uses this interface to call the algorithm defined by Concrete
* Strategies.
*/
class Strategy
{
public:
virtual ~Strategy() = default;
virtual std::string doAlgorithm(std::string_view data) const = 0;
};
/**
* The Context defines the interface of interest to clients.
*/
class Context
{
/**
* @var Strategy The Context maintains a reference to one of the Strategy
* objects. The Context does not know the concrete class of a strategy. It
* should work with all strategies via the Strategy interface.
*/
private:
std::unique_ptr<Strategy> strategy_;
/**
* Usually, the Context accepts a strategy through the constructor, but also
* provides a setter to change it at runtime.
*/
public:
explicit Context(std::unique_ptr<Strategy> &&strategy = {}) : strategy_(std::move(strategy))
{
}
/**
* Usually, the Context allows replacing a Strategy object at runtime.
*/
void set_strategy(std::unique_ptr<Strategy> &&strategy)
{
strategy_ = std::move(strategy);
}
/**
* The Context delegates some work to the Strategy object instead of
* implementing +multiple versions of the algorithm on its own.
*/
void doSomeBusinessLogic() const
{
if (strategy_) {
std::cout << "Context: Sorting data using the strategy (not sure how it'll do it)\n";
std::string result = strategy_->doAlgorithm("aecbd");
std::cout << result << "\n";
} else {
std::cout << "Context: Strategy isn't set\n";
}
}
};
/**
* Concrete Strategies implement the algorithm while following the base Strategy
* interface. The interface makes them interchangeable in the Context.
*/
class ConcreteStrategyA : public Strategy
{
public:
std::string doAlgorithm(std::string_view data) const override
{
std::string result(data);
std::sort(std::begin(result), std::end(result));
return result;
}
};
class ConcreteStrategyB : public Strategy
{
std::string doAlgorithm(std::string_view data) const override
{
std::string result(data);
std::sort(std::begin(result), std::end(result), std::greater<>());
return result;
}
};
/**
* The client code picks a concrete strategy and passes it to the context. The
* client should be aware of the differences between strategies in order to make
* the right choice.
*/
void clientCode()
{
Context context(std::make_unique<ConcreteStrategyA>());
std::cout << "Client: Strategy is set to normal sorting.\n";
context.doSomeBusinessLogic();
std::cout << "\n";
std::cout << "Client: Strategy is set to reverse sorting.\n";
context.set_strategy(std::make_unique<ConcreteStrategyB>());
context.doSomeBusinessLogic();
}
int main()
{
clientCode();
return 0;
}
2.3 常见问题
在上面的例子里面,一切都很美好,但是你只可以选择一种交通工具实现从a到b,如果你中途想要换交通工具怎么办?说得专业点就是你想要使用组合的策略,因为单一的策略不能够满足你的需求。
本能反应是给a到b继续拆分最小项,在每个最小项里面使用单一策略。这是一个解决方法,但是如果你获取的信息不足以支撑你继续拆分怎么办?这个时候你就可以在上下文类里面增加一个组合策略的方法。
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <utility>
#include <algorithm>
using namespace std;
/**
* The Strategy interface declares operations common to all supported versions
* of some algorithm.
*
* The Context uses this interface to call the algorithm defined by Concrete
* Strategies.
*/
class Strategy
{
public:
virtual ~Strategy() = default;
virtual std::string doAlgorithm(std::string_view data) = 0;
};
/**
* Concrete Strategies implement the algorithm while following the base Strategy
* interface. The interface makes them interchangeable in the Context.
*/
class ConcreteStrategyA : public Strategy
{
public:
std::string doAlgorithm(std::string_view data) override
{
std::string result(data);
std::sort(std::begin(result), std::end(result));
std::cout << "ConcreteStrategyA:" << result << std::endl;
return result;
}
};
class ConcreteStrategyB : public Strategy
{
std::string doAlgorithm(std::string_view data) override
{
std::string result(data);
std::sort(std::begin(result), std::end(result), std::greater<>());
std::cout << "ConcreteStrategyA:" << result << std::endl;
return result;
}
};
/**
* The Context defines the interface of interest to clients.
*/
class Context
{
/**
* @var Strategy The Context maintains a reference to one of the Strategy
* objects. The Context does not know the concrete class of a strategy. It
* should work with all strategies via the Strategy interface.
*/
private:
///上下文里面的这个智能指针让上下文类可以去和一个,注意是一个,策略相关联
//std::unique_ptr<Strategy> strategy_;
Strategy* strategy_;
///原来这里没有存放策略的vector,现在这里加一个vector,里面可以存放多个策略,注意是多个
std::vector<Strategy*> strategyies_;
/**
* Usually, the Context accepts a strategy through the constructor, but also
* provides a setter to change it at runtime.
*/
public:
Context()
{
}
/**
* Usually, the Context allows replacing a Strategy object at runtime.
*/
//usually 有,但是在这个简单的例子里面没有,问就是为了简单
/**
* The Context delegates some work to the Strategy object instead of
* implementing +multiple versions of the algorithm on its own.
*/
void doSomeBusinessLogic()
{
this->add_strategies();
this->execute_strategies();
}
//这里为了方便理解,直接写死增加的策略方式
//实际应用过程中可以增加入参,根据入参使用不同的组合方式
void add_strategies() {
strategy_ = new ConcreteStrategyA;
strategyies_.push_back(strategy_);
strategy_ = new ConcreteStrategyB;
strategyies_.push_back(strategy_);
strategy_ = new ConcreteStrategyA;
strategyies_.push_back(strategy_);
strategy_ = new ConcreteStrategyA;
strategyies_.push_back(strategy_);
strategy_ = new ConcreteStrategyB;
strategyies_.push_back(strategy_);
}
//这里再增加一个执行策略的函数
void execute_strategies() {
if (strategyies_.empty())return;
while (!(strategyies_.empty())) {
strategy_ = strategyies_.back();
strategyies_.pop_back();
strategy_->doAlgorithm("qazwsxedcrfvtgbyhnujmikolp");
delete strategy_;
}
}
};
/**
* The client code picks a concrete strategy and passes it to the context. The
* client should be aware of the differences between strategies in order to make
* the right choice.
*/
void clientCode()
{
Context context;
std::cout << "Client: Strategy is set to normal sorting.\n";
context.doSomeBusinessLogic();
std::cout << "\n";
}
int main()
{
clientCode();
return 0;
}