软件设计原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 7 条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本

1.开闭原则

定义:软件实体应该对扩展开放,对修改关闭。
软件实体

  1. 项目中划分的模块
  2. 类与接口
  3. 方法
    开闭原则的含义:当应用需求改变时,在不修改软件实体的前提下,可以扩展模块的功能,使其满足新的需求。

1.1开闭原则的作用

开闭原则是面向对象设计的终极目标

  1. 对软件测试的影响:软件遵守开闭原则,软件测试只需要对扩展的代码进行测试就可以。
  2. 提高代码的可复用性:粒度越小,被可复用的可能性就越大,在面向对象程序设计中,根据原子性和抽象变成可以提高代码的可复用性。
  3. 提高软件的可维护性:遵守开闭原则,其稳定性和延续性增强从而易于扩展和维护。

1.2开闭原则的实现

通过"抽象约束、封装变化"来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

例子:Windows桌面主题
分析:Windows 的主题是桌面背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的桌面主题,也可以从网上下载新的主题。这些主题有共同的特点,可以为其定义一个抽象类(Abstract Subject),而每个具体的主题(Specific Subject)是其子类。用户窗体可以根据需要选择或者增加新的主题,而不需要修改原代码。
其类图如图所示:
开闭原则

2.里氏替换原则

定义:继承必须确保父类所拥有的性质在子类中仍然成立。
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原则是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。

2.1里氏替换的作用

  1. 里氏替换原则是实现开闭原则的重要方式之一。
  2. 它克服了继承中重写父类造成的可复用性变差的缺点。
  3. 是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

2.2里氏替换原则的实现

里氏替换的原则通俗的讲就是:子类可以扩展父类的功能,但不改变父类原有的功能。也就是说子类在继承父类时除添加新的方法完成新增的功能外,尽量不要重写父类的方法
如果通过重写父类的方法来完成新的功能,这样写起来非常的简单,但是整个继承体系的可复用性就会变的非常的差,特别是运行多态比较繁琐时,程序运行出错的概率就会非常的大
如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。
例子:几维鸟不是鸟
分析:鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期,其类图如图 所示
里氏替换原则
程序运行结果:

如果飞行300公里:
燕子将飞行2.5小时.
几维鸟将飞行Infinity小时。

程序运行错误的原因是:几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则

3.依赖倒置原则

定义:高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,核心思想:面向接口编程,不要面向实现编程。
依赖倒置倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。

3.1依赖倒置的作用

  1. 降低类间的耦合性
  2. 提高系统的稳定性
  3. 可以减少并行开发引起的风险
  4. 可以提高代码的可读性和可维护性

3.2依赖倒置原则的实现方法

依赖倒置的原则的目的是通过面向接口编程来降低类间的耦合性,在项目中遵循以下四点就能满足这个规则:

  1. 每个类尽量提供接口或者抽象类,或者两者都兼备
  2. 变量的声明类型尽量是接口或者抽象类
  3. 任何类都不能从具体类派生
  4. 使用继承时尽量遵守里氏替换原则

例子:顾客购物
依赖倒置原则

4.单一职责原则

定义:单一职责原则又称单一功能原则,单一职责规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。
该原则提出对象不应该承担太多的职责,如果一个对象承担太多的职责,至少存在以下两个缺点:

  1. 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力
  2. 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全部包含进来,从而造成代码冗余和代码浪费

4.1单一职责的作用:

单一职责的核心是控制类的粒度大小,将对象解耦,提高其内聚性。

  1. 降低类的复杂度,一个类只负责一项职责。
  2. 提高类的可读性,复杂性降低,自然可读性会提高。
  3. 提高系统的可维护性,可读性提高,自然维护性就提高了。
  4. 变更引起的风险降低,变更是必然的,如果单一原则遵守好,当修改一个功能时,可以显著降低对其他功能的影响。

4.2单一职责的实现

