写点什么

解析设计模式与设计原则:构建可维护性和可扩展性代码的重要性

  • 2023-10-17
    广东
  • 本文字数:2916 字

    阅读完需:约 10 分钟

解析设计模式与设计原则:构建可维护性和可扩展性代码的重要性

本文分享自华为云社区《深入解析设计模式与设计原则:构建可维护性和可扩展性代码的重要性》,作者: Lion Long。

一、为什么需要设计模式?

1.1、设计模式的定义


设计模式大概有 23 种。


设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。


从定义可以看出,设计模式的使用有很多的局限性。一定要明确它解决什么问题,再使用它。当不清楚设计模式解决什么问题时不要轻易使用。


通俗的讲,设计模式是解决软件开发过程中一些问题的固定套路。不要过度的封装或使用设计模式,除非明确了需求的具体变化方向,而且变化方向的点是反复的出现,才会使用设计模式;即慎用设计模式。


设计模式要到达一定的工程代码量才能精通。但是,了解设计模式是需要的。

1.2、设计模式由来


设计模式的由来可以追溯到 20 世纪 80 年代,由计算机科学家埃里希·伽玛(Erich Gamma)等人首次提出。他们将设计模式定义为可重复利用的解决方案,用于常见问题和设计挑战。


设计模式的出现是为了解决软件开发中的一些常见问题,帮助开发人员更高效地编写可维护和可扩展的代码。通过使用设计模式,开发人员可以借鉴先前的成功经验,避免重复发明轮子,同时提高代码的可读性和可理解性。


设计模式的目标是提供经过验证和经过时间考验的解决方案,以解决特定情境中的常见问题。设计模式不是一种具体的算法或代码片段,而是一种在特定情境下的解决方案模板。它们可以应用于各种编程语言和开发环境中。


设计模式通常分为三种类型:创建型模式、结构型模式和行为型模式。


  • 创建型模式关注对象的创建机制;

  • 结构型模式关注对象之间的关系和组织方式;

  • 行为型模式关注对象之间的交互和通信。


一些常见的设计模式包括单例模式、工厂模式、观察者模式、策略模式等。


一句话来说,就是:满足设计原则后,慢慢迭代出来的。

1.3、 设计模式解决的问题


使用设计模式的前提条件:具体的需求既有稳定点又有变化点。


(1)稳定点,即不会变的东西。如果全是稳定点,不需要设计模式。


(2)变化点,即经常发生变化。如果全是变化点,发生的改变没有具体的方向,这也不需要设计模式。比如游戏开发,使用脚本语言解决全是变化的点,因为脚本不需要重新编译,热更新就可以。


设计模式具体解决问题的场景:希望修改少量的代码,就可以适应需求的变化。比如,整洁的房间有一个好动的猫,如何保证房间的整洁?把猫关到笼子中,使猫在有限范围内活动。


也就是使用设计模式,让变化点在有限范围内变化。

二、设计模式基础


设计模式和开发语言相关的,利用语言的特性实现设计模式。


对于 C++而言,设计模式的基础是:


(1)面向对象的思想。面向对象的三个特征,封装(目的是隐藏实现细节,实现模块化)、继承(目的是希望无需修改原有类的基础上,通过继承来实现功能的扩展;C++可以多继承)、多态(静态多态是函数重载,同一个函数名但参数不同来同时表现出不同的形态;动态的多态是继承中虚函数的重写)。设计函数很多依赖于动态的多态。


(2)设计原则。

2.1、C++多态之虚函数重写


假设一个基类,有两个虚函数:


class Base{	public:		virtual void func1(){}		virtual void func2(){}		int a;};
复制代码


其虚函数表和内存布局为:



此时有一个子类继承 Base:


class Subject : public Base{	public:		virtual void func2(){}		virtual void func3(){}		int b;};
复制代码


其虚函数表和内存布局为:



从内存布局可以看到,有虚函数就会为该类生成虚函数表指针,虚函数表是编译的时候编译器自动帮我们自动生成的。虚函数表其实是一个一维数组,数组的元素保存的虚函数地址,通过偏移就可以调用到相对应的函数。


对于 Base 类而言,虚函数表有 func1 和 func2;Subject 继承 Base,它的虚函数表中也会有 Base 的虚函数,而且虚函数表中 Base 的虚函数在 Subject 的虚函数前面。


如果 Subject 没有重写 Base 虚函数,那么虚函数表中保存的虚函数地址是一样的(如示例中的 func1)。


如果 Subject 重写 Base 虚函数,那么虚函数表中会发生替换,将 Subject 重新的虚函数地址替换掉 Base 中相应虚函数的地址(如示例中的 func2)。


如果 Subject 自己有新的虚函数,则也要加入虚函数表中。

2.2、多态的体现


(1)早绑定。假如有 Base *p=new Subject;如果 Subject 没有重写 Base 虚函数,那么会将 Subject 类型转换为 Base 类型,这就是早绑定。


(2)晚绑定。假如有 Base *p=new Subject;如果 Subject 重写了 Base 虚函数,那么 p 实际指向的是 Subject 对象,这就是晚绑定。

2.3、扩展方式


(1)继承。


(2)组合。

2.4、多态组合


// 继承class Subject : public Base{};
// 组合class Subject{private: Base base;};
复制代码



设计模式中的组合通常是指组合基类指针。好处是可以扩展 Base 的功能,通过多态方式让组合解耦合。


// 组合基类指针class Subject{private:	Base *base;};
复制代码


三、设计原则


设计原则是设计模式还没产生它就存在了。设计原则是多代程序员总结的开发原则。


3.1、依赖倒置


实现要依赖接口,接口又可以转换为抽象,即具体实现的代码需要依赖这个抽象。具体使用接口(客户)也要依赖这个抽象。


高层模块不应该依赖低层模块,两者都应该依赖抽象;


抽象不应该依赖具体实现,具体实现应该依赖于抽象;



自动驾驶系统公司是高层,汽车生产厂商为低层,它们不应该互相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变动;自动驾驶系统、汽车生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象)。

3.2、开放封闭


一个类应该对扩展(组合和继承)开放,对修改关闭。针对封装和多态。

3.3、面向接口


不将变量类型声明为某个特定的具体类,而是声明为某个接口;客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案;主要针对封装。

3.4、封装变化点


将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点的实现层次分离。主要针对封装和多态。

3.5、单一职责


一个类应该仅有一个引起它变化的原因。主要针对封装。

3.6、里氏替换


子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可能出现错误;覆盖了父类方法却没有实现父类方法的职责。


主要针对多态中的虚函数重写。

3.7、接口隔离


(1)不应该强迫客户依赖于它们不用的方法;


(2)一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责;


(3)客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。


通过限定词隔离。类与类之间依赖接口,通过接口隔离类。

3.8、组合优于继承


继承耦合度高,组合耦合度低。

3.9、最小知道原则


让用户尽量不选择它不需要的接口。

总结


通过介绍设计模式的定义、分类和应用场景,以及设计原则的作用,强调了它们在软件开发中的重要性。设计模式提供了可重复利用的解决方案,帮助开发人员解决常见问题和设计挑战,并提高代码的可读性、可理解性和可维护性。设计原则则为设计模式提供了指导,如单一职责原则、开放封闭原则等。通过应用设计模式和设计原则,开发人员可以构建高质量、可维护和可扩展的软件系统,避免重复劳动,提高代码的可重用性和灵活性。


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
解析设计模式与设计原则:构建可维护性和可扩展性代码的重要性_开发_华为云开发者联盟_InfoQ写作社区