设计模式的SOLID原则和创建式设计模式 一

设计模式的SOLID原则和创建式设计模式 一

设计模式的作用

如果说算法的作用是让我们写出高效的程序,那么设计模式的目的就是让我们写出好的,扩展性强的,符合规范的代码。

SOLID设计原则

在软件设计中,要遵循一些原则。SOLID五大原则在软件设计中非常常见。
S 单一职责原则
每个模块都负责单一的职责,若一个模块负责多个原则,则应该考虑将这个模块划分

事实上,这个原则看上去很简单,但在软件设计中一个模块是否负责单一职责是很难区分的,可能会随着适用场景的改变而改变。
举个例子:

class Person{
	int PersonID;
	string name;
	int age;
	int sex;
	
	string city;
	string address;
};

当用于只管理维护用户信息,不用于其他用途的场景时,该类是符合单一职责的。
但是当业务场景中加上了一个配送的场景,此时就不符合单一职责了,应该将Person的地址信息给拆分开来。

O 开闭原则

对扩展开放,对修改封闭

开闭原则用于判断自己的代码是否有扩展性,如果要需要增加一个新的功能时,需要改变已有的实现的话,那么就属于违反开闭原则。如何判断改变已有的实现,有几个层级,如子系统层级,类层级,方法层级,在业务较简单的实现在方法层级符合开闭原则就够了,若是业务复杂的话,则需要考虑在类层级上符合开闭原则。

L 里氏替换原则。
当让一个子类继承父类时,我们需要遵循这样一个原则:子类能够替代父类所在的任何一个场景,如果不能做到,那么就违背了里氏替换原则
举几个符合里式替换原则的例子,父类为鸟,子类为麻雀。因为麻雀就是鸟,这样的父子类就是符合里氏替换原则的。

class bird{
public:
	bird(){
		cout<<"create a bird"<<endl;
	}
	virtual ~bird(){
		cout<<"destruct a bird"<<endl;
	}
}; 

class sparrow : public bird{
public:
	sparrow(){
		cout<<"create a sparrow"<<endl;
	}
	virtual ~sparrow(){
		cout<<"destruct a sparrow"<<endl; 
	}
};

I 接口隔离原则
每个类所用到的接口集应该是最小的。
举一个正确的例子和一个错误的例子就很容易理解

class Interface{
	virtual void f1() = 0;
	virtual void f2() = 0;
	virtual void f3() = 0;
	virtual void f4() = 0;
	virtual void f5() = 0;
}; 

class A:public Interface{
public:
	void f1(){
		cout<<"A call f1()"<<endl;
	}
	void f2(){
		cout<<"A call f2()"<<endl;
	}
	void f3(){
		cout<<"A call f3()"<<endl;
	}
	void f4(){
	}
	void f5(){
	}
};

class B:public Interface{
public:
	void f1(){
		cout<<"B call f1()"<<endl;
	}
	void f4(){
		cout<<"B call f4()"<<endl;
	}
	void f5(){
		cout<<"B call f5()"<<endl;
	}
	void f2(){
	}
	void f3(){
	}
};

在这个例子中,A类只需要用到方法f1,f2,f3,,B类只需要用到f1,f4,f5,它们都用到了接口Interface,但是由于接口中定义了5个方法,所以A类不得不实现它用不到的f4,f5方式,这样就造成了代码的冗余,此时我们应该将接口划分成两个,一个专门用来定义A所要用到的方法,一个专门用来定义B中用到的方法。

D
依赖倒置原则
高层模块不应该依赖底层模块,两者都应该依赖抽象。
这是一个很好的保证扩展性的策略。如果在系统中发现高层模块依赖底层模块的实现,我们可以考虑在这个底层模块中加一层抽象,以此来隔离低层模块的变化。
同样举个例子来说明。
在订餐系统中有一个order类,order类可以预定不同种类的食物,这时order类需要依赖各种各样的食物类来实现,为了不违背依赖倒置原则,我们可以在其中加一个抽象的food类,使得order和各种各样的事物类都依赖于这个food类,这样就遵循了依赖倒置原则,就算之后增加了新的事物,我们也能只用再加一个新的依赖于food类的具体食物就行,这样就将变化封装起来了。

创建式设计模式

创建式相关的设计模式主要为了解决对象创建的工作。分别包括单例模式,工厂模式,原型模式,建造者模式

单例模式的目的是为了保证每个类只能创建一个实例,实现方法有
恶汉式单例模式,懒汉式单例模式(不安全),加锁型单例模式(安全),双检查锁单例模式(会有reorder问题)
直接上代码

//author: Solitude
//date: 2021-09-28
//purpose: descripe the design pattren about creating objects

#include<iostream>
#include<mutex> 
using namespace std;

//懒汉式单例模式 
//C++没有实现java中的静态代码块概念,所以懒汉式单例模式得需要使用特殊的方式编写  
//本段代码只用于表意 无法实际运行 
//缺点是如果用不上实例对象却用到了类中的某个方法时,会导致浪费内存 
#if 0 
class Singleton1{
private:
	Singleton1(){}
	~Singleton1(){}
	static Singleton1* single;
	
	static 
	{
		//initialize the single 
	}
public:
	Singleton1* getInstance(){
		return single;
	}
	
};  
#endif 
//饿汉式单例模式 
//缺点是线程不安全 
class Singleton2{
private:
	Singleton2(){}
	~Singleton2(){}
	Singleton2* single; 
public:
	Singleton2* getInstance(){
		if(single == nullptr){
			//initialize the single
			//...
		}
		return single;
	}
	
}; 
//饿汉式单例模式(加锁) 
//能够实现线程安全,缺点是锁粒度较大,效率不高
class Singleton3{
private:
	Singleton3(){
		//initialize the single
	}
	~Singleton3(){}
	Singleton3* single; 
	std::mutex mtx;
public:
	Singleton3* getInstance(){
		mtx.lock();
		if(single == nullptr){
			Singleton3();
		}
		mtx.unlock();
		return single;
	}
	
};
//懒汉式的double check模式,效率较高 
//但是有个reorder问题
//reorder问题描述
//构造函数一般分为3个步骤 1.申请内存 2.调用构造函数初始化内存 3.返回
//有些编译器对构造函数做了初始化 将第2步延后执行,申请内存之后就直接返回
//这样可能会有一个另外的类得到未初始化的对象就直接使用,导致报错 
//这个问题无法靠程序解决,只有依靠编译器支持 
class Singleton4{
private:
	Singleton4(){
		//initialize the single
	}
	~Singleton4(){}
	Singleton4* single; 
	std::mutex mtx;
public:
	Singleton4* getInstance(){
		if(single == nullptr){
			mtx.lock();
			if(single == nullptr){
				Singleton4();
			}
			mtx.unlock();
		} 
		return single
	}
};

int main(){
	
} 

工厂模式主要为了解决创建多种相似类别的对象问题。
原型模式主要为了解决得到与当前实例对象相同的对象的问题
建造者模式主要为了应付对象构建过程多样化的问题,解决方案为将对象构建过程与对象本身解耦

这个放在下篇博客中介绍

上一篇:基于single-spa的微前端项目


下一篇:注解、正则表达式和单例模式