为什么学习设计模式? 设计模式像是推荐一个更少出错更高效更方便的一种方式。
类似学习数据结构和算法。
数据结构:在内存里面存储数据的方式:map用到树、数组用到的就是线性表、打游戏时候要找到最近的路径那么就要用图。通常我们都是调用别人做好的轮子。
算法:比如排序 把无序变有序,有时候我们不知道怎么写的但是能对应一个轮子也能做但是要做的好
设计模式是针对:面向对象。 C不行
面向对象: 封装、继承、多态
封装:一些属性需要设置 private
继承:private 不能继承 (使用上不能,内存里面有),如果有纯虚函数想继承那么子类必须要重写虚函数,不然不能实例化。有纯虚函数的类叫抽象类
多态:同名函数有不同的状态(前提是有继承关系、有虚函数、子类重写虚函数)使用的时候 父类的指针或者引用 指向不同的子类对象,因为子类的(A类、B类 有函数同名,但是里面的实现是不一样的)。原理是编译器做了动态绑定:就是最终运行的时候才确定调用谁,而不是在编译的时候去指定。
静态变量是属于类的,所有对象共享同一个。
成员变量属于对象的,每个对象都可以有一个。
静态成员函数也是属于类的,不属于对象:使用时候直接用类名调用,注意:静态变量能直接使用,非静态变量不能直接使用,可以间接使用: 可以用穿参的方式 内对象.静态成员。
《--------------------------------以上是讨论设计模式的前提------------------------------》
UML类图 :unified(统一的) modeling(建模) language.
面向对象设计主要就是用 UML的类图,类图用于描述系统中所包含的类以及它们之间的相互关系它是系统分析和设计阶段的重要产物,也是系统编码和测试的重要模型依据.
分析类和类之间的关系:
继承:父子
关联:一个类里面包含一个类 ,A类当做B类的一个成员(包含于被包含的关系)
类之间三种关联状态:
单向关联:A类中有B类当做成员。
双向关联:A类中有B类当做成员,B类中也有A类的当成员。
自关联:典型的就是链表自身类型的指针指向下一个节点的地址 在A类里面有个成员他的类型是A类型 。当前类中包含一个自身类型的对象成员
自关联指的就是 当前类中包含一个自身类型的对象成员,这在链表中非常常见,单向链表中都会有一个指向自身节点类型的后继指针成员,而双向链表中会包含一个指向自身节点类型的前驱指针和一个指向自身节点类型的后继指针。就以双向链表节点类为例,它的 C++写法为:
class Node{
private:
void* m_data;
Node* m_prev;
Node* m_next;}
聚合关系:
聚合(Aggregation)
关系表示整体与部分的关系。
在聚合关系中,成员对象是整体的一部分,但是成员对象可以脱
离整体对象独立存在
汽车(Car)与引擎(Engine)、轮胎(Wheel)、车灯(Light)
森林(Forest)与植物(Plant)、动物(Animal)、水(Water)、阳光(Sunshine)中
组合:
组合(Composition)关系也表示的是一种整体和部分的关系,但是在组合关系中整体对象可以控制成员对象的生命周期,一旦整体对象不存在,成员对象也不存在,整体对象和成员对象之间具有同生共死的关系。
在 UML 中组合关系用带实心菱形的直线表示,下面举两个组合关系的例子:
头(Head)和嘴巴(Mouth)、鼻子(Nose)、耳朵(Ear)、眼睛(Eye)
树(Tree)和树根(Root)、树干(Trunk)、树枝(Branch)、树叶(Leaf)
依赖:类和类之间的关系
依赖(Dependency)关系是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示个事物便用另一个事物时使用依赖关系,大多数情况下依赖关系体现在某个类的方法便用另一个类的对象作为参数。
依赖关系通常通过三种方式来实现:
1.将一个类的对象作为另一个类中方法的参数
2.在一个类的方法中将另一个类的对象作为其对象的局部变量
3、在一个类的方法中调用另一个类的静态方法
他们之间的关系区别:
类之间的关系强弱顺序是这样的:继承(泛化)>组合 > 聚合 > 关联 > 依赖。
设计模式的三个原则:
单一职责原则:一个类就负责一个事情。(说的就是封装)
开放封闭原则:是上一个单一原则的延续(说的就是继承)
开放-封闭原则说的是软件实体(类、模块、函数等)可以扩展,但是不可以修改。也就是说 对于扩展是开放的,对于修改是封闭的。
该原则是程序设计的一种理想模式,在很多情况下无法做到完全的封闭。
但是要对自己设计的类、模块在哪里产生了变化了然与心。
因此 需要在这些位置创建抽象类,来隔离以后发生的这些同类变化(其实就是对多态的应用,创建新的子类并量写父类虚函数,用以更新处理动作)。
此处的抽象类,其实并不等价与C++中完全意义上是抽象类(需要有纯虚函数),这里所说的抽象类只需要包含虚函数(纯虚的或非纯虚函数)能够实现多态即可。
开放-封闭原则是面向对象设计的核心所在,这样可以给我们设计出的程序带来巨大的好处,使其可维护性、可扩
展性、可复用性、灵活性更好。
依赖倒置原则:(实际就是多态)
1.高层模块不应该依赖低层模块,两个都应该依赖抽象
高层模块不应该依赖低层模块(就是别直接调用Api):比如我想(上层:比如界面控件)调用 运动控制卡 走move()这个dll函数 那么我不是直接调用,而是再封装一层一个MyMove()的函数 这个MyMove()调用move这个dll函数 ,(上层:比如界面控件则调用MyMove()这个样就分开了,《:::::::《这样做的好处:大聪明的项目组接了一个新项目,低层使用的是的数据库接口,高层基于这套接口对数据库表进行了添MySql删查改,实现了对业务层数据的处理、而后由于某些原因,要存储到数据库的数据量暴增,所以更换了0racle 数据库,由于低层的数据库接口变了,高层代码的数据库操作部分是直接调用了低层的接口,因此也需要进行对应的修改,无法实现对高层代码的直接复用,大聪明欲哭无泪。(直接调用底层的,没有中间层,会导致顶层的也要改)。
应用层 | ||
父类 | ||
应用层调用子类实现操作数据库的功能这样应用层就不需要改了 | 子类(操作MySql类) | 子类(操作Oracle类) |
操作MySql的Api | 操作Oracle的Api |
底层通过中间层抽象类屏蔽的底层的特性。用户不用关心实现,用就行 。
2.抽象不应该依赖细节(就是“抽象类”的虚函数不写细节),细节应该依赖抽象 (细节依赖是通过多态在子类重写父类虎函数的时候实现的)
里氏代换原则:子类类型必须能够替换掉它们的父类类型
关于这个原理的应用其实也很常见,比如在 Qt中,所有窗口类型的类的构造函数都有一个 Qwidget:类型的参数(QWidget 类是所有窗口的基类),通过这个参数指定当前窗口的父对象。虽然参数是窗口类的基类类型,但是我们在给其指定实参的大多数时候,指定的都是子类的对象,其实也就是相当于使用子类类型替换掉了它们的父类类型
这个原则的要满足的第一个条件就是 继承 ,其次还要求 子类继承的所有父类的属性和方法对于子类来说都是合理的。
高层模块:可以理解为上层应用,就是业务层的实现
低层模块:可以理解为底层接口,比如封装好的 API、动态库等
抽象:指的就是抽象类或者接口,在C++中没有接口,只有抽象类
在实际应用中就可以使用子类替换掉父类,同时功能也不会受到影响,父类实现了复用,子类也能在父类的基础上增加新的行为,这个就是里氏代换原则。
(注意:C++里面好像没有接口概率(虚函数重写相当于接口吧),只有java有接口概率)。
.抽象类中提供的接口是固定不变的
.低层模块是抽象类的子类,继承了抽象类的接口,并且可以重写这些接口的行为高层模块想要实现某些功能,同用的是抽象类中的函数接口,并且是通过抽象类的父类指针/引用其子类的实例对象(用子类类型替换父类类型)比如控件Button(QWidget *p)但是时间P是Diglog 所有Button(Diglog p)Diglog继续了QWidget 类替换掉父类 这个就是里氏代换原则 ,这样就实现了多态。