面向对象设计原则

在面向对象的软件设计中,只有尽量降低各个模块之间的耦合度,才能提高代码的复用率,系统的可维护性、可扩展性才能提高。面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,如果把设计模式比作武功招式,那么设计原则就好比是内功心法。常用的设计原则有七个,下文将具体介绍。

七种设计原则简单归纳如下

  • 单一职责原则:专注降低类的复杂度,实现类要职责单一;
  • 开放关闭原则:所有面向对象原则的核心,设计要对扩展开发,对修改关闭;
  • 里式替换原则:实现开放关闭原则的重要方式之一,设计不要破坏继承关系;
  • 依赖倒置原则:系统抽象化的具体实现,要求面向接口编程,是面向对象设计的主要实现机制之一;
  • 接口隔离原则:要求接口的方法尽量少,接口尽量细化;
  • 迪米特法则:降低系统的耦合度,使一个模块的修改尽量少的影响其他模块,扩展会相对容易;
  • 组合复用原则:在软件设计中,尽量使用组合/聚合而不是继承达到代码复用的目的。

那么如何去遵守这七个原则?设计原则的遵守并不是是和否的问题,而是度的问题。任何事物过犹不及,设计原则也是一样,制定这七个原则并不是一味的要求我们去遵守他们,而是根据实际情况灵活运用。

下图中的每一条维度代表一项原则,依据遵守程度在维度上画一个点,如果对这项原则遵守的合理的话,这个点会落在环内,如果遵守的少会落在小圆圈里面,过度遵守会落在大圆圈外面,一个良好的设计体现在图中,应该是七个点都落在环中的七边形。
在这里插入图片描述

如下图,图1(从左到右)属于良好设计,七个设计原则遵守程度都在合理范围内;图2虽然有些不足,但也可以接受;图3和图4,一个几乎不遵守,一个遵守过度,这两种设计模式都需要进行重构。
在这里插入图片描述

单一职责原则

一个类只负责一个功能领域中的相应职责。就一个类而言,应该只有一个引起它变化的原因。
不同的类具备不同的职责,各司其职,有以下优点。

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 类的可读性高,进而可以提高系统的可维护性;
  • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

单一职责原则是最简单但又最难运用的原则,难点在于职责和粒度划分。一个类往往具有多重职责,而发现类的多重职、职责粒度拆分的大小需要要设计人员具有较强的分析设计能力和相关实践经验。

开放关闭原则

一个软件实体(软件实体可以指一个软件模块、一个由多个类组成的局部结构、一个独立的类甚至一个函数)应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

软件系统的需求变化是不可避免的,当我们实现新需求时,需要尽量保证系统的稳定性和扩展性。符合开放关闭原则的系统,可以很容易地扩展,且扩展时无须修改现有代码,使得系统的扩展性和稳定性有很大提高。随着时间的推移,软件规模越来越大,维护成本越来越高,设计满足开放关闭原则的软件系统也变得越来越重要。

系统的抽象化设计,是开放关闭原则的关键。可以通过接口、抽象类定义系统的抽象层,通过实现类进行扩展,增加新功能只需增加新的实现类,而无需修改抽象层,从而实现在不修改已有代码的情况下实现系统扩展。

里氏替换原则

里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述为“如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。”通俗的讲即所有引用基类(父类)的地方必须能透明地使用其子类的对象。

里氏替换原则是实现开放关闭原则的的重要实现手段之一,由于使用基类对象的地方都可以使用子类对象,因此尽量使用基类类型定义对象,而在运行时再确定其子类类型,用子类对象来替换父类对象。

依赖倒置原则

抽象不应该依赖于细节,细节应当依赖于抽象。依赖倒转原则要求我们要针对接口编程,不针对具体实现编程。在程序代码中传递参数时或在关联关系中,尽量引用高层的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。这样一来,如果系统行为发生变化,只需对抽象层进行扩展,而不必修改系统现有的业务逻辑,从而满足开放关闭原则的要求。时下各种开发框架提供的依赖注入功能都很好的应用了此原则。

在大多数情况下,开放关闭原则、里氏代换原则和依赖倒置原则这三个设计原则会同时出现,开放关闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充,目标一致,只是分析问题时所站角度不同而已。

接口隔离原则

使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。接口隔离原则有两层含义:(1)客户需要什么样的接口,就提供什么样的接口,不需要的就删除掉;(2)类之间的依赖关系应建立在最小接口上,也就是说接口要尽量的细化,同时接口中的方法应该尽量少,职责要尽可能单一。

当一个接口太大时,我们需要将它分割成多个更小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事。

在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。

迪米特法则

迪米特法则又叫最小知识原则,一个软件实体应当尽可能少地与其他实体发生相互作用。迪米特法则是对软件系统中实体之间通信的限制,对实体之间通信的宽度和深度提出了要求。迪米特法则要求当修改系统的某一个模块时,要尽量少地影响其他模块,从而使类与类之间保持松散的耦合关系,使系统扩展相对容易。
迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其朋友包括以下几类:

  • 当前对象本身(this);
  • 以参数形式传入到当前对象方法中的对象;
  • 当前对象的成员对象;
  • 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
  • 当前对象所创建的对象。

任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。在应用迪米特法则时,一个对象只能与直接朋友发生交互,不要与“陌生人”发生直接交互,这样做可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。

迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。

在将迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

组合复用原则

组合复用原则也叫合成/聚合复用原则(CARP),就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些已有对象委派任务,从而达到复用已有功能的目的。简单的说是,尽量使用对象的组合/聚合(has-a),而不是继承(is-a)来达到复用的目的。

组合/聚合和继承是实现代码复用的两种基本途径,在设计过程中尽量使用组合/聚合,而不是继承。因为继承使得基类与子类有较强的耦合性,通常情况下基类的内部细节对子类来说是可见的,这样基类的实现细节会暴露给子类,破坏了系统的封装性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值