???前言:听说有本很牛的关于Java设计模式的书——重学Java设计模式,然后买了(*^▽^*)
开始跟着小傅哥学Java设计模式吧,本文主要记录笔者的学习笔记和心得。
打卡!打卡!
(引读:这里的节奏是,先说一下概念定义,然后是模拟场景,最后是反例、正例。)
单一职责原则,它规定一个类应该只有一个发生变化的原因。
为什么?
因为如果开发的一个功能不是一次性的,当一个Class类负责超过两个及以上职责时,当需求不断迭代、实现类持续扩张,就会出现难以维护、不好扩展、测试难度大和上线风险高等问题。
一个视频网站用户分类的例子:
根据上面的需求,直接编码,实现一个最简单的基本功能:根据不同的用户类型,判断用户可以观看的视频类型。
public class VideoUserService {
public void serveGrade(String userType){
if ("VIP用户".equals(userType)){
System.out.println("VIP用户,视频1080P蓝光");
} else if ("普通用户".equals(userType)){
System.out.println("普通用户,视频720P超清");
} else if ("访客用户".equals(userType)){
System.out.println("访客用户,视频480P高清");
}
}
}
? ? ? ? 如上,这一个类包含着多个不同的行为,多种用户职责,如果在这样的类上继续扩展功能就会显得很臃肿。比如再加一个“超级VIP会员”,可以超前点播,按上面的实现方式,只能继续ifelse。这样的代码结构每次迭代,新需求的实现都可能会影响到其他逻辑。
? ? ? ? 视频播放是视频网站的核心功能,当完成核心功能的开发后,就需要不断地完善用户权限,才能更好运营网站。其实就是不断建设用户权益,根据不同的用户类型提供差异化服务。
? ? ? ? 为了满足不断迭代的需求,就不能向上面一样把所有职责行为混为一谈,而是应该提供一个上层的接口类,对不同的差异化用户给出单独的实现类,拆分各自的职责。
(1)定义接口
public interface IVideoUserService {
// 视频清晰级别;480P、720P、1080P
void definition();
// 广告播放方式;无广告、有广告
void advertisement();
}
? ? ? ? 定义出上层接口IVideoUserService,统一定义需要实现的功能,包括视频清晰级别接口definition()、广告播放方式接口advertisement()。然后三种不同类型的用户就可以分别实现自己的服务类,做到职责统一。
(2)实现类
????????1)访问用户,只能看480P的高清视频,有广告
public class GuestVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("访客用户,视频480P高清");
}
public void advertisement() {
System.out.println("访客用户,视频有广告");
}
}
? ? ? ? 2)普通会员,可以看720P的超清视频,有广告
public class OrdinaryVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("普通用户,视频720P超清");
}
public void advertisement() {
System.out.println("普通用户,视频有广告");
}
}
? ? ? ? 3)VIP会员,付费的大哥,可以看1080P的蓝光视频,无广告
public class VipVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("VIP用户,视频1080P蓝光");
}
public void advertisement() {
System.out.println("VIP用户,视频无广告");
}
}
? ? ? ? 假设有新的需求如下:7天试用VIP会员,可以试用看1080P的蓝光视频,但是有广告。
// 7天试用VIP用户
public class TryVipVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("7天试用VIP用户,视频1080P蓝光");
}
public void advertisement() {
System.out.println("7天试用VIP用户,视频有广告");
}
}
????????在项目开发的过程中,尽可能保证接口的定义、类的实现以及方法开发保持单一职责,对项目后期的迭代和维护是很好的。
在面向对象编程领域中,开闭原则规定软件的对象、类、模块和函数对扩展应该是开放的,但是对于修改是封闭的。
这就意味着应该用抽象定义结构,用具体实现扩展细节,以此确保软件系统开发和维护过程的可靠性。
开闭原则的核心思想可以理解为面向抽象编程。
小结:对扩展是开放的,对修改是封闭的。
?对于外部调用方,只要能体现出面向抽象编程,定义出接口并实现其方法,即不修改原有方法体,只通过继承方式进行扩展,都可以体现出开闭原则。
?(1)场景案例
计算三种形状的面积,长方形、三角形,圆形。其中圆的π=3.14,但后续由于π的取值精度不适用于后面的场景,需要再扩展,接下来模拟这个场景来体现开闭原则。
(2)定义接口
public interface ICalculationArea {
/**
* 计算面积,长方形
*
* @param x 长
* @param y 宽
* @return 面积
*/
double rectangle(double x, double y);
/**
* 计算面积,三角形
* @param x 边长x
* @param y 边长y
* @param z 边长z
* @return 面积
*
* 海伦公式:S=√[p(p-a)(p-b)(p-c)] 其中:p=(a+b+c)/2
*/
double triangle(double x, double y, double z);
/**
* 计算面积,圆形
* @param r 半径
* @return 面积
*
* 圆面积公式:S=πr2
*/
double circular(double r);
}
?(3)实现类
特别地,这里的π取3.14D,这也是要扩展精度的方法和体现开闭原则的地方。
public class CalculationArea implements ICalculationArea {
private final static double π = 3.14D;
public double rectangle(double x, double y) {
return x * y;
}
public double triangle(double x, double y, double z) {
double p = (x + y + z) / 2;
return Math.sqrt(p * (p - x) * (p - y) * (p - z));
}
public double circular(double r) {
return π * r * r;
}
}
如果不考虑开闭原则,也不考虑整个工程服务的使用情况,直接改π值。
private final static double π = 3.141592653D;
更好的做法,按照开闭原则。继承父类,扩展需要的方法,同保留原有的方法,新增自己需要的方法。它的主要目的是不能因为个例需求的变化二改变预定的实现类。
public class CalculationAreaExt extends CalculationArea {
private final static double π = 3.141592653D;
@Override
public double circular(double r) {
return π * r * r;
}
}
扩展后的方法满足了π精度变化的需求,需要使用此方法的用户可以直接调用。而其他的方法,也不影响继续使用。