设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是可以提高代码的可复用性、可维护性、可读性、稳健性及安全性。
设计模式是一种方法学,是为了提高软件应对变化的能力的一种方法学。如果想把软件做得更好一点,让自己的开发能力更强一点。设计模式提供了一种讨论软件设计的公共语言,设计模式通常描述一组相互紧密作用的类与对象。类怎么写、类与类,类与对象之间怎样组合
1994年,GOF(四人组)归纳了23种在软件开发中使用频率较高的设计模式。
设计者模式按照设计目的分为三种,创建型、结构型、行为型。
(1)创建型:创建型主要用于创建对象。
对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。
(2)结构型:用于处理类和对象的组合。
关注于对象的组成以及对象之间的依赖关系,描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
结构型模式分为类结构式模式和对象结构式模式:
合成复用原则,尽量使用关联关系,故大部分结构型模式为对象结构型模式。
(3)行为型:用于描述对类和对象怎样交互和怎样分配职责。
关注于对象的行为问题,是对在不同的对象之间划分责任和算法的抽象化;不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
工厂模式、抽象工厂模式、原型模式、建造者模式、单例模式;
记忆口诀:创工原单建抽(创公园,但见愁)
1、 抽象工厂模式(Abstract Factory Pattern)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
实例:衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。
2、 建造者模式(Builder Pattern)
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
3、 原型模式(Prototype Pattern)
用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
4、 工厂方法模式(Factory Method Pattern)
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
实例:需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。而至于需要哪个牌子的汽车,就到哪个牌子的工厂。
5、 单例模式(Singleton Pattern)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式;
记忆口诀:结享外组适代装桥(姐想外租,世代装桥)
ABCDFFP
1、 适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
2、 桥接模式(Bridge Pattern)
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
3、 组合模式(Composite Pattern)
将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
4、 装饰模式(Decorator Pattern)
动态地给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类方式更为灵活。
5、 外观模式(Facade Pattern)
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
6、 享元模式(Flyweight Pattern)
运用共享技术有效地支持大量细粒度的对象。
7、 代理模式(Proxy Pattern)
为其他对象提供一个代理以控制对这个对象的访问。
职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态者模式、策略者模式、模板模式、访问者模式;
记忆口诀:行状责中模访解备观策命迭(形状折中模仿,戒备观测鸣笛)
1、 责任链模式(Chain of Responsibility Pattern)
为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
2、 命令模式(Command Pattern)
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
3、 解释器模式(Interpreter Pattern)
给定一个语言,定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
4、 迭代器模式(Iterator Pattern)
介绍
提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
5、 中介者模式(Mediator Pattern)
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
6、 备忘录模式(Memento Pattern)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
7、 观察者模式(Observer Pattern)
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
8、 状态模式(State Pattern)
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
9、 策略模式(Strategy Pattern)
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
10、 模板方法模式(Template Method Pattern)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
11、 访问者模式(Visitor Pattern)
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
UML是由图形符号表达的建模语言,可用于对软件密集型系统进行建模,其特点包括工程化、规范化、可视化、系统化、文档化等。
UML由视图(View)、图(Diagram)、模型元素(Model Element)和通用机制(General Mechanism)等几个部分组成。
a) 视图(View): 是表达系统的某一方面的特征的UML建模元素的子集,由多个图构成,是在某一个抽象层上,对系统的抽象表示。
b) 图(Diagram): 是模型元素集的图形表示,通常是由弧(关系)和顶点(其他模型元素)相互连接构成的。
c) 模型元素(Model Element):代表面向对象中的类、对象、消息和关系等概念,是构成图的最基本的常用概念。
d) 通用机制(General Mechanism):用于表示其他信息,比如注释、模型元素的语义等。另外,UML还提供扩展机制,使UML语言能够适应一个特殊的方法(或过程),或扩充至一个组织或用户。
视图包括:用户视图、结构视图、行为视图、实现视图、环境视图。
(1)用户视图(用例视图):以用户的观点表示系统从不同的角度来描述软件系统,该视图描述系统的需求。
用例视图是被称为参与者的外部用户所能观察到的系统功能的模型图。。用例是系统中的一个功能单元,可以被描述为参与者与系统之间的一次交互作用。用例模型的用途是列出系统中的用例和参与者,并显示哪个参与者参与了哪个用例的执行。描述参与者与系统之间的关系
(2)结构视图(静态视图):表示系统的静态行为,描述系统的静态元素,如类与对象,以及它们之间的关系。
(3)行为视图:表示系统的动态行为,描述系统的组成元素(如对象),在系统运行时的交互关系。
状态图(状态、事件、转换、动作)
活动图(状态、活动、完成转换、分叉、结合)
(4)实现视图:用于表示系统中逻辑元素的分布,描述系统的物理文件及它们之间的关系;
(5)环境视图:表示系统中物理元素的分布,用于描述系统中硬件设备以及它们之间的关系。
视图是由图组成的,UML提供13种不同的图。
图分为用例视图、设计视图、进程视图、实现视图和拓扑视图,又可以静动分为静态视图和动态视图。静态图分为:用例图、类图、对象图、包图、构件图、部署图。动态图分为:状态图,活动图,协作图,序列图。
类图:对应于结构视图,类图使用类描述系统的静态结构。使用频率最高的UML图之一。
类图的组成:类名、属性(类的成员变量)、方法。
可见性:公有(public)、私有(private)、保护(protected)分别用+、-、#表示。
类之间的关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation,组合(Composition),依赖(Dependency)
class Circle
{
private:
double radius_;
Point center_;
public:
void setRadius(double _radius);
void setCenter(Point _center);
double getArea() const;
double getCircumfrence() const;
}
‘in’:用于输入的参数,get the value
‘out’:用于输出的参数, set the value
‘inout’:既可作为输入又可作为输出, get the value and set the value
由弱至强依次是:依赖 <关联 < 聚合<组合
当一个对象(调用者)需要调用另外一个对象(被调用者)的方法去完成某些工作时,依赖关系就建立了。依赖关系是一种使用关系,一个类的实现需要另一个类的协助。
特点:假设A类的改变影响了B类,那么B类就依赖于A类。我中可以没有你
在UML中,依赖关系使用虚线箭头表示,箭头指向被依赖的一方
特定事物改变可能影响到使用该事物的其他事物
class Car
{
public :
void move (){
}
static void start(){
} //静态方法
};
class Driver
{
public :
//形参构成依赖
void Driver(Car& car)
{
car.move();
}
//局部变量构成依赖
void run(){
Car* car = new Car;
}
//静态方法调用构成依赖
Car::start();
}
用于表示一类对象与另一类对象之间有联系。元素间的结构化关系,是一种弱关系,被关联的元素间通常可以被独立的考虑。
特点:通常将一个类的对象作为另一个类的属性。我中有你。
(1)双向关联
关联是双向的,两个类都知道另一个类的公共属性和操作。
双向的关联可以有两个箭头或者没有箭头。
顾客与商品:顾客(Customer)购买商品(Product)并拥有商品,反之卖出的商品总有某个顾客与之有联系。
class Customer
{
private:
Product[] products;
}
class Procduct
{
private:
Customer customer;
}
(2)单向关联
类的关联是单向的,只有一个类知道另外一个类的公共属性和操作;
单向的关联有一个箭头。一方持有另外一方的实例,如学生对应成绩表
class Customer
{
private:
Address address;
}
class Address
{
}
(3)自关联
类的属性对象为类的本身。
class Node
{
private:
Node subNode;
}
(4)多重关联
两对象在数量上对应的关系,如一对多、多对一、多对多等。
小结:
依赖与关联关系的区别:
依赖:一个类的某个方法参数类型为另一个类
关联:一个类是另一个类的成员变量,可以是单向的,也可以是双向的。
表示一个整体与部分的关系。在成员类中,成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整个对象独立存在。
特点:整体与部分的关系,部分可以脱离整体存在,如图书馆和图书。我中有你,整体与部分不是共生死
Aggregation体现的是整体和部分之间的关系,即“has-a”关系。整体和部分是可以分离的,即整体和部分都可以拥有各自的生命周期。
在UML中,空心菱形+实心线+箭头(或无箭头),菱形指向整体,箭头指向部分;
如汽车与发动机
class Engine
{
}
class Car
{
private:
Engine engine;
public:
Car(Engine engine)
{
this.engine = engine;
}
public:
void setEngine(Engine engine){
this.engine = engine;
}
}
类Car 中并不直接实例化Engine ,而是通过构造方法或设置方法在类外部实例化好的Engine对象以参数形式传入到Car中。
表示类之间整体与部分的关系,但是组合关系中部分与整体具有统一的生存周期,一旦整体对象不存在,部分对象也将不存在。
Composition体现的也是整体和部分之间的关系,即“is-a”关系。组合关系是更为强力的聚合关系,整体和部分是不可以分离的。整体的生命周期结束时,也意味着部分的生命周期结束。
特点: 组合的成员必须依赖于整体。我中有你,整体与部分同生共死
图形表示:在UML中,实心菱形+实心线+箭头(或无箭头),菱形指向整体,箭头指向部分;
如人的头和嘴的关系
class Heart{
};
class Student
{
public:
Heart* heart;
Student(){
heart=new Heart;
}
~Student(){
delete heart;
}
};
在构造函数中实例化Mouth 对象,创建是同时创建,销毁时同时销毁。学生如果没有心脏将无法存活,组合关系的类具有相同的生命周期。
泛化关系即为继承关系,用于描述父类与子类之间的关系。
图形表示:在UML中,继承使用实线空心箭头表示,空心箭头指向父类(接口)
class Person
{
protected :
string name;
int age;
public:
void move()
{
}
void say()
{
}
}
class Student :public Person
{
private string studentNo;
public :
void study()
{}
}
class Teachear :public Person
{
private string teacherNo;
public :
void teacher()
{}
}
2.4.6 实现关系
在UML中,继承使用虚线空心箭头表示,空心箭头指向接口。
所谓的接口,即将内部实现细节封装起来,外部用户用过预留的接口可以使用接口的功能而不需要知晓内部具体细节。C++中,通过类实现面向对象的编程,而在基类中只给出纯虚函数的声明,然后在派生类中实现纯虚函数的具体定义的方式实现接口,不同派生类实现接口的方式也不尽相同,从而实现多态
(1)类接口的定义 通常在头文件中完成类接口的定义
#ifndef INTERFACE_DEFINE_AND_REALIZE
#define INTERFACE_DEFINE_AND_REALIZE
#include
using std::string;
//define interface
class Person
{
public:
Person():m_StrName("###") //成员列表初始化参数
{};
virtual ~Person(){};
virtual void Eat()=0;//人需要吃东西
virtual void Sleep()=0;//人需要睡觉
virtual void SetName(const string strName)=0;//人都有名字
virtual string GetName()=0;//获取名字
virtual void Work()=0;//人可能要有工作
private:
string m_StrName;
};
//实现接口
//实现接口是通过派生类实现的,每个派生类依据自身特点,可以获取同一接口的不同实现
//也就是所谓的多态
class Student:public Person
{
public:
Student():m_strName("***")
{};
~Student()
{};
void Eat();
void Sleep();
void SetName(const string strName);
string GetName();
void Work();
private:
string m_strName;
};
#endif
(2)接口的实现 通常在源文件中完成接口的实现
#include "InterfaceDefineAndRealize.h"
#include
#include
using std::string;
using std::cout;
using std::endl;
//接口的外部实现
void Student::Sleep()
{
cout<<"student sleep."<
面向对象设计原则是设计模式的基础,每一种设计模式都符合一种或多种面向对象的原则。
(1)单一职责原则(SRP) :要求类的职责只有一个,不能将太多的职责放在一个类中。
类的职责包括数据职责和行为职责,数据职责通过属性来体现,行为职责通过方法来体现。通过将不同的职责封装到不同的类中,实现高内聚,低耦合。
(2)开闭原则(OCP):软件实体对扩展开放的,对修改是关闭的。
关键利用接口、抽象类等机制来定义系统的抽象层,在通过具体的类来进行扩展。扩展新需求时,无须对抽象层进行改动,只需要增加新的具体类来实现新的业务功能即可;(对扩展是开放的,对更改是封闭的)
class AbstractCalculator //定义计算器抽象类
{
public:
virtual int getResult() = 0;
virtual void setOperatorNumber(int a, int b) = 0;
};
//加法计算器类 一个类只做一件事
class PlusCalculator :public AbstractCalculator
{
public:
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
virtual int getResult()
{
return mA + mB;
}
public:
int mA;
int mB;
};
//减法计算器类
class MinuteCalculator :public AbstractCalculator
{
public:
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
virtual int getResult()
{
return mA - mB;
}
public:
int mA;
int mB;
};
//乘法计算器类
class MultiplyCalculator :public AbstractCalculator
{
public:
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
virtual int getResult()
{
return mA * mB;
}
public:
int mA;
int mB;
};
int main(void)
{
AbstractCalculator* caculator = new PlusCalculator;
calculator->setOperatorNumber(10, 20);
cout << calculator->getResult() << endl;
delete calculator;
return 0;
}
增加一个新的功能时,通过增加一个类实现,而不是修改源代码
(3)里氏替换原则(LSP):软件系统中,接受基类对象的地方必然可以接受一个子类对象。
使用基类对象的地方都可以使用子类对象。如有一个基类BaseClass和其子类SubClass,method(base) 可以运行,则method(sub) 也可运行,反之不成立。程序中尽量使用基类类型来对对象进行定义,在运行时再确定其子类类型,用子类对象来替换父类对象。(子类型必须能够替换它们的父类型)
子类型(subtype)必须能够替换掉他们的基类型(base type)。子类可以扩展父类的功能,但是不能改变父类原有的功能。
含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
含义2:子类中可以增加自己特有的方法。
含义3:但子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
含义4:但子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类方法的输出参数更严格或相等
里氏替换原则优点:
里氏代换原则是对“开-闭”原则的补充。实现“开闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
第一点:子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
lass Foo {
public void cal(int num1, int num2) {
int value = num1 + num2;
System.out.println("父类计算结果: " + value);
}
}
class Son : Foo {
public void cal(int num1, int num2) {
int value = num1 - num2;
System.out.println("子类计算结果:" + value);
}
}
class Cal{
public static void main(String[] args) {
Foo foo = new Foo();
foo.cal(2,1);
Son son = new Son();
son.cal(2,1);
}
}
在本例中,父类的本意是想要定义一个两数相加的方法,但是子类继承该方法后却修改为减法,并且也成功了。子类这样操作后,会对整个继承体系造成破坏。
(4)依赖转换原则(DIP) :针对抽象层编程,不针对具体类编程。
程序应该依赖于抽象的接口,而不应该依赖于具体的类。下层模块实现抽象的接口,而上层模块只需依赖于这些接口。依赖注入就是将一个类的对象传入另一个类,注入时尽量注入父类对象,在程序运行时再通过子类对象来覆盖父类对象。
1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象
#include
using namespace std;
class Business
{
public:
virtual void GeneralService() = 0;
};
class TransferAccounts :public Business//办理转账业务
{
public :
void GeneralService() {
cout << "办理转账业务" << endl;
}
};
class SaveMoney :public Business//办理存钱业务
{
public:
void GeneralService() {
cout << "办理存钱业务" << endl;
}
};
class DrawMoney :public Business//办理取钱业务
{
public:
void GeneralService() {
cout << "办理取钱业务" << endl;
}
};
void test(Business*my) {
my->GeneralService();
delete my;
}
void main() {
test(new TransferAccounts);
test(new SaveMoney);
test(new DrawMoney);
}
(5)接口隔离原则(ISP):使用多个专门的接口来取代一个统一的接口。
将一些大的接口进行细化,同时注意粒度,不能太小,也不能太大。
1、客户端不应该依赖它不需要的接口。
2、类间的依赖关系应该建立在最小的接口上。
(6)合成复用原则(CRP):复用功能时,尽可能多使用组合和聚合关联关系,尽量少用继承关系。
通过继承来实现复用,子类覆盖父类,问题是会破坏系统的封装性,基类发生改变,子类也不得不改变;通过组合/聚合复用将一个类的对象作为另一个类的对象的一部分。
尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的。
(7)迪米特法则(LOD):一个软件实体对其他实体的引用越少越好。
如果,两个类不彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
用于降低系统的耦合度,使类与类之间保持松散的耦合关系。类之间的耦合越弱,越利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。
如中介者模式
#include
#include
#include
using namespace std;
class AbstractBuilding
{
public:
virtual void sale() = 0;
virtual string getQuality() = 0;
};
class BuildingA :public AbstractBuilding
{
public:
BuildingA()
{
mQulity = "高品质";
}
virtual void sale()
{
cout << "楼盘A" << mQulity << "被售卖!" << endl;
}
virtual string getQuality()
{
return mQulity;
}
public:
string mQulity;
};
class BuildingB :public AbstractBuilding
{
public:
BuildingB()
{
mQulity = "低品质";
}
virtual void sale()
{
cout << "楼盘B" << mQulity << "被售卖!" << endl;
}
virtual string getQuality()
{
return mQulity;
}
public:
string mQulity;
};
// 中介类
class Mediator
{
public:
Mediator()
{
AbstractBuilding* building = new BuildingA;
vBuilding.push_back(building);
building = new BuildingB;
vBuilding.push_back(building);
}
// 对外提供接口
AbstractBuilding* findMyBuilding(string quality)
{
for (vector::iterator it = vBuilding.begin();
it != vBuilding.end(); it++)
{
if ((*it)->getQuality() == quality)
{
return *it;
}
}
return NULL;
}
~Mediator()
{
for (vector::iterator it = vBuilding.begin();
it != vBuilding.end(); it++)
{
if (*it != NULL)
delete *it;
}
}
public:
vector vBuilding;
};
int main()
{
Mediator* mediator = new Mediator;
AbstractBuilding* building = mediator->findMyBuilding("低品质");
if (building != NULL)
{
building->sale();
}
else
{
cout << "没有符号条件的楼盘" << endl;
}
system("pause");
}
参考文献:
【1】C++设计模式之开闭原则_duixiaoyan的博客-CSDN博客_c++开闭原则
【2】设计模式之里氏替换原则示例 - kosamino - 博客园
【3】C++ UML类图详解_波风水门-CSDN博客_c++类图
【4】UML类图的6大关系 - keepfool - 博客园
【5】 C++接口的定义与实现的详细过程 - 自由真实个性 - 博客园
【6】UML视图及设计模式总结 - 计应191(西)四组 - 博客园
【7】快速记忆23种设计模式 - 知乎
【8】认识UML类关系——依赖、关联、聚合、组合、泛化 - 腾讯云开发者社区-腾讯云