怎么免费推广自己网站,免费制作网页的网站,湘潭建设公司网站,用fw做明星的网站设计模式之创建型设计模式详解 一、设计模式是什么#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何… 设计模式之创建型设计模式详解 一、设计模式是什么二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何扩展代码4.4、小结 总结 一、设计模式是什么
设计模式总共有23种那什么是设计模式呢设计模式是指在软件开发中经过验证的用于解决在特定环境下重复出现的特定问题的解决方案。
从这个定义可以看出设计模式有很多的限定词比如“特定”、“重复特定”等。那说明什么问题呢说明设计模式在使用的时候它有很多的局限性。所以学习设计模式一定要切入它的一个本质也就是它解决一个什么问题然后再去使用它。当我们不清楚这个设计模式解决什么问题的时候不要轻易的去使用设计模式所以设计模式是适用的好。
设计模式的定义换一句都能够听懂的话就是设计模式是解决软件开发过程中一些问题的固定套路解决问题的固定套路。因此不要过度的去封装或者去使用设计模式除非我们已经确定了这个设计模式就是明确了我们的这个具体需求的变化方向而且这个变化方向的这个点呢经常的出现反复的出现那么我们才会去使用设计模式就是要使用恰当。还有一个就是设计模式类似于一个哲学或者说类似武侠小说里的一个武功秘籍一定要具备一定的工程代码量的才能够精通。但是学习设计模式还是有必要的我们要提前知道设计模式。
设计模式分为创建型、行为型、结构型等等接下来将讲解几个常见的创建型设计模式。
二、模板方法
模板方法是使用最频繁并且是符合设计模式思想的一个设计模式。
1定义定义一个操作中的算法的骨架 而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法的定义很抽象记住它是没有任何作用的关键是怎么去分析它要去分析它的稳定点和变化点。其实模板方法的定义中已经包含了它的稳定点和变化点所以只需要理解模板方法里面描述的稳定点是什么变化点是什么才是能够帮助我们具体应用它。
2解决的问题
稳定点算法骨架。模板方法的稳定点是“定义一个操作的算法的骨架”。关键字就是骨架不改变一个算法的结构好就是稳定点。变化点子流程需要变化。从定义上可以看出模板方法的子类它需要重新定义该算法的某些特定步骤这个是它的变化点。也就是说某一个算法的骨架由多个流程构成模板方法解决的一个问题是这个流程通常是通过一个接口暴露出来要在子类当中去重写重写里面的一些若干子类。比如说一段流程可以用另外一个方法来实现希望不改变一个算法的结构即先后顺序子类要做的是去重写修改它若干的一些流程修改完之后它未来的调用方式是先调用子类的第一个流程然后调用子类的第二个流程再调第三个流程依次类推。所以变化点就是可能对一个或者是多个子流程进行变化。
学习某一个设计模式不需要记住它的定义要记的是它的稳定点是什么它的变化点是什么要以简短的语句来描述它这样才能学会它。
2.1、代码结构
首先来了解一个具体的场景这里举一个最简单的例子帮助理解模板方法 某个品牌动物园有一套固定的表演流程但是其中有若干个表演子流程可创新替换以尝试迭代更新表演流程。 可以看到这个例子跟上面的定义极其的相似通过这个语句里应该很快就能够发现出来它的稳定点有一套固定的表演流程。品牌动物园里面去看这个表演可能有十几个节目动物园不提供单个表演流程收费而是一整套流程收费哪怕是中间进来也要把整个流程的票价买全。然后这个品牌动物园也是有市场竞争的需要去替换某一些子流程比如说有一些国庆节想去进行一个优化以便跟国庆相关一些这时可以安插进去此外还需要去进行迭代创新。所以它的变化点就是里面的子流程呢需要去替换。
接下来看一下代码实现首先是不使用设计模式的代码实现如果代码没有设计模式也要符合设计原则。目的是 1设计模式是由设计原则演变来的 2符合设计原则的代码只需要修改少量代码就能够演变成设计模式。
#include iostream
using namespace std;class ZooShow {
public:ZooShow(int type 1) : _type(type) {}public:void Show() {if (Show0())PlayGame();Show1();Show2();Show3();}private:void PlayGame() {cout after Show0, then play game endl;}bool Show0() {if (_type 1) {// return true;} else if (_type 2 ) {// ...} else if (_type 3) {}cout _type show0 endl;return true;}void Show1() {if (_type 1) {cout _type Show1.1 endl;} else if (_type 2) {cout _type Show1.2 endl;} else if (_type 3) {}cout _type Show1 endl;}void Show2() {if (_type 20) {}cout base Show2 endl;}void Show3() {if (_type 1) {cout _type Show3.1 endl;} else if (_type 2) {cout _type Show3.2 endl;}cout _type Show3 endl;}
private:int _type;
};int main () {ZooShow *zs new ZooShow(1);zs-Show();return 0;
}
以上代码满足的设计原则 1接口隔离原则。类封装的时候使用权限限定词统一或固定的流程用public子流程使用private限定使用户不能单独的调用子流程类与类依赖通过接口实现依赖注入。这里它有两个意思第一个是指类封装的时候怎么实现这个类封装当中的接口隔离呢主要是通过权限限定词动物园有一个固定的表演流程这个表演流程是以统一的方式提供的把这个统一的流程用show()来进行一个封装并且是public限定词来进行封装它。 2最少知道原则。用户只能看到show()其他的子流程不可见。示例中有五个表演子流程这些表演子流程不应该让用户去选择他们不需要的接口所以用private来进行限定。
以上代码破坏了哪些设计原则 1单一职责原则。稳定点是show()子流程是变化点迭代通过_type指定随着迭代次数的增加代码不断膨胀当要知道某次迭代由哪些流程构成时极为困难。这些接口中有多个变化方向随_type变化所以不满足单一职责原则。 2开闭原则。接口中有很多的if判断每次迭代更新版本都修改了类说明类不稳定。每一次迭代更新都需要去修改该类所以应该要对扩展开放。C的扩展方式
通过继承的方式继承无需修改原有类的基础上通过继承的实现对功能的扩展。通过组合的方式通常面对对象当中的组合是指的多态的组合主要通过组合基类的指针。
通过传入一个_type来解决需求是不可取的应该去扩展它。因此符合设计模式的代码如下
#include iostream
using namespace std;// 开闭
class ZooShow {
public:void Show() {// 如果子表演流程没有超时的话进行一个中场游戏环节如果超时直接进入下一个子表演流程if (Show0())PlayGame();Show1();Show2();Show3();}private:void PlayGame() {cout after Show0, then play game endl;}bool expired;// 对其他用户关闭但是子类开放的
protected:virtual bool Show0() {cout show0 endl;if (! expired) {return true;}return false;}virtual void Show2() {cout show2 endl;}virtual void Show1() {}virtual void Show3() {}
};// 框架
// 模板方法模式
class ZooShowEx10 : public ZooShow {
protected:virtual void Show0() {if (! expired) {return true;}return false;}
}class ZooShowEx1 : public ZooShow {
protected:virtual bool Show0() {cout ZooShowEx1 show0 endl;if (! expired) { // 里氏替换return true;}return false;}virtual void Show2(){cout show3 endl;}
};class ZooShowEx2 : public ZooShow {
protected:virtual void Show1(){cout show1 endl;}virtual void Show2(){cout show3 endl;}
};class ZooShowEx3 : public ZooShow {
protected:virtual void Show1(){cout show1 endl;}virtual void Show3(){cout show3 endl;}virtual void Show4() {//}
};
/*
*/
int main () {ZooShow *zs new ZooShowEx10; // 晚绑定// ZooShow *zs1 new ZooShowEx1;// ZooShow *zs2 new ZooShowEx2;zs-Show();return 0;
}
设计符合设计模式的代码时首先希望这些Show0(),Show1,Show2,...是需要暴露给子类去使用的允许子类去重写它。前面的代码中通过private限定词使子类无法够访问到Show0(),Show1,Show2,...private只能自己可以使用当然也可以打破这种限制额外的知识点可以用friend即友元类比如说friend class A;此时这个类A对象是可以访问private的。
所以要修改这个限定词让用户不能够访问它如果用户可以访问那些接口那么用户就可能会自由组合下面的接口改成public的话用户可能会单独去调用它也有可能独的去组合里面的调用但又希望让子类可以去修改它因此可以修改为protectedprotected可以让子类去访问它。注意流程中有一个PlayGame()它是一个中间的、可有可无的一个流程所以在这里用private来修饰因为不希望子类去重写它并没有进行一个改变它依然是private限定词来进行限定。
protect里面可以让子类去重写的一些接口并且使用virtual关键字修饰virtual关键字主要目的是为了使用多态多态可以去通过继承然后重写它的功能。也就是说接口对子类是开放的。
假设要修改这个Show0()子类直接去实现这个Show0()去修改它的子流程就行了通过这种方式就可以直接使用它。在示例中使用一种多态的方式来使用它子类指针通过晚绑定早绑定是指里面没有虚函数的时候它是一个早绑定会把这个类强制转化为这个类因为类很容易转换晚绑定是指里面有一个virtual关键字会走虚函数通过虚函数表指针对晚绑定到实际指向的对象。
通过这种方式来进行重写比如说要有第二次更新迭代修改Show0()和show2()、第三次迭代要修改Show1()和show2()、第四次阶代修改Show1()、show3()和show4()就可以扩展代码。通过这种方式扩展代码之后基类就变得非常的稳定因为每次迭代更新都没有修改里面的代码只是在上面增加代码就相当于整洁的房间有好动的猫这好动的猫就是这四个函数现在用笼子把这个猫关起来了代码上通过扩展的方式来限定它变化的方向利用继承并使用多态的方式进行扩展功能这样一来变化的方向就在一个朝着一个方向变化。
在这里还涉及一个设计原则里氏替换原则。主要是指多态当中的虚函数复写的流程可以看到示例中的Show0()有一个隐藏职责如果这个表演流程没有超时的话就会进行一个游戏环节如果这个表演流程由于某些原因超时了则直接进入下一个表演流程Show1()不会进入中场互动游戏流程。示例中故意增加了这一个需求主要解释一下里氏替换原则。里氏替换原则要求在多态当中的虚函数复写即一定要实现父类方法的职责示例中Show0的职责就是看有没有超时示例中定义了expired来实现。如果子类要去覆写这个Show0()要注意到它里面有一个隐含的职责要遵循它里氏替换 即实现某一个功能时不能只关注子流程的功能而忘记他的隐藏职责如果直接一个return true就会不管超没超时都去玩游戏。所以去复写它的方法的时候一定要实现他的职责这就是里氏替换一定要把隐含职责实现起来没有超时去玩游戏超时了则进入下一个游戏。
通过这样子的修改代码就满足了设计原则。在迭代出一个设计模式的时候只要让他满足一些一些设计原则就可以自动演变成设计模式只需要修改少量的代码第一个把这个限定词改成protected第二个在前面加一个virtual关键字就实现了模板方法。
模板方法的代码结构
在基类中用一个public并定义出一个骨架流程接口。有一个protected限定词所有的子流程对子类开放并且是虚函数。多态的使用方式。也就是用基类指针指向子类对象。
注意在工作当中发现有一个public并提供了一个骨架流程接口比如说某一个方法骨架流程当中它里面的一些流程是对子类使用的protected的关键字并且看到虚函数90%是模板方法。判定方式
有一个protected限定词就是对子类开放的。一些流程是虚函数并且子类当中覆写这些虚函数。用基类指针指向一个子类的对象。
根据以上三点就可以确定90%是模板方法一定要记住它的代码结构是什么样子好方便去看代码的时候一眼就能看出来它是什么设计模式。
2.2、符合的设计原则
模板方法符合哪些设计原则呢最开始的时候只符合少量的设计原则慢慢的它就迭代出模板方法。模板方法符合的设计原则如下
单一职责。基类非常的稳定只有一个职责职责就是骨架骨架接口都用protected进行或者是private进行限定了限定以后用户是不能够访问它的。开闭原则。对扩展开放对修改关闭。要改变它只能通过继承的方式通过覆写方式来实现利用继承的方式进行扩展来开放。依赖倒置。子类的扩展时需要依赖基类的虚函数实现使用者只依赖接口。依赖导致有两层含义一个是依赖接口第二个是依赖虚函数。所有的子类的这一个类的实现都需要依赖基类的虚函数来进行实现还要实现它隐含职责。即具体的子类实现依赖具体的某一个接口或者是函数。封装了变化点。通过protected让它限定住变化的地方让子类去扩展。接口隔离。有两种分别是类与类之间的一个隔离和类的封装的本身通过限定词去进行隔离。最小知道原则。对于用户而言只需要知道一个接口然后直接调用。
2.3、如何扩展代码
这个模板方法分析特别的详细读者可以按照上述方式去分析设计模式。扩展方式主要通过写一个类去继承基类来修改里面的子流程这对于初学者代码量还不够的朋友一定要掌握即怎么去使用这个设计模式设计模式已经写好了怎么在上面扩展代码需要掌握的。示例
// 模板方法模式
class ZooShowEx10 : public ZooShow {
protected:virtual void Show0() {if (! expired) {return true;}return false;}
}
// 调用
int main () {ZooShow *zs new ZooShowEx10; // 晚绑定// ZooShow *zs1 new ZooShowEx1;// ZooShow *zs2 new ZooShowEx2;zs-Show();return 0;
}
扩展代码的方法步骤
实现子类继承基类复写子流程。通过多态调用方式去使用它。
2.4、小结
经典应用场景模板方法是使用最频繁的几乎每一个项目都会使用这个模板方法项目中这个设计模式肯定是必然会出现。
要点 1最常用的设计模式子类可以复写父类子流程使父类的骨架流程丰富 2反向控制流程的典型应用 3父类 protected 保护子类需要复写的子流程这样子类的子流程只能父类来调用。
本质通过固定算法骨架来约束子类的行为。 结构图 思维导图
三、观察者模式
1观察者模式同样是使用的比较频繁的一种设计模式首先看一下它的定义定义对象间的一种一对多变化的依赖关系以便当一个对象(Subject)的状态发生改变时所有依赖于它的对象都得到通知并自动更新。这是设计模式的作者给的定义不用记住它要做的是去分析它的稳定点和变化点即它解决的问题是什么。
2解决的问题
分析一下定义“对象间的一种一对多”这句话要注意一个对多个的依赖关系“以便当一个对象发生改变时”中的“一个对象”是指“一对多”当中的“一”“所有依赖它的对象都得到通知并自动更新”中“所有依赖它的对象”是指这个“多”。它的稳定点显然是这种一对多的依赖关系它主要描述的就是“一”变化的时候“多”跟着变化这种关系是稳定的也是它的一个职责。它的变化点是指“多”增加和“多”减少。注意这个“一”对“多”没明确多少个就是它变化的地方观察者模式要解决的问题就是这个变化点让这个变化点在有限范围内变化。
稳定点“一”对“多”变化的依赖关系“一”变化“多”跟着变化。变化点“多”增加“多”减少。
分析设计模式就是分析它的稳定点和分析它的变化点。
3.1、代码结构
代码结构是最重要的要知道怎么用代码来实现需求实现稳定点和变化点。首先有这样一个需求 气象站发布气象资料给数据中心数据中心经过处理将气象信息更新到两个不同的显示终端A 和B。 这个设备终端会一直改变可能会是手机、是电脑、是平板、其他具体的某一个平台等等也可能会有多个。这里“一对多”的“一”是指这个数据中心它会处理数据从而产生变化然后会让其他的终端发生改变即数据中心就是专门来处理气象数据的处理完气象数据之后要把数据发生变更所有的终端都要跟着数据中心计算的结果来发生改变。
没有使用设计模式的时候是怎么进行实现的
class DisplayA {
public:void Show(float temperature);
};class DisplayB {
public:void Show(float temperature);
};class DisplayC {
public:void Show(float temperature);
}class WeatherData {
};class DataCenter {
public:void TempNotify() {DisplayA *da new DisplayA;DisplayB *db new DisplayB;DisplayC *dc new DisplayC;// DisplayD *dd new DisplayD;float temper this-CalcTemperature();da-Show(temper);db-Show(temper);dc-Show(temper);dc-Show(temper);}
private:float CalcTemperature() {WeatherData * data GetWeatherData();// ...float temper/* */;return temper;}WeatherData * GetWeatherData(); // 不同的方式
};int main() {DataCenter *center new DataCenter;center-TempNotify();return 0;
}先实现不同的终端啊比如说有DisplayA 、DisplayB 、DisplayC 三个终端还有一个气象中心类、一个数据中心类数据中心做数据处理把数据发给气象中心然后气象中心会去计算数据计算这个数据之后产生的所有变化都发送给终端终端依赖于数据的变更。不符合设计模式的情况下首先new一个数据中心所有的终端也new出来然后用数据中心计算一下资料产生数据包括比如说天气、建议穿多少衣服、适不适合洗车等等这些其他的数据计算完之后会有一个具体的数据相对应的所有的终端都去更新这个数据。
这种实现方式有什么问题呢能不能够实现变化的需求呢可以看到当终端增加之后总是要修改代码TempNotify添加一个接口这样一来接口就变得不稳定了因为每一次增加数据或者修改数据接口都要跟着变化。
因此以上代码每次新增设备都需要修改代码。这使稳定点变为不稳定不符合设计原则 / 设计模式。 符合设计模式的实现如下
#include list
#include algorithm
using namespace std;
//
class IDisplay {
public:virtual void Show(float temperature) 0;virtual ~IDisplay() {}
};class DisplayA : public IDisplay {
public:virtual void Show(float temperature) {cout DisplayA Show endl;}
private:void jianyi();
};class DisplayB : public IDisplay{
public:virtual void Show(float temperature) {cout DisplayB Show endl;}
};class DisplayC : public IDisplay{
public:virtual void Show(float temperature) {cout DisplayC Show endl;}
};class DisplayD : public IDisplay{
public:virtual void Show(float temperature) {cout DisplayC Show endl;}
};class WeatherData {
};// 应对稳定点抽象
// 应对变化点扩展继承和组合
class DataCenter {
public:void Attach(IDisplay * ob) {// 添加设备}void Detach(IDisplay * ob) {// 移除设备}void Notify() {// 一变化多跟着变化float temper CalcTemperature();for (auto iter : obs) {iter.Show(temper);}}// 接口隔离
private:WeatherData * GetWeatherData();float CalcTemperature() {WeatherData * data GetWeatherData();// ...float temper/* */;return temper;}std::listIDisplay* obs;
};int main() {// 单例模式DataCenter *center new DataCenter;// ... 某个模块IDisplay *da new DisplayA();center-Attach(da);// ...IDisplay *db new DisplayB();center-Attach(db);IDisplay *dc new DisplayC();center-Attach(dc);center-Notify();//-----center-Detach(db);center-Notify();//....center-Attach(dd);center-Notify();return 0;
}
为了满足设计模式可以使用面向接口编程首先提供一个接口Idisplay所有的终端都要继承自这个具体的接口具体的终端的改变不应该影响数据中心接口的稳定性不稳定主要来源于具体终端的增加和减少设计模式就是要让这个稳定点的变得稳定可以通过给它增加一个存储容器比如说有一个容器std::listIDisplay* obs;把所有的终端都存储好增加一个接口就往里面添加一个终端来监听终端想不再监听这个去数据中心的数据变化就调用Detach这些接口是对用户开放的用户可以往里面增加终端也可以往里面解除终端示例中还有一个非常重要的功能就是Notify()进行广播数据数据发生变化时通知所有的终端跟着变化。
现在为了让这些不同的终端能够进行统一管理示例中添加了IDisplay 类用来描述这个具体终端的功能这里是显示的功能它是没有具体实现的是一个纯虚函数具体的终端如果要往里面增加就去继承它继承它之后实现显示的功能因为每一个终端它的显示的方式不一样这样一来解决了这个具体的问题让原来的不稳定现在变得稳定。
应对稳定点通过抽象的方式来解决应对变化点通过各种的方式来进行解决和扩展多态和组合。注意稳定点也是需要抽象的示例中是怎么让这个数据中心变得稳定呢因为稳定是“一”数据中心对“多”list的依赖关系就可以依赖具体的接口。
未使用设计模式之前示例是不稳定的它不稳定的原因是因为终端端的增加或者是减少要修改代码设计模式通过抽象它的稳定点“一”对“多”的依赖关系“一”是数据中心“多”是把它抽象成一个具体的接口好因为设备可能每个都不一样但是他们都有一个相同的地方显示功能然后让他们的“一”对“多”的依赖建立在接口上面并且用一个容器std::listIDisplay*去保存它。因为它的“多”会有的时候增加有的时候减少。示例中提供Attach和Detach给用户使用以方便动态的添加或减少设备另外也添加了Notify接口为“多”服务里面使用循环来遍历Show接口。
3.2、符合的设计原则
这里分析一下观察者模式符合哪些设计原则
面向接口编程。以上述例子分析针对稳定点代码声明了某一个接口使程序根本不关注是哪一个终端设备这个接口中有一个“显示”功能的只需要知道对象所具有的接口就行了因为所有的终端都有一个显示功能数据中心只关注终端有没有显示功能具体是什么对象它根本不关注只需要有一个显示功能就行了数据中心把具体的数据算完之后直接调用这一个显示接口就行了即 把终端的所有的显示接口都调用一下那么这个一对多的关系就跟着变化了。针对变化点多可能增加好多可能减少每个终端都是自己调用终端自己去变化。接口隔离。类与类依赖接口隔离。它有两种一种是类的封装通过限定值来实现这个接口隔离类的封装当中用限定词public protect private去隔离具体的实现决定哪些数据要暴露给用户使用哪些不暴露给用户使用不应该让用户依赖他们不需要的接口另外一种是指类与类之间的关系好即类与类的依赖应该依赖具体的接口通过接口来进行隔离两个类。这里所说的符合接口隔离原则主要是指第二种。示例中的“一”对“多”是数据中心与不同的设备所属的两个类他们的依赖是通过一个具体的接口这个接口起到了一个结耦合的作用这个也是所说的组合接口。封装变化点。如上述代码的Attach()和Detach()。前面的模板方法是用protect来进行限定实现封装变化点使其他人不能够访问但是可以让子类去重写通过重写的方式来进行扩展功能在这里的封装变化点因为变化点是“多”的增加和“多”的减少因此通过提供两个接口一个是Attach解决“多”增加的问题Detach解决“多”减少的问题实现封装变化点。
3.3、如何扩展代码
如何扩展这个代码呢即如何扩展这个观察者模式呢以上述例子分析
首先是终端需要继承class IDisplay接口比如说现在增加了一个终端只需要写少量的代码就可应对需求的变化增加一个具体的类这个类继承class IDisplay接口然后在某一个逻辑当中去写一个数据中心center这个center在实际开发过程当中肯定是个单例模式它会首先需要把终端给添加进来好接下来数据中心center调用Notify主要加的代码只有两部分。
1继承实现接口。 2添加终端调用Attach即“多”增加。 3移除终端调用Detach即“多”减少。
3.4、小结
1要点
观察者模式使得我们可以独立地改变目标与观察者从而使二者之间的关系松耦合观察者自己决定是否订阅通知目标对象并不关注谁订阅了观察者不要依赖通知顺序目标对象也不知道通知顺序常用在基于事件的ui框架中也是 MVC 的组成部分常用在分布式系统中、actor框架中
2本质触发联动。 3结构图
观察者模式的定义不需要去记住需要知道的是它解决什么问题它的一个稳定点是什么稳定点是一对多的依赖关系一变化多也跟着变化它的变化点是什么呢它的变化点是这个“多”它可能会增加也有可能会减少。基于这种场景既包含这个稳定点又包含这种变化点的场景好就是使用观察的模式的场景。
观察者模式的代码结构是通过有一个“一”的类还有一个“多”的类“多”是通过接口的方式来实现“一”的类里面有一个容器容纳所有的“多”“一”对“多”的依赖关系就通过这个接口来进行隔离。比如示例中因为只关注这个具体的“显示”的功能并不关注终端是哪一个对象或者是哪一个类只关注有没有“显示”功能。另外会有一个具体的接口抽象出这个“变化”用一个接口来实现不同的变化点就继承这一个接口实现它的“显示”功能。
使用会有一个单例“一”对“多”中的“一”需要加“显示”功能“一” 定时会去调用Notify或者有变化的时候主动调Notify“多”不用关心这个这个是“一”的职责不是“多”的职责。
怎么去扩展代码呢扩展代码我们只需要写两部分代码
继承接口。在某个模块中Attach到“一”当中。
思维导图
四、策略模式
1定义定义一系列算法把它们一个个封装起来并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。
经过前面两种设计模式的讲解我们应该知道学习一个策略模式的固定套路分析稳定点和变化点。这个定义不是很重要看不看得懂也没关系没看懂也没关系。它解决什么问题呢本质上就是它的稳定点是什么它的变化点是什么。
2解决的问题
稳定点客户程序与算法的调用关系。定义中的“该模式使得算法可独立于使用它的客户程序而变化”就是稳定点这个稳定点是指客户程序去调用这个具体接口的一种方式这种调用关系是一个稳定点。客户程序反正是要调用某一个算法客户程序自己来提供一些参数。变化点新增算法和算法内容变化。有很多很多不同的算法可以去相互替换客户程序不需要去关注不同的变化反正是要调用这样的一个算法然后客户端自己提供一些参数得到符合所需要的一个结果。这一系列的方法他们是一种平行的关系他们都是可替换的一个关系。这里解释这么多可能也没什么效果后面看代码就明白了。
策略模式的稳定点只会是一对一的关系调用某一个算法但是有很多的候选项传入哪一个就调哪一个非常的简单。
4.1、代码结构
首先看一个背景 某商场节假日有固定促销活动为了加大促销力度现提升国庆节促销活动规格。 这个背景的稳定点是“促销活动”这个促销活动会去调用一个促销活动的算法比如说买800送100买、买900送多少200活动打几折等等这个就是具体的一些算法。这个活动是整个商场的活动可能去轮番的选择总之会选择一个算法来做促销。
变化点就是某商场它有一个统一的活动可能是打折、满减、送卡送礼品等等那么现在为了增加增加促销力度提升某一个规格或者增加一个活动比如增加一个圣诞节、参与某一个什么活动这些就是变化点。相对应的稳定点就是节假日会调用一个算法来实现这个促销活动。
我们来看具体的一个实现当没有使用这个设计模式的时候的实现如下
enum VacationEnum {VAC_Spring,VAC_QiXi,VAC_Wuyi,VAC_GuoQing,VAC_ShengDan,
};class Promotion {VacationEnum vac;
public:double CalcPromotion(){if (vac VAC_Spring {// 春节}else if (vac VAC_QiXi) {// 七夕}else if (vac VAC_Wuyi) {// 五一}else if (vac VAC_GuoQing) {// 国庆}else if (vac VAC_ShengDan) {}}};一个Promotion的促销活动里面有一个算法根据不同的活动春节活动、七夕活动、五一活动、国庆活动、圣诞活动等等会有相对应的算法里面很多if else的使用根据不同的这个规格去调用不同的算法这些算法都是并行的一种关系但是最终只会调用一个算法这个就是一个普通的一个实现。
策略模式就是用来消除if else的使用。看一下符合设计模式的一种方式首先对于稳定点是要用抽象去解决对于变化点通过扩展的方式去解决它即继承和组合组合是指多态的组合不是指组合对象。符合设计模式的实现如下
class Context {};// 稳定点抽象去解决它
// 变化点扩展继承和组合去解决它
class ProStategy {
public:virtual double CalcPro(const Context ctx) 0;virtual ~ProStategy();
};
// cpp
class VAC_Spring : public ProStategy {
public:virtual double CalcPro(const Context ctx){}
};
// cpp
class VAC_QiXi : public ProStategy {
public:virtual double CalcPro(const Context ctx){}
};
class VAC_QiXi1 : public VAC_QiXi {
public:virtual double CalcPro(const Context ctx){}
};
// cpp
class VAC_Wuyi : public ProStategy {
public:virtual double CalcPro(const Context ctx){}
};
// cpp
class VAC_GuoQing : public ProStategy {
public:virtual double CalcPro(const Context ctx){}
};class VAC_Shengdan : public ProStategy {
public:virtual double CalcPro(const Context ctx){}
};class Promotion {
public:Promotion(ProStategy *sss) : s(sss){}~Promotion(){}double CalcPromotion(const Context ctx){return s-CalcPro(ctx);}
private:ProStategy *s;
};int main () {Context ctx;ProStategy *s new VAC_QiXi1();Promotion *p new Promotion(s);p-CalcPromotion(ctx);return 0;
}这里的稳定点是这个调用关系提供了这样的一个接口class ProStategy来抽象这个稳定点这个接口就是具体的一个策略类。另外还有一个具体的Promotion 是“促销活动”目的应该是让Promotion这个类稳定。前面的代码中这个类不稳定的原因是根据不同的活动来进行if else判断现在让这个类和具体的活动用接口进行隔离要符合一个设计原则需要进行一个抽象采用接口ProStategy *s隔离的方式隔离这个促销类跟促销活动算法类。
这里还采用一种依赖注入的方式引入了一个新的技术点接口隔离有两种一个是类的封装一个是类与类依赖一个接口类与类依赖一个接口前面讲解过是采用一个容器容器存储接口在这里是采用的另外一种解决这个接口隔离的问题也就是依赖注入。前面讲过的通过具体的函数其实也是依赖注入通过函数把具体的接口的传进来解决类与类之间的一个隔离的问题也就是两个类的依赖性的只建立在一个接口上面它也是一种依赖注入。通过这种依赖注入可以让整个类变得稳定了。未来增加任何的促销活动都不需要修改促销类它永远是稳定的因为它只需要调用一下算法关于算法是怎么改变的可以通过注入的方式来告诉这个促销类应该使用哪一个算法。
这样一来变化点都不会影响稳定点了。增加算法通过继承具体的接口ProStategy 然后再去实现这一个CalcPro函数。未来的变化点当中可能还要去进行修改算法内容可以直接在自己的实现中修改CalcPro函数就行了不会影响其他的接口也不会影响Promotion促销类。
依赖注入有一个具体的接口指针通过函数或者构造函数传参进来的这种方式叫做依赖注入。
private:ProStategy *s;
// ...Promotion(ProStategy *sss) : s(sss){}~Promotion(){}
代码结构小结 1基类有接口。 2客户程序与算法的调用关系有一个类或接口有调用封装。 3通过依赖注入调用。 4具体的调用。
4.2、符合的设计原则
接口隔离。解决的是一个类与类之间的一个接口隔离这里的解决方案采用依赖注入这是一个新的词专门用于解决两个类只依赖一个接口通过一个地方解决两个类的依赖。面向接口编程。只关注接口所需的的功能就行了接口里面实现了其他的功能根本不关心比如国庆可能还实现了其他的职责加了任何方法都跟促销没什么关系可以加任意的方法因为只关注的是某一个接口这个接口就是某一个具体的算法即面向接口编程。开闭原则。对扩展开放对修改关闭类是没法修改的只通过依赖注入的方式来进行改变可以用构造函数也可以使用具体的某一个函数接口实现。
4.3、如何扩展代码
继承接口。写一个接口扩展它然后实现具体的算法解决新加算法的问题。通过依赖注入调相对应的函数。使用时new一个具体的一个算法然后通过依赖注入的方式加到promotion当中去promotion调用这个接口。
4.4、小结
要点
策略模式提供了一系列可重用的算法从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。策略模式消除了条件判断语句也就是在解耦合。
本质 分离算法选择实现。
结构图 思维导图 总结
本文介绍了设计模式的概念及其在软件开发中的应用。首先解释了设计模式是什么它是一种解决特定问题的可复用且经过验证的解决方案。然后重点介绍了三种常见的设计模式模板方法、观察者模式和策略模式。
在模板方法部分详细说明了代码结构和模板方法的作用。模板方法是一种行为型设计模式它定义了一个算法的骨架将一些步骤延迟到子类中实现。文章还讨论了符合的设计原则并提供了如何扩展代码的实用建议。
接下来介绍了观察者模式的代码结构和设计原则。观察者模式是一种发布-订阅模式它用于定义对象之间的一对多依赖关系。文章强调了观察者模式的灵活性和可扩展性并提供了如何扩展代码的示例。
最后讨论了策略模式的代码结构和设计原则。策略模式是一种行为型设计模式它定义了一系列可互换的算法并使其能够独立于客户端而变化。文章强调了策略模式的可维护性和可测试性并给出了扩展代码的建议。
通过阅读本文读者将对设计模式有一个清晰的理解并了解到如何应用模板方法、观察者模式和策略模式来解决实际的软件开发问题。无论是初学者还是有经验的开发人员都能从本文中获得有益的知识和实用的技巧。