5分钟学设计模式之《OO设计原则》

面向对象设计原则是设计模式的基础,每一个设计模式都符合某一个或者多个面向对象设计原则,面向对象设计原则是用于评价一个设计模式的使用效果的重要指标之一。

最常见的 7 大设计原则如下:

 

单一职责原则

一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中

在软件系统中,一个类承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责越多,相当于将这些职责耦合在一起,当其中一个职责发生变化时可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中。下面通过一个简单的实例进一步分析单一职责原则:

某 CRM 系统中客户信息图形统计模块提出了如下的初始设计方案

在 CustomerDataChart 类的方法中,getConnection()方法用于连接数据库,findCustomers()用于查询所有的客户信息,createChart()方法用于创建图表,displayChart()用于显示图表。很明显 CustomerDataChart 类承担了太多的职责,现使用单一职责原则对其进行重构。

通过对当前类中的各种职责进行分析可知,主要有三种:获取数据库连接、从数据库获取数据、创建和展示图表。因此可拆分为 3 个类,分别是:

  • DBUtil: 数据库连接管理类,负责连接数据
  • CustomerDAO:数据库操作类,负责从数据库获取数据
  • CustomerDataChart:图表操作类,负责创建和展示图表

使用单一职责原则重构后的类图设计如下:

 

开闭原则

开闭原则:软件实体应当对扩展开放,对修改关闭

 

软件实体可以指一个软件模块、一个由多个类组成的局部结构或者一个独立的类。为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在 Java 等面向对象编程语言中,都提供了接口、抽象类等机制,可以通过他们定义系统的抽象层,在通过具体类来进行扩展,如果需要修改系统的行为,无须对抽象层进行改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

 

里氏代换原则

里氏代换原则:所有引用基类的地方必须能透明地使用其子类对象

里氏代换原则表明,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。

    在运用里氏代换原则时,应该将父类设计为抽象类或者接口,让子类继承父类或者实现父接口,并实现父类中声明的方法,在运行时子类实例替换父类对象,可以方便地扩展系统的功能,无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。

 

依赖倒转原则

依赖倒转原则:高层模块不应该依赖底层模块,它们都应该依赖抽象,抽象不应该依赖于细节,细节应该依赖于抽象

简单来说,依赖倒转原则要求针对接口编程,而不是针对实现编程。

依赖倒转原则要求在程序代码中传递参数时或在关联关系中尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不用具体类来做这些事情。一个具体的类应当只实现接口或者抽象类中声明过的方法,而不要给出多余的方案,否则将无法调用到在子类中增加的新方法。

在实现依赖倒转原则时需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入到其他对象中,常用的注入方式有 3 种,分别是:构造注入、设值注入和接口注入

下面通过一个简单的实例来加深对开闭原则、里氏代换原则和依赖倒转原则的理解:

某 CRM 系统中经常需要将存储在 TXT 或 Excel 的文件中的客户信息转存到数据库中,因此需要进行数据格式转换,在客户数据操作类 CustomerDAO中将调用数据格式转换类的方法来实现格式转换,初始方案设计如下:

    分析现有的设计,发现由于每次转换的数据来源不一定相同,因此需要经常更换数据转换类,因此需要修改 CustomerDAO代码,而且在引入并使用新的数据转换类时也不得不修改 CustomerDAO的代码,系统扩展性较差,违反了开闭原则,因此需要进行重构。如下图所示:

重构后,引入了抽象数据转换类,CustomerDAO针对抽象类DataConvertor编程,而将具体数据转换类名存储在配置文件中,符合依赖倒转原则。根据里氏代换原则,程序运行时具体数据转换类对象替换 DataConvertor类型的对象,程序不会产生任何异常。在更换具体数据转换类时无须修改源代码,只需要修改配置文件。如果需要增加新的数据转换类,只要将新增的数据转换类作为 DataConvertor的子类并修改配置文件即可,原有代码无须修改,满足开闭原则。

 

在重构过程中同时使用了开闭原则,里氏代换原则和依赖倒转原则,三者一般是同时出现,相辅相成。总结来说就是:开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段

 

接口隔离原则

接口隔离原则:客户端不应该依赖那些它不需要的接口

 对于“接口”,这里有两种具体的含义:

  • 一种是指某种编程语言中的interface 类型
  • 另一种是指一个类型所有具有的方法特征的集合

在本文中主要指第二种,具体来说就是定义接口时要尽可能提供职责唯一的接口,而不是大而全的接口,因为实现一个接口就得实现该接口的所有方法。大而全的接口不利于系统的维护与扩展。但同时也要控制接口粒度不能太细,否则可能会导致“接口爆炸”。

 

合成复用原则

合成复用原则:又称组合/聚合复用原则,优先使用对象组合,而不是通过继承来达到复用的目的

合成复用原则就是在一个新的对象里通过关联关系来使用一些已有对象,使之成为新对象的一部分。简而言之就是在复用对象时多用组合或者聚合,少用继承。

继承 VS 组合/聚合

  • 继承复用会破坏系统的封装性,因为继承会将类的实现细节暴露给子类,由于基类的某些内部细节对子类来说是可见的,因此这种复用又称为“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变。
  • 组合/聚合复用可以调用已有对象的功能,但是成员对象的内部实现细节对新对象是不可见的,因此这种复用又称为“黑箱”复用,对比继承复用而言,其耦合度更低,成员对象的变化对新对象的影响不大,而且合成复用可以在运行时动态进行,新对象可以动态的引用与成员对象类型相同的其他对象。

一般而言,如果两个类之间是“Has-A”的关系应使用组合/聚合,如果是“Is-A”的关系可以使用继承。

 

迪米特原则

迪米特原则:每个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位

迪米特原则要求一个软件实体应该尽可能少地与其他实体发生相互作用,从而降低系统的耦合度,使类与类之间保持松散的耦合关系。

迪米特原则有一种通俗的说法是“不要和陌生人说话”。意思是只与自己的朋友发生通信,对于“陌生人”不应该有直接的通信。“朋友”范围包括以下几类:

1、当前对象本身

2、以参数形式传入到当前对象方法中的对象

3、当前对象的成员对象

4、如果当前对象的成员对象是一个集合,那么集合中的元素也是朋友

5、当前对象所创建的对象

 

只要满足上述情况之一,就是当前对象的“朋友”,如果两个不必要的对象需要通信,则可以通过“第三方”对象来进行转发调用。因此将迪米特原则运用到系统设计中时,需要注意:

1、在类的划分上要尽量松耦合

2、每个类都应该尽可能降低其成员变量和成员函数的访问权限

 

总结

  • 单一职责原则要求在软件系统中一个对象只包含单一职责,并且该职责被完整地封装在一个类中
  • 开闭原则要求软件实体应该对扩展开放,对修改关闭
  • 里氏代换原则要求在软件中所有引用到基类的地方必须能透明地使用其子类的对象
  • 依赖倒转原则要求高层模块不依赖底层模块,他们都应该依赖抽象,抽象不依赖细节,细节应该依赖抽象
  • 接口隔离原则要求客户端不应该依赖那些它不需要的接口
  • 合成复用原则要求优先使用对象组合,而不是通过继承来达到复用的目的
  • 迪米特原则要求每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位

相关文章请点击:

5分钟学设计模式之《设计模式简介》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值