单一职责原则是说一个类只有一个发生变更的原因,这个是在类层之上的,那在接口层次,方法层次,
也要遵循单一原则,那我们现在结合一些例子来说明一下,那单一职责的好处呢,首先降低类的复杂性,提高可读性,
提高维护性,最重要的是变更的时候风险率降低,那我们现在结合例子一起来体会一下,单一职责原则,首先呢我们
现在有一个类
这个类图还是非常非常简单的,其实是由Test创建这两个类,这个Bird类现在已经不用了,那职责在类的层次上,
还是比较清晰的,那刚刚说的是类的职责上,那我们再看一下在接口级别上,还是用我们的课程举例,ICourse是一个
接口
package com.learn.design.principle.singleresponsibility;
/**
* 鸟主要的移动方式我们来学一下
*
* @author Leon.Sun
*
*/
public class Bird {
/**
* 主要是用翅膀飞
* 这里面我们增加一个birdName
* 鸟的名称
*
*
* @param birdName
*/
public void mainMoveMode(String birdName){
/**
* 这里要做一个判断
* 这个就非常符合我们的日常开发
* 一个需求来说
* 我们在这里做一个修改
* 其实是最快的方式
* 我们在实际开发中还要考虑开发的成本
* 时间,进度
* 完全遵守单一原则
* 有的时候还是要看实际情况的
* 但是我们有一颗按照原则写代码的心
* 条件允许的情况下
* 还是要大家来遵守这些设计原则
* 那需求现在增加了鸵鸟
* 那我们在原有的类加一个判断
*
*
*/
if("鸵鸟".equals(birdName)){
/**
* 如果是鸵鸟的话
* 用脚走
* 鸵鸟用脚走
*
* 这里面有bug了
* System.out.println(birdName+"用翅膀飞");
* 下一行又走到了这里
* 这里就是一个典型的一个例子
* 那我们在扩展的时候
* 对于一些边界
* 我们这里增加了if
* 但是下边并没有放到else里
* 这种情况也比较常见
* 正确的写法应该是这样的
* 如果继续扩展下来
* 包括企鹅
* 企鹅也是鸟
* 企鹅也是用脚走
* 那鸵鸟跟企鹅呢
* 我们还可以细分一下
* 那如果现在再传来一些特殊的鸟类
* 那我们的这个方法还要继续扩展
* 还记得我们说的
* 单一职责原则其中有一个最重要的好处
* 他就是变更时风险率降低
* 那现在是不遵循单一原则的
* 所以风险还是比较大
* 当然我们看到的这个方法还比较简单
* 那实际的业务比这个复杂
* 边界比这个判断要 更多一些
* 那我们现在从类的形式来把这个拆分开
*
*/
System.out.println(birdName+"用脚走");
}else{
System.out.println(birdName+"用翅膀飞");
}
}
}
package com.learn.design.principle.singleresponsibility;
/**
* 我们 创建一个类FlyBird
*
*
* @author Leon.Sun
*
*/
public class FlyBird {
public void mainMoveMode(String birdName){
System.out.println(birdName+"用翅膀飞");
}
}
package com.learn.design.principle.singleresponsibility;
/**
* 我们再创建一个类WalkBird
* 走路的鸟
*
*
* @author Leon.Sun
*
*/
public class WalkBird {
public void mainMoveMode(String birdName){
System.out.println(birdName+"用脚走");
}
}
package com.learn.design.principle.singleresponsibility;
/**
* 我们写一个测试类
*
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) {
/**
* new一个bird
* 我们传进去一个大雁
* 大雁用翅膀飞
* 我现在再传一个鸵鸟
* 那鸵鸟用翅膀飞
* 就不对
* 因为鸵鸟飞不起来
* 那我们再看一下这个类
*
* 可以看到大雁没有问题
* 用翅膀飞
* 那鸵鸟用脚走
* 鸵鸟用翅膀飞
*
*
*/
// Bird bird = new Bird();
// bird.mainMoveMode("大雁");
// bird.mainMoveMode("鸵鸟");
/**
* 那我们飞的鸟和走的鸟都区分一下
* 这块还是比较简单的
* 我们应用层来判断这个逻辑
* 如果是大雁
* 我们就用FlyBird
* 如果是鸵鸟我们就用行走的Bird
* 那这个就是类的单一原则的体现
* 我们把一个类进行拆分
* 这样我们就使每个类的方法职责单一的
* 比较简单
* 也不至于引入的时候出现新的问题
* 那我们来看一下类图
*
*
*/
FlyBird flyBird = new FlyBird();
flyBird.mainMoveMode("大雁");
WalkBird walkBird = new WalkBird();
walkBird.mainMoveMode("鸵鸟");
}
}
课程类实现两个接口,我们可以通过实现一个接口或者多个接口,来组合出这个实现类的一个实现,但是我也可以实现
一个接口,也就是我们这个实现类实现什么职责呢,都是有清楚明确的定义,复杂性也是降低了,复杂性降低了可读性也就提高了,
可读性提高了也就更容易维护了,可读性也就提高了,同时变更引起的影响降低了,一个接口的更改只对相应的实现类有影响,
与其他的接口无关,这一点对项目的帮助是非常大的,这个就是从接口级别上来讲,单一职责,刚刚类,接口,我们再来看看
单一职责
package com.learn.design.principle.singleresponsibility;
/**
* 这个是一个接口
* 我们来看一下这个接口
* 对于课程来说
* 获得课程的名字
* 获得课程的视频
* 那在接口的基础上做单一原则的话
* 也就是ICourse这个接口
* 可不只有一个职责
* 首先一个大的职责是获得课程的信息
* 比如名称和视频字节流
* 另外一个职责是管理课程
* 和课程内容无关
* 例如学习课程
* 那如果学习课程需要获取name
* 视频字节流
* 如果退了这个课程呢
* 可能就获取不了这个名字和字节流了
* 因为这个课程已经被我退掉了
* 那退会影响获取内容的变化
* 这两个职责是互相影响的
* 现在我们退了这个课程
* 那获取课程信息的时候
* 我们这个实现就获取不到
* 就可以了
* 那整体来看
* 这个接口两个职责
* 获取课程相关信息
* 还有课程管理上的相关处理
* 那我们就可以把这个接口拆成两个接口
* 一个是获取课程信息的接口
* 另外是课程管理的接口
* 我们来尝试一下
*
*
* @author Leon.Sun
*
*/
public interface ICourse {
/**
* 我是可以获得课程的名称的
*
*
* @return
*/
String getCourseName();
/**
* 我还可以获得一个视频
* 拿到一个字节流
*
* @return
*/
byte[] getCourseVideo();
/**
* 学习课程
*
*/
void studyCourse();
/**
* 退款
*
*/
void refundCourse();
}
package com.learn.design.principle.singleresponsibility;
public interface ICourseManager {
/**
* 然后把这个拿到这边
*
*/
void studyCourse();
void refundCourse();
}
package com.learn.design.principle.singleresponsibility;
/**
* 注意这个关键字是interface
*
* @author Leon.Sun
*
*/
public interface ICourseContent {
/**
* 我们把这两个拿到这里边
*
* @return
*/
String getCourseName();
byte[] getCourseVideo();
}
package com.learn.design.principle.singleresponsibility;
/**
* 我们现在来实现一个Course的实现类
* 他来实现ICourseManager这个接口
* 同时来实现ICourseContent这个接口
* CourseImpl实现类实现这两个接口
* 我们来看一下类图
*
*
* @author Leon.Sun
*
*/
public class CourseImpl implements ICourseManager,ICourseContent {
@Override
public void studyCourse() {
}
@Override
public void refundCourse() {
}
@Override
public String getCourseName() {
return null;
}
@Override
public byte[] getCourseVideo() {
return new byte[0];
}
}
package com.learn.design.principle.singleresponsibility;
/**
* 那我们简单的总结一下
* 类的单一职责原则和接口方法的单一职责是一样的
* 但是我们在实际的项目开发中
* 我们在创建类的时候
* 包括依赖组合聚合
* 受很多因素的影响
* 包括我们项目的规模
* 还有项目的周期
* 技术人员的水平
* 还有对进度的把控
* 这个都是一个平衡的因素
* 那另外有一个考虑
* 也就是我们在扩展的时候
* 如果我们没有面向接口的编程
* 而又非常遵循单一职责原则
* 可能引起类的一个爆炸
* 类的数量会比较多
* 所以我们在总结起来
* 就是说在实际的开发中
* 我们的接口和方法
* 一定要做到单一职责
* 这个其实还是蛮好的
* 对于我们维护起来也会比较方便
* 而且成本也非常低
*
*
* @author Leon.Sun
*
*/
public class Method {
/**
* 我们现在有一个方法叫updateUserInfo
* 这里面可以更新用户名称
* 还可以更新地址
* 其实这个方法就是同时更新userName和地址
*
*
* @param userName
* @param address
*/
private void updateUserInfo(String userName,String address){
/**
* 这里是一个伪代码
* 大家认为是一个更新的过程就可以了
*
*
*/
userName = "geely";
address = "beijing";
}
/**
* 如果我们还有一种写法
* 如果是一个可变长度的参数
* 还有其他的properties
* 其他的属性
* 那这个方法的职责就更多了
* 不一定更新什么
* 这里是一个可变长的参数
* 可变长的参数肯定是放在方法最后一个位置
* 声明才可以
* 这里面可能包括各种信息
* 例如说他的体重
* 那这个方法从命名上
* 包括里面的实现上
* 职责就不是单一的
*
*
* @param userName
* @param properties
*/
private void updateUserInfo(String userName,String... properties){
userName = "geely";
// address = "beijing";
}
/**
* 那更好的方式应该是这样的
* 大家可以理解他的一个变化
* 只更新名字
* 这个就叫做updateUsername
* 需要更新name的话
* 就调用这个方法
* 这两个方法的职责是非常的单一且清晰的
* 那我们在写方法的时候
* 经常还会碰到这个场景
*
*
* @param userName
*/
private void updateUsername(String userName){
userName = "geely";
}
/**
* 这个就叫做updateUserAddress
* 我们把之前的那个方法拆分成两个方法
* 如果我们需要更新地址的话
* 那就调用这个方法
*
*
* @param address
*/
private void updateUserAddress(String address){
address = "beijing";
}
/**
* 后面多了一个布尔
* 这个方法里面传了一个布尔类型
* 这就有意思了
* 我们看一下
*
*
* @param userName
* @param address
* @param bool
*/
private void updateUserInfo(String userName,String address,boolean bool){
/**
* 我们一般会这么写
* 也就是布尔类型要么true要么false
* 其实这个方法的职责要么todo something1
* 还有todo something2
* 那这种情况我们就应该把这个方法拆开
* 因为这个方法很明显的就是两个职责
* 如果你传进来的布尔你没有使用
* 如果使用的就拆开
* 有布尔的存在这个方法就不会有单一的职责
* 这样开发起来简单
* 维护起来也容易
*
*
*/
if(bool){
//todo something1
}else{
//todo something2
}
userName = "geely";
address = "beijing";
}
}