设计原则 - 单一职责原则

单一职责原则: 每个类只负责自己的事情,而不是变成万能的

含义

  • 单一职责(Simple Responsibility PincipleSRP)是指不要存在多于一个导致类变更的原因
  • 一个类只负责一项职责,应该仅有一个引起它变化的原因,且该职责被完整的封装在一个类中

优点

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多
  • 提高类的可读性,提高系统的可维护性
  • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响

实现方式

单一职责原则是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中,而发现类的多重职责需要设计人员具有较强的分析设计能力、对业务的了解和相关重构经验

问题

比如一个类 T 负责两个不同的职责:职责 P1,职责 P2。

当由于职责 P1 需求发生改变而需要修改类 T 时,有可能会导致原本运行正常的职责 P2 功能发生故障。

解决方法

遵循单一职责原则。分别建立两个类 T1、T2,使 T1 完成职责 P1 功能,T2 完成职责 P2 功能。

这样,当修改类 T1 时,不会使职责 P2 发生故障风险;同理,当修改 T2 时,也不会使职责 P1 发生故障风险。

扩展

说到单一职责原则,其实很多人不知不觉的都在使用,即使没有学习过设计模式的人,或者没有听过单一职责原则这个概念的人也会自觉的遵守这个重要原则,因为这是一个常识,比如你去在原有的项目上开发一个新的业务功能的时候,你肯定是会从新建立一个类,来实现一个新的功能,肯定不会基于原有的 A 功能身上直接写 B 业务的功能,肯定一般都是会新写一个类来实现 B 功能。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责 P 被分化为粒度更细的职责 P1 和 P2。

比如:类 T 只负责一个职责 P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责 P 细分为粒度更细的职责 P1,P2,这时如果要使程序遵循单一职责原则,需要将类 T 也分解为两个类 T1 和 T2,分别负责 P1、P2 两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类 T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责 P,在未来可能会扩散为 P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。

举例说明

类层面

/**
 * 调用类
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Course course = new Course();
        course.liveCourse("直播课");
        course.liveCourseEncrypt("直播课");
        course.ReplayCourse("录播课");
        course.ReplayCourseEncrypt("录播课");
    }
}
/**
 * 课程类
 */
public class Course {
    
    
    public void liveCourse(String courseName) {
    
    
        System.out.println(courseName + ":不能快进");
    }

    public void liveCourseEncrypt(String encryptType) {
    
    
        System.out.println("加密方式A" + encryptType);
    }

    public void ReplayCourse(String courseName) {
    
    
        System.out.println(courseName + ":可以任意的来回播放");
    }

    public void ReplayCourseEncrypt(String encryptType) {
    
    
        System.out.println("加密方式B" + encryptType);
    }
}

Course(课程)类承担了两种处理逻辑。假如,现在要对课程进行加密,那么直播课和录播课进行录像加密,但是加密方式不同,
这样就出现了多个引起课程类变化(类被修改)的原因(直播课还是录播课),
既然出现了多个原因,那我们就需要对职责进行分离解耦,拆分为直播课类,和录播课类

直播课(LiveCourse)

/**
 * 直播课
 */
public class LiveCourse {
    
    
    public void watch(String courseName) {
    
    
        System.out.println(courseName + ":不能快进");
    }

    public void encrypt(String encryptType) {
    
    
        System.out.println("加密方式A" + encryptType);
    }
}

录播课(ReplayCourse)

/**
 * 录播课
 */
public class ReplayCourse {
    
    
    public void watch(String courseName) {
    
    
        System.out.println(courseName + ":可以任意的来回播放");
    }

    public void encrypt(String encryptType) {
    
    
        System.out.println("加密方式B" + encryptType);
    }
}
/**
 * 课程
 */
public class Course {
    
    
    public static void main(String[] args) {
    
    
        LiveCourse liveCourse = new LiveCourse();
        liveCourse.watch("直播课");
        ReplayCourse replayCourse = new ReplayCourse();
        replayCourse.watch("录播课");
    }
}

如果后续在增加线下课,可以继续新加一个 OfflineCourse 类进行扩展;根据不同的业务进行划分,以它作为我们实现高内聚低耦合的指导方针

接口层面

业务继续发展,课程要做权限。没有付费的学员可以获取课程基本信息权限,已经付费的学员可以获得学习权限。那么对于控制课程层面上至少有两个职责。我们可以把展示职责和管理职责分离开来,都实现同一个抽象依赖。

设计一个顶层接口

public interface ICourse {
    
    
    //获得基本信息
    String getCourseName();

    //获得视频流
    byte[] getCourseVideo();

    //更新课程
    void updateCourse();

    //删除课程
    void deleteCourse();
}

通过学习类层面的单一职责我们可以把这个接口拆成两个接口类,创建一个接口 普通用户(ICourseInfo) 和 管理员(ICourseManager)

/**
 * 展示
 */
public interface ICourseInfo {
    
    
    //获得基本信息
    String getCourseName();

    //获得视频流
    byte[] getCourseVideo();
}
/**
 * 管理
 */
public interface ICourseManager {
    
    
    //更新课程
    void updateCourse();

    //删除课程
    void deleteCourse();
}

方法层面

public class AddMsg {
    
    
    private void add(String userName, String address, String[] addOther) {
    
    
        //添加用户信息
        userName = "张三";
        address = "常州";
        //添加其他无关信息
        addOther = new String[123];
    }
}

上面的 add()方法中都承担了多个职责,既可以修改用户信息,也可以修改其他杂七杂八的数据,明显不符合单一职责。那么我们做如下修改,把这个方法拆成两个:

public class AddMsg {
    
    
    private void addUser(String userName, String address) {
    
    
        //添加用户信息
        userName = "XiaoQiang";
        address = "Changsha";
    }

    private void addOther(String[] addOther) {
    
    
        //添加其他无关信息
        addOther = new String[123];
    }
}

这修改之后,开发起来简单,维护起来也容易。所以编写代码的过程,尽可能地让接口和方法保持单一职责,便于我们项目后期的维护

这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,需要根据实际情况来确定。我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;

例如本文所举的这个例子,它太简单了,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。

猜你喜欢

转载自blog.csdn.net/qq_42700109/article/details/132861191