1. 设计模式概述
(1)目标:面向对象系统的分析和设计实际上追求的就是两点:高内聚和低耦合;
(2)核心思想:隔离变化,封装变化;把变化的抽象为不变的,并且封装起来,提供不变的接口;
(3)设计模式的地位:设计模式在面向对象中的作用,就相当于数据结构在面向过程中的作用一样;本质是一套千人总结的代码可重复性高的几套程序整体架构;
(4)设计模式的分类
创建型,隐藏创建对象的方式,主要是用函数封装变化的输入,尤其是new的解耦(不在主函数里面new对象,而是用类封装起来,降低与类型名之间的耦合度) | 工厂方法模式 | factory,定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中 |
抽象工厂模式 | abstract,提供一个创建一系列相关或者相互依赖的接口,而无需指定它们具体的类 | |
单例模式 | singleton,保证一个类仅有一个实例,并提供一个访问它的全局访问点 | |
结构型,利用组合的方式将抽象的类组合起来,重在依赖于抽象类,重在类 | 代理模式 | proxy,为其他对象提供一种代理以控制对这个对象的访问 |
适配器模式 | adapter,是将一个类的接口转换成客户希望的另外的一个接口,使得原来由于接口不兼容而不能一起工作的那些类可以一起工作 | |
组合模式 | 是将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性 | |
外观模式 | 是为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 | |
行为型,将变化的成员函数进行抽象,依赖于抽象,重在方法 | 模板方法模式 | 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 |
观察者模式 | 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新 |
(5)设计模式的六大原则(重点)
①开放封闭原则:当需要新增功能时,只增加新的类(新的代码),不修改原来的类,不修改源代码;
②依赖倒转原则:对于类中的成员函数方法,要依赖于抽象(抽象类的统一接口),而不是依赖于具体的实现类;
③接口隔离原则:类之间的依赖关系应该建立在最小的接口上,就是说接口的参数个数要少,这样系统越灵活;如果参数个数过多,则开发难度增加,可维护性降低;
④里氏替换原则:任何基类都可以出现的地方,子类一定可以出现;主要是采用继承的方式,继承方式的优点(提高代码的复用性,与可扩展性),缺点(继承是侵入式的,只要继承就拥有了父类的属性和方法,降低了代码的灵活性;增强了耦合性,当基类的属性或方法改变时,必须考虑子类的修改,可能会有大段代码需要重构);补充的四层含义:
❶派生类必须实现基类的抽象方法,否则派生类也无法实例化对象;但是一般不覆写基类的非抽象(已实现)的方法;
❷派生类可增加自己特有的函数方法
❸当派生类覆写基类的函数方法时,函数的输入参数应该更加宽松;
❹当派生类的方法实现基类的抽象函数方法时,函数的返回值应该比基类更加严格;
总结:将子类定义为抽象类,并定义抽象的成员函数方法,派生类继承后,让派生类重新定义这些方法;此时基类是抽象类,也不可以实例化对象;
⑤合成复用原则:尽量使用组合的方式实现复用,就是在自己的类里面定义其他类的属性或者在自己的成员函数中的参数是其他类的对象或指针(这就是两种实现组合的方式),这样就可以该成员属性调用其他类的成员函数方法;引申出来为类与类之间的关系:
类与类之间的关系 | |
泛化继承关系 | 即派生类与基类之间的关系,基类的属性与方法,派生类都有,且派生类还可以有额外的属性与方法 |
实现关系 | B类中的函数的形参是A类的成员或指针 |
依赖关系 | B类中的函数的形参是A类的成员或指针、B类的成员函数中的局部变量是A类的对象或指针 |
关联关系 | |
聚合关系 | |
组合关系 | B类中的成员属性是A类的对象或对象指针;可以使系统更加灵活,降低类与类之间的耦合度 |
⑥迪米特法则:就是A类不直接链接C类,而是通过B类中转;
2. 设计模式的具体实现,以计算器(四则运算的例子:加减乘除)来演示:
版本一:
思路是定义一个完整的类,里面含有计算的数值与计算的方法,数值通过构造函数的方式传进来,方法通过该类的对象进行调用,并将加减乘除的符号传给该函数的参数;
耦合度:很高,因为比如说新增计算%取余运算时,需要破坏原有的类;
优点:实现简单;
缺点:耦合度较高,程序扩展型差;
#include <iostream>
#include <string>using namespace std;class Cal
{
public:Cal(int a, int b) : m_a(a),m_b(b){}double get_result(string &op){if(op == "+"){return m_a + m_b;}else if(op == "-"){return m_a - m_b;}else if(op == "*"){return m_a * m_b;}else if(op == "/"){return m_a / m_b;}else if(op == "%") //这里新增就需要破坏这个类{return double((int)m_a % (int)m_b);}return 0;}private:double m_a;double m_b;
};int main(int argc, char **argv)
{double a = 0;double b = 0;string op{};cout<<"please input the a : ";cin>>a;cout<<"please input the b : ";cin>>b;cout<<"please input the op : ";cin>>op;Cal cal(a,b);cout<<"result = "<<cal.get_result(op)<<endl;return 0;
}
版本二:
思路:通过一个共同的抽象类(基类),提供构造函数(给计算值a,b赋值),提供统一的计算函数方法(接口),该函数是纯虚函数,具体的实现由各派生类实现;实现方式是通过基类的指针调用各派生类的计算方法;
耦合度:低,当新增功能时,只需要新增一个类去继承基类,然后修改具体的计算实现方式;例如新增一个Mod类实现取余计算的方法;
优先:耦合度低,扩展性强;
缺点:new暴露在主函数中,对用户可见,且与类名之间的耦合度高,没有解耦;
#include <iostream>
#include <string>using namespace std;class Cal //抽象的基类的作用一:通过构造函数给a,b赋值;作用二:提供统一的函数方法接口,计算结果;
{ //具体的实现交给派生类;通过该抽象类指针调用各派生类的函数实现方法;
public:Cal(int a, int b) : m_a(a),m_b(b){}virtual ~Cal(){}virtual double get_result() = 0;// private: //因为派生类要在自己的类内访问,所以不能够设置为私有属性;double m_a;double m_b;
};class Add : public Cal
{
public: Add(int a, int b) : Cal(a,b) //给计算数值赋值也是由派生类完成,调用基类的构造函数,给a,b赋值;{}double get_result(){return m_a + m_b;}~Add() = default;
};class Sub : public Cal
{
public: Sub(int a, int b) : Cal(a,b){}double get_result(){return m_a + m_b;}~Sub() = default;
};class Mul : public Cal
{
public: Mul(int a, int b) : Cal(a,b){}double get_result(){return m_a * m_b;}~Mul() = default;
};class Div : public Cal
{
public: Div(int a, int b) : Cal(a,b){}double get_result(){return m_a / m_b;}~Div() = default;
};class Mod : public Cal
{
public: Mod(int a, int b) : Cal(a,b){}double get_result(){return double((int)m_a % (int)m_b);}~Mod() = default;
};int main(int argc, char **argv)
{double a = 0;double b = 0;string op{};cout<<"please input the a : ";cin>>a;cout<<"please input the b : ";cin>>b;cout<<"please input the op : ";cin>>op;Cal *cal;if(op == "+"){cal = new Add(a,b);}else if(op == "-"){cal = new Sub(a,b);}else if(op == "*"){cal = new Mul(a,b);}else if(op == "/"){cal = new Div(a,b);}else if(op == "%"){cal = new Mod(a,b);}cout<<"result = "<<cal->get_result()<<endl;return 0;
}
版本三:
思路:先有一个抽象的函数方法基类,然后加减乘除继承该基类,并在派生类中覆写该函数方法,函数接口是接收要计算的a,b的值,并不作为成员属性存储;然后利用一个组合类,将上面所有的基类的指针当做属性成员进行存储,同样的并不存储a,b;内部的成员函数的方法接受a,b和op,再根据op进行选择对应的成员指针指向的函数方法(上面的各基类);
耦合度:低,只需要新增一个类继承抽象的函数方法基类,然后组合类中新增对应的派生类指针成员即可;
优点:耦合度低,扩展性好;
缺点:暂无
#include <iostream>
#include <string>using namespace std;class Operation //这个是一个抽象类(对函数方法的抽象),并不负责对a,b的赋值;
{ //各派生类主要负责对该方法的覆写;
public:virtual double get_result(double a, double b) = 0;
};class AddOperation : public Operation //覆写了基类中的函数方法,a,b由调用该类中的函数方法时传递进来;
{
public:double get_result(double a, double b){return a + b;}
};class SubOperation : public Operation
{
public:double get_result(double a, double b){return a -+ b;}
};class MulOperation : public Operation
{
public:double get_result(double a, double b){return a * b;}
};class DivOperation : public Operation
{
public:double get_result(double a, double b){return a / b;}
};class ModOperation : public Operation
{
public:double get_result(double a, double b){return double((int)a % (int)b);}
};class Cal //这是一个组合的类,将上面覆写函数方法的派生类指针组合到该类里面,作为函数成员属性;
{ //这写派生类的指针作为属性成员,当然由构造函数进行初始化,由主函数调用时传进来;
public: //这里两种构造函数对应两种初始化方法,private四个成员的两种初始化方法,现在这种方式就无需主函数传进来了;// Cal(Operation *add, Operation *sub, Operation *mul, Operation *div, Operation *mod) : m_add(add),m_sub(sub),m_mul(mul),m_div(div),m_mod(mod)// {// }Cal(){}double get_result(double a, double b, string &op) //该组合类的函数方法,参数负责接收a,b和op,由调用该函数方法时传进来;{if(op == "+"){return m_add->get_result(a,b);}else if(op == "-"){return m_sub->get_result(a,b);}else if(op == "*"){return m_mul->get_result(a,b);}else if(op == "/"){return m_div->get_result(a,b);}else if(op == "%"){return m_mod->get_result(a,b);}return 0;}private:// Operation *m_add;// Operation *m_sub;// Operation *m_mul;// Operation *m_div;// Operation *m_mod;Operation *m_add = new AddOperation;Operation *m_sub = new SubOperation;Operation *m_mul = new MulOperation;Operation *m_div = new DivOperation;Operation *m_mod = new ModOperation;
};int main(int argc, char **argv)
{double a = 0;double b = 0;string op{};cout<<"please input the a : ";cin>>a;cout<<"please input the b : ";cin>>b;cout<<"please input the op : ";cin>>op;// Cal cal(new AddOperation, new SubOperation, new MulOperation, new DivOperation, new ModOperation);Cal cal;cout<<"result = "<<cal.get_result(a,b,op)<<endl;return 0;
}
版本四:
版本五:
版本六:
版本七:
版本八:
版本九: