Java 设计模式之设计原则

一 概述

1.1 设计模式的目的

编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好的:

  • 代码重用性 (即:相同功能的代码,不用多次编写)
  • 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
  • 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
  • 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
  • 使程序呈现高内聚,低耦合的特性

1.2 设计模式的七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么这样设计的依据)

  • 单一职责原则
  • 里氏替换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特法则
  • 组合/聚合复用原则
  • 开闭原则

二 单一职责原则

单一职责 SRP–Single Responsibility Principle(高内聚、低耦合)

应该有且仅有一个原因引起类的变更

  • 系统中的每个类都应该只有一个职责,而所有类所关注的就是自身之职责的完成
  • 职责是指为”变化的原因”
  • 如果能想到多个原因去改变一个类,这个类就是多个职责
  • 并不是单一功能原则,并不是每个类只能有一个方法,而是单一”变化的愿意”原则
  • 如果一个类有多个职责,这些职责就耦合在了一起,当一个职责发生变化时,可能会影响其他职责
  • 多个职责耦合在一起,会影响服用性(可能只需要服用该类的某一个功能,但是该职责和其他职责耦合在一起,很难分离出来)

其实就是我们常说的高内聚低耦合原则,单一职责原则是最简单也非常难实现的原则

好处

  • 类的复杂性降低,实现什么职责都有清晰明确的定义
  • 可读性提高,复杂性降低,那当然可读性提高了
  • 可维护性提高,可读性提高,那当然更容易维护了
  • 变更引起的风险降低,变更时必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

适用于:模块、类、接口、方法。
建议:接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。

举例:

MVC 模式下,无论是 Controller 层还是 service 亦或是 DAO 层,针对每个表格都有独立的实现类,不要掺杂在一起,以免因为任何一个操作的变化都引起类的修改。

三 里氏替换原则

里氏替换原则 LSP------ Liskov Substitution Principle(低耦合)

所有引用的基类的地方必须能够透明的使用其子类的对象,也就是一句话:子类型必须能够替换掉它们的父类型。

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类必须完全实现了父类的方法,具备父类完全的功能
    如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。
  • 子类可以有自己的特征
  • 覆盖和实现父类方法时,输入的参数可以被放大,但是不能被缩小(重载而非覆盖)
  • 覆盖和实现父类方法时,输出的结果可以被缩小,但是不能被放大
    父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。

如果不遵循里氏替换原则,代码出现问题的几率会大大的增加。

好处:

为我们如何实现良好的继承和使用多态提供了依据,也是实现开闭原则的重要保证

最佳实践

在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。

四 依赖倒置原则

依赖倒置 DIP–Dependence Inversion Principle

定义:

  • 高层模块不应该依赖底层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象

也就是要针对接口编程,不要针对实现编程。

程序中所有的依赖关系都终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计。

这个是开闭原则的基础,“面向接口编程”——OOD(Object-Oriented Design,面向对象设计)的精髓之一。

在实际编程中,我们一般需要做到如下3点:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的
  • 接口或抽象类不依赖于实现类
  • 实现类依赖接口或抽象类

更加精简的定义就是“面向接口编程”。

最佳实践

依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,我们怎么在项目中使用这个规则呢?只要遵循以下的几个规则就可以:

  • 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
    这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置

  • 变量的表面类型尽量是接口或者是抽象类

  • 任何类都不应该从具体类派生

  • 尽量不要覆写基类的方法

  • 结合里氏替换原则使用
    父类出现的地方子类就能出现,再 DIP,我们可以得出这样一个通俗的规则: 接口负责定义 public 属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

依赖倒置原则是7个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。

五 接口隔离原则

接口隔离原则 Interface Segregations Principle, ISP(高内聚)

接口隔离原则有以下两种定义:

  • 客户端不应该依赖它不需要的接口
    客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性
  • 类间的依赖关系应该建立在最小的接口上
    要求是最小的接口,也是要求接口细化,接口纯洁,与第一个定义如出一辙,只是一个事物的两种不同描述

含义:建立单一接口,尽量细化接口,接口中的方法尽量少。

为各个类建立专用的接口。
在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。
运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。
设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

接口隔离原则 VS 单一职责原则

  • 接口隔离原则与单一职责的审视角度是不相同的
  • 单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分
  • 接口隔离原则要求接口的方法尽量少

例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”。专门的接口指什么?就是指提供给每个模块的都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。

最佳实践

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。但是,这个原子该怎么划分是设计模式中的一大难题,在实践中可以根据以下几个规则来衡量:

  • 一个接口只服务于一个子模块或业务逻辑
  • 通过业务逻辑压缩接口中的 public 方法,接口时常去回顾,尽量让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法
  • 已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理
  • 了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,环境不同,接口拆分的标准就不同。深入了解业务逻辑,结合实际情况进行接口设计

六 迪米特法则

迪米特法则 Law of Demeter,LoD(松耦合)

也称为最少知道原则(Least KnowledgePrinciple,LKP)

定义

一个对象应该对其他对象有最少的了解。

通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多 public 方法,我就调用这么多,其他的我一概不关心。

迪米特法则还有一个更简单的定义:只与直接的朋友通信。
一句话总结就是:一个对象应该对其他对象保持最少的了解。

最佳实践

  • 迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。在采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合

  • 迪米特法则要求类间解耦,但解耦是有限度的,除非是计算机的最小单元——二进制的0和1。那才是完全解耦,在实际的项目中,需要适度地考虑这个原则,别为了套用原则而做项目。原则只是供参考,如果违背了这个原则,项目也未必会失败,这就需要大家在采用原则时反复度量,不遵循是不对的,严格执行就是“过犹不及”。

七 组合/聚合复用原则

组合/聚合复用原则 Composition/Aggregation Reuse Principle (松耦合)

当一个类想使用另一个类的功能时,优先使用对象的组合,而不是继承,尽量多使用组合。

合成聚合复用原则是指在一个新对象中通过组合关系使用原来已有的一些对象,使之成为新对象的一部分,通过使用已有对象的 API 完成已有功能的调用。

为什么要是用合成聚合,尽量不要使用继承?

  • 继承破坏包装,把超类的实现细节直接暴露给子类,不利于信息的隐匿
  • 如果父类发生改变,会引发一系列子类的改变,类之间耦合度高
  • 继承是一种静态功能的使用,在运行的过程中不能发生改变,聚合复用可以动态传入子类对象实现功能动态改变

好处

非常有利于构建可维护,可复用,可扩展和灵活性好的软件系统。

八 开闭原则

开闭原则 OCP— Open Closed Principle(高内聚、低耦合)

开闭原则是 Java 世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统

定义:一个软件实体如类、模块和函数应该对拓展开放,对修改关闭。

换句话说,一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。

因为需求有变化,要求我们设计程序时必须为程序功能的新增留好接口,在新增功能时,不要修改原有代码,而是新增代码。

经典的话说:过去的事已成为历史,是不可修改的,因为时光不可倒流,但现在或明天计划做什么,是可以自己决定(即扩展)的。

重要性

开闭原则是最基础的一个原则,前面节介绍的六个原则都是开闭原则的具体形态,也就是说前六个原则就是指导设计的工具和方法,而开闭原则才是其精神领袖。换一个角度来理解,依照 Java 语言的称谓,开闭原则是抽象类,其他六大原则是具体的实现类,开闭原则在面向对象设计领域中的地位就类似于牛顿第一定律在力学、勾股定律在几何学、质能方程在狭义相对论中的地位,其地位无人能及。

如何使用开闭原则

  • 实现开闭原则的关键是抽象
  • 定义一个抽象层,只规定功能而不提供实现,实现通过定义具体的类来完成
  • 当需求变化时,不通过修改抽象层来完成,而是通过定义抽象层的新实现完成
  • 通过抽象类及接口,规定了具体的类的特征作为抽象层,相对稳定,不需要修改,从而满足对修改关闭,从抽象类到处的具体类可以作为改变系统的行为,从而满足对扩展开放

最佳实践

  • 开闭原则也只是一个原则
    开闭原则只是精神口号,实现拥抱变化的方法非常多,并不局限于这6大设计原则,但是遵循这6大设计原则基本上可以应对大多数变化。因此,我们在项目中应尽量采用这6大原则,适当时候可以进行扩充,例如通过类文件替换的方式完全可以解决系统中的一些缺陷。大家在开发中比较常用的修复缺陷的方法就是类替换,比如一个软件产品已经在运行中,发现了一个缺陷,需要修正怎么办?如果有自动更新功能,则可以下载一个.class文件直接覆盖原有的class,重新启动应用(也不一定非要重新启动)就可以解决问题,也就是通过类文件的替换方式修正了一个缺陷,当然这种方式也可以应用到项目中,正在运行中的项目发现需要增加一个新功能,通过修改原有实现类的方式就可以解决这个问题,前提条件是:类必须做到高内聚、低耦合,否则类文件的替换会引起不可预料的故障。

  • 项目规章非常重要
    如果你是一位项目经理或架构师,应尽量让自己的项目成员稳定,稳定后才能建立高效的团队文化,章程是一个团队所有成员共同的知识结晶,也是所有成员必须遵守的约定。优秀的章程能带给项目带来非常多的好处,如提高开发效率、降低缺陷率、提高团队士气、提高技术成员水平,等等。

  • 预知变化
    在实践中过程中,架构师或项目经理一旦发现有发生变化的可能,或者变化曾经发生过,则需要考虑现有的架构是否可以轻松地实现这一变化。架构师设计一套系统不仅要符合现有的需求,还要适应可能发生的变化,这才是一个优良的架构。

开闭原则是一个终极目标,任何人包括大师级人物都无法百分之百做到,但朝这个方向努力,可以非常显著地改善一个系统的架构,真正做到“拥抱变化”。

九 总结

首先,无论是7大设计原则还是 23 种设计模式,根本目标其实就是一个:“拥抱变化”,包括需求的变化、运行环境的变化、性能要求的提升等等,实现一个需求并不难,但是当变化来临时,能否泰然处之,那就是个技术活了。

其次,“拥抱变化"落实到代码层面是什么?——你的代码是可维护的、可扩展的,还是说"牵一发动全身”,一点小的改动很多东西都要跟着变动。

再次,要做到"拥抱变化",让你的代码很容易维护和扩展,核心理念就是"高内聚、低耦合",以及"面向接口编程(面向抽象编程)”

最后,设计原则、设计模式是前辈总结的"经验",但不是"条款",尽可能遵循这些规范会让你的设计无限接近完美,但世界上本就没有十全十美的东西,凡事都要有个度,不要认死理,不要为了"套模式"而应用设计模式,要具体问题具体分析,根据实际情况进行权衡。

题外话:设计做得再漂亮,代码写得再完美,项目做得再符合标准,一旦项目亏本,产品投入大于产出,那整体就是扯淡!一切必须得成功!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值