【OO设计原则】——01单一职责

面向对象(OO)设计基础

1、面向对象的概念

面向对象(Object Oriented,OO)是软件开发方法,面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术 发展到一定阶段后的产物(早期的计算机编程是基于面向过程的方法)。

面向对象设计是以对象为中心,以类和继承为构造机制,将程序和数据封装其中,并充分利用接口和多态提供灵活性,来认识、理解、刻划客观世界和设计、构建相应的软件系统。

所以,起初,“面向对象”是专指在程序设计中采用封装继承多态等设计方法。后来,面向对象的思想已经涉及到软件开发的各个方面。如,面向对象的分析(OOA,Object Oriented Analysis),面向对象的设计(OOD,Object Oriented Design)、以及我们经常说的面向对象的编程实现(OOP,Object Oriented Programming)。

2、面向对象的特征

虽然各种面向对象编程语言相互有别,但都能看到它们对面向对象基本特征的支持,

即 “抽象、封装、继承、多态” :

– 抽象,先不考虑细节

良好的抽象策略可以控制问题的复杂程度,增强系统的通用性和可扩展性。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。

– 封装,隐藏内部实现

封装是指将现实世界中存在的某个客体的属性与行为绑定在一起,并放置在一个逻辑单元内。该逻辑单元负责将所描述的属性隐藏起来,外界对客体内部属性的所有访问只能通过提供的用户接口实现。

– 继承,复用现有代码

继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。通过类的继承关系,使公共的特性能够共享,提高了软件的重用性。

– 多态,改写对象行为

多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。多态性允许每个对象以适合自身的方式去响应共同的消息。多态性增强了软件的灵活性和重用性。

3、面向对象的设计目标

※可扩展性Extensibility:有了新的需求,新的性能可以容易添加到系统中,不影响现有的性能,也不会带来新的缺陷。

※可修改性Flexibility:系统一部分的代码要修改时不会破坏系统的现有结构,也不会影响到其它的部分。

※可替换性Pluggability:可以将系统中的某些代码替换为相同接口的其它类,不会影响到系统。

 

单一职责原则SRP(Simple Responsibility Principle)

There should never be morethan one reason for a class to change.

对于一个类,有且仅有一个引起它变化的原因。

通俗讲就是我们不要让一个类承担过多的职责。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会导致类的行为功能发生变化,,而这种变化将影响到该类不同职责的使用者。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到破坏。我们要做的就是要分离这种多职责的变化,从而降低耦合度。

单一职责适用于继承体系、类、接口,以及方法,即不管是类、接口还是方法都要做到只干一件事。

例1:

这里用一个IUserInfoService的例子来说明怎么样进行职责的划分: 下面的类图展示了用户相关的一些信息的操作行为,而且我们也会经常习惯性的在业务层去写一个这样服务类:

那么这个类存在什么问题呢? 乍一看没什么,其实是将用户属性和用户的行为混在了一起,对于用户属性一般我们应该是封装一个bean类,而用户行为应该独立出来,为什么?因为从业务逻辑来讲这在很大程度上会是变化的东西,比如现在是有login()、deleteUser()、addUser()和changePassword()几个行为,那么将来某一天老板说我加一个用户签到功能,好,于是你就要到IUserInfo接口中添加一个sign()方法, 结果隔了一天老板又说我要加一个用户评论功能,于是你又要加一个comment()方法。。这是不是在反复的修改这个类(接口)?所以这就没有做到有且仅有一个引起变化类的原因,所以我们要将那些变化的原因提取出来单独封装,而那些基础的属性像userName,userId的操作基本是共用而且不会变的。

所以我们按照单一职责,应该这个接口改造如下:

例2

这里举另外一个简单的例子来自于经典的《HeadFirst设计模式》,也是此书中的开篇例子,鸭子类的设计:

可以看到我们现在的类图设计是这样的,有一个鸭子的基类Duck,它具有quack(呱呱叫)、swim(游泳)、fly(飞行)、display(外观展示)几个方法,然后我们现在要用这个基类来生成一些鸭子,比如绿头鸭MallardDuck、红头鸭RedDuck以及玩具橡皮鸭RubberDuck,其中绿头鸭和红头鸭都是真实的鸭子,它们具有鸭子的基本的呱呱叫、会游泳和会飞本领,只是展示外观不同,所以覆写display()方法,而橡皮鸭也是一种鸭子但是它是一种玩具,只会吱吱叫并且不会飞,所以我们除了覆写display()以外,我们还要覆写quack()和fly()方法。

好,那么现在问题来了,并不是所有的鸭子类都是会呱呱叫和会飞的,假如boss现在要求再添加10种鸭子,其中有一些会叫但是不会飞,另一些会飞但是不会叫,还有一些是既不会飞也不会叫,那你是不是要到每一个子类中去覆写quack()和fly()实现不同的代码?所以这个就是变化的部分,就要按照单一职责,将变化的与不变的隔离开来,很显然我们需要将quack和fly这两个具有个体变化的行为独立分离出来。从类层次角度来看,也可以这样理解,出现问题的原因是超类没有进行很好的抽象,聚合过多职责(特征)。不变化的部分才是顶层类应该抽象出的相似属性、方法,而易变化的部分属于子类个性化特征,可由接口来扩展。

到这里,我们已经把Duck类中两个变化的个体行为职责从Duck类中剥离了出来,现在fly和quack行为作为两个独立的接口,只有需要他们的那些子类才去实现它们。这里你可能疑问,即便我的子类实现了fly和quack相关接口,还是要到每一个子类中去实现相关的方法,对的,因为java接口没有实现代码,所以不能做到代码复用,要解决这个问题,我们需要进一步做改造,这个改造涉及到了另一个设计原则,所以我们放到后面去讲。总之,到这里我们的Duck类已经完成了单一职责的改造,fly和quack的行为不会再影响它了(尽管有点问题,但我们在后面的原则中会很好的解决它)。

Single Responsibility Principle (SRP)从职责(改变理由)的侧面上为我们对类(接口)的抽象的颗粒度建立了判断基准:在为系统设计类(接口)的时候应该保证它们的单一职责性。  

单一职责的好处:

  • 类的复杂性降低,因为只干一件事
  • 可读性提高,复杂性降低了自然提高了可读性
  • 可维护性提高,可读性提高了自然更加易于维护
  • 变更的风险性降低,变更时只修改引起变化的类、接口或者方法,其他不受影响

总结:

  •  一个类只有一个引起它变化的原因,否则就应当考虑重构。
  • SRP由引起变化的原因决定,而不由功能职责决定。虽然职责常常是引起变化的轴线,但是有时却未必,应该审时度势。
  • 测试驱动开发,有助于实现合理分离功能的设计。
  • 可以通过Façade模式或Proxy模式进行职责分离。

其实单一职责是一个很有争议的职责,因为在实际项目当中最难确定的就是"职责",一个类到底负责的是哪些职责?有时我们往往难以确定。在实际当中很多的情况下,最终妥协的结果就是破坏了单一职责。

猜你喜欢

转载自blog.csdn.net/qq_42022528/article/details/86531677
今日推荐