单一职责原则是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中。
例子:学生工作管理
分析:大学学生工作主要包括学生生活辅导和学生学业指导两个方面的工作,其中生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作,学业指导主要包括专业引导、学习辅导、科研指导、学习总结等工作。如果将这些工作交给一位老师负责显然不合理,正确的做 法是生活辅导由辅导员负责,学业指导由学业导师负责,其类图如图所示
单一职责原则

5.接口隔离原则

定义:客户端不应该被迫依赖于它不使用的方法,要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
1.单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
2.单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建

5.1接口隔离原则的作用

接口隔离原则是为了约束接口,降低类对接口的依赖性

  1. 将臃肿庞大的接口分解为多个粒度更小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性
  2. 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性
  3. 如果接口的粒度大小定义合理,能够保证系统的稳定性,但是定义过小则会造成接口数量过多,使设计复杂化,如果定义过大灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险
  4. 使用多个专门的接口能够体现对象的层次 ,因为通过接口的定义,实现对总接口的定义
  5. 能减少项目中代码的冗余,过大的接口里面通常放置许多不同的方法,当实现这个接口时被迫设计冗余的代码

5.2接口隔离原则的实现

  1. 一个接口尽量小但要有限度,一个接口之服务一个子模块或者业务逻辑
  2. 为依赖接口的类定制服务,只提供调用者需要的方法,屏蔽不需要的方法
  3. 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
  4. 提高内聚,减少对外交互,使接口用最少的方法完成最多的事情

例子:学生成绩管理系统
接口隔离原则

6.迪米特法则

定义:迪米特法则又叫最小知识法则,只与你的直接朋友交谈,不跟陌生人说话。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的调用, 可以通过第三方转发该调用。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

6.1迪米特法则的作用

  • 降低类之间的耦合度,提高模块的相对独立性
  • 由于亲和度降低,从而提高了类的可复用率和系统的扩展性
    过度使用迪米特法则会使系统产生大量的中介类,增加系统的复杂性,使模块之间的通信效率降低

6.2迪米特法则的实现方法

从迪米特法则的定义和特点可知,它强调两点:

  • 从依赖者的角度,只依赖应该依赖的对象
  • 从被依赖者的角度,只暴露应该暴露的方法

所以应用迪米特法则要注意一下六点:

  1. 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
  2. 在类的结构设计上,尽量降低类成员的访问权限。
  3. 在类的设计上,优先考虑将一个类设置成不变类。
  4. 在对其他类的引用上,将引用其他对象的次数降到最低。
  5. 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
  6. 谨慎使用序列化(Serializable)功能。

例子:明星和经济人的关系
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图所示
迪米特法则

7.合成复用原则

定义:合成复用原则又叫组合/聚合原则。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑用继承关系来实现。
合成复用原则和里氏替换原则相辅相成,两者都是开闭原则的具体实现规范

7.1合成复用原则的作用

通常类的复用包括继承复用和合成复用,继承复用缺点:

  1. 继承复用破坏类的封装性,因为继承会将父类的实现细节暴露给子类,父类对子类透明,所以这种复用又称为白箱复用
  2. 父类和子类的耦合度高,父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展和维护
  3. 限制了复用的灵活性,从父类的继承而来的实现都是静态的,在编译时已经定义,所以运行时不可能发生变化

采用组合或聚合服用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有一下优点:

  1. 维持了类的封装性,因为成分对象的内部细节是新对象看不见的,所以这种复用称为黑箱复用
  2. 新旧类之间的耦合度低,这种复用所需依赖较少,新对象存取成分对象的唯一方法是通过成分对象接口
  3. 复用的灵活性高,这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象

7.2合成复用原则的实现

合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
例子:汽车分类管理
分析:汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。下图所示是用继承关系实现的汽车分类的类图
继承复用
从图 1 可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。但如果改用组合关系实现就能很好地解决以上问题,其类图如图 2 所示
聚合复用
这 7 种设计原则是软件设计模式必须尽量遵循的原则,各种原则要求的侧重点不同。其中,开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;单一职责原则告诉我们实现类要职责单一;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合度;合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值