从零开始单排学设计模式「单一职责原则」黑铁 - 青铜 晋级赛
阅读本文大概需要 3.7 分钟。
本篇是设计模式系列的第五篇,虽然之前也写过相应的文章,但是因为种种原因后来断掉了,而且发现之前写的内容也很渣,不够系统。
所以现在打算重写,加上距离现在也有一段时间了,也算是自己的一个回顾吧!
学而时习之,不亦说乎。
推荐阅读:
目前段位:黑铁 I - 青铜 III 晋级赛
Let's Go!
前言
设计模式不是语法,是一种巧妙的写法,能把程序变的更加灵活。架构模式比设计模式大,架构模式是战略,而设计模式是战术。
设计模式(面向对象设计)中有6大原则,分别是:单一职责原则、里氏替换原则、依赖倒转原则、接口隔离原则、迪米特法则、开闭原则。
单一职责原则
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为,就一个类而言,应该只有一个引起它变化的原因。
通俗易懂的讲,就是:一个类不能太“累“!它是最简单的面向对象设计原则,用于控制类的粒度大小。
在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
业务需求
实现一个手机系统,具备拍照和播放音乐的功能。
代码实现
既然要实现拍照和播放音乐的功能,那我们先定义两个功能接口。
照相接口
/** * 具有照相功能的接口 * @author: LKP * @date: 2019/2/16 */ public interface IPhotograph { void photograph(); }
播放音乐接口
/** * 具有播放音乐功能的接口 * @author: LKP * @date: 2019/2/16 */ public interface IPlayMusic { void playMusic(); }
不遵循单一原则的设计,播放音乐及拍照功能的改变都会引起变化。
手机类
/** * @author: LKP * @date: 2019/2/16 */ public class MobilePhone implements IPhotograph, IPlayMusic { /** * 拍照 */ @Override public void photograph() { System.out.println("拍照片"); } /** * 播放音乐 */ @Override public void playMusic() { System.out.println("播放音乐"); } }
客户端代码
/** * @author: LKP * @date: 2019/2/16 */ public class Main { public static void main(String[] args) { IPlayMusic musicPlayer; IPhotograph photographer; MobilePhone phone = new MobilePhone(); musicPlayer = phone; photographer = phone; //播放音乐 musicPlayer.playMusic(); //拍照 photographer.photograph(); } }
遵循单一原则的设计,引发改变的只有播放音乐功能的变化。
音乐播放器类
/** * 实现播放音乐功能的音乐播放器类 * @author: LKP * @date: 2019/2/16 */ public class MusicPlayer implements IPlayMusic { @Override public void playMusic() { System.out.println("播放音乐"); } }
遵循单一原则的设计,引发改变的只有摄像功能的变化。
摄像机类
/** * 实现照相功能的摄像机类 * @author: LKP * @date: 2019/2/16 */ public class Carmera implements IPhotograph { @Override public void photograph() { System.out.println("拍照片"); } }
客户端代码
/** * @author: LKP * @date: 2019/2/16 */ public class Main { public static void main(String[] args) { IPlayMusic musicPlayer; IPhotograph photographer; //MobilePhone phone = new MobilePhone(); //musicPlayer = phone; //photographer = phone; musicPlayer = new MusicPlayer(); photographer = new Carmera(); //播放音乐 musicPlayer.playMusic(); //拍照 photographer.photograph(); } }
糟糕的设计会造成什么样的后果呢?
让我们来设想一下,有一天我们的需求发生了变化(需求变化-程序员一生的敌人兼朋友),现有拥有了一部手机,有拍照的功能以及播放音乐的功能,满足了现有的需求。
突然有一天觉得简单的拍照功能已经不能满足了,现在需要能够拍摄高清图片的照相功能,那么怎么办呢?换手机呗,找一个支持高清拍照功能的手机。
那么好的,需求又变了,现在想要能播放高品质音乐的功能,但是新换的支持高清拍摄的手机的硬件不支持高品质音乐播放,好的,继续换手机,前提是还要支持拍摄高清照片。
相信现在已经能够看出一些端倪了,这两个职责无论哪一个发生了变化,你都要去改变手机,现在只有两个职责,夸张一点说,如果有十个职责呢?那么岂不是要天天换手机,要么就不满足需求变化。
既然我们看到了糟糕的设计,现在我们回到单一职责上,既然你的需求是拍照片和播放音乐,那么我给你一台相机还有一台音乐播放器,哪个功能需要改变你就换哪个,以后你要换的时候也不必去考虑其他功能,只需要关心引起你自己变化的原因。
如果拍照功能发生改变,我们就去改变照相机,播放音乐功能需要改变我们就去改变音乐播放器。
我们不需要去考虑播放高品质音乐是不是会对拍摄高清图片的功能造成影响。
我们一定要遵循单一职责原则吗?
在现有的需求上能做到当然可以去做,但是往往有的时候,需求不是在设计的时候发生改变,而是一定程度之后,你已经有了一定的代码量了,可能修改的开销很高,这个时候就仁者见仁智者见智。
就如上述,若是将手机类拆分,则影响了底层调用的实现,也需要修改,弱是调用的地方太多,那么修改的地方也会很多,若是发布了,改起来也不是很方便。
但是当然,也有一定的手法来做这件事情,比如手机类保留,让手机类拥有一个摄像机类对象和一个音乐播放器类对象,然后播放音乐方法则调用音乐播放器类实例的播放音乐功能,照相功能则调用摄像机类实例的照相功能。
这样可以在不影响原有的东西的基础上又遵循原则。
总结
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
遵循单一职责原则的优点:
1)降低类的复杂度,一个类只负责一项职责。
2)提高类的可读性,可维护性
3)降低变更引起的风险。
往期精彩回顾