• 「设计模式」六大原则之单一职责小结



    「设计模式」六大原则系列链接:
    「设计模式」六大原则之一:单一职责小结
    「设计模式」六大原则之二:开闭职责小结
    「设计模式」六大原则之三:里氏替换原则小结
    「设计模式」六大原则之四:接口隔离原则小结
    「设计模式」六大原则之五:依赖倒置原则小结
    「设计模式」六大原则之六:最小知识原则小结

    六大原则体现很多编程的底层逻辑:高内聚、低耦合、面向接口编程、面向抽象编程,最终实现可读、可复用、可维护性。

    设计模式的六大原则有:

    • Single Responsibility Principle:单一职责原则
    • Open Closed Principle:开闭原则
    • Liskov Substitution Principle:里氏替换原则
    • Law of Demeter:迪米特法则(最少知道原则)
    • Interface Segregation Principle:接口隔离原则
    • Dependence Inversion Principle:依赖倒置原则
      把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。

    本文介绍 SOLID 中的第一个原则:单一职责原则

    1.单一职责原则定义

    单一职责原则 [Single Responsibility Principle 简称 SRP],又叫单一功能原则。

    A class or module should have a single responsibility。

    我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。

    就一个类而言,应该仅有一个引起它变化的原因。

    所谓职责就是指类变化的原因,也就是业务需求。如果有一个类有多于一个的原因被改变,那么这个类就有超过两个及以上的职责。而单一职责原则约定一个应该有且只有一个改变业类的原因。

    单一职责原则的核心就是解耦和增强内聚性。

    一句话总结:一个类只干一件事

    应用实践:不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。

    2.如何理解单一职责原则(SRP)?

    一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。

    3. 如何判断类的职责是否足够单一?

    不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如,出现下面这些情况就有可能说明这类的设计不满足单一职责原则:

    • 类中的代码行数、函数或者属性过多;

      会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分(也就是重构)。

      如果你是没有太多项目经验的编程初学者,实际上,我也可以给你一个凑活能用、比较宽泛的、可量化的标准,那就是一个类的代码行数最好不能超过 200 行,函数个数及属性个数都最好不要超过 10 个。

    • 类依赖的其他类过多,或者依赖类的其他类过多;

      不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分。

    • 私有方法过多;

      我们就要考虑能否将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性。

    • 比较难给类起一个合适的名字;

      很难用一个业务名词概括,或者只能用一些笼统的 Manager、Context 之类的词语来命名,这就说明类的职责定义得可能不够清晰;

    • 类中大量的方法都是集中操作类中的某几个属性。

    4. 类的职责是否设计得越单一越好?

    单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

    5. 应用体现

    • Android 中的架构演变思想运用体现:Android 里面 Activity 过于臃肿会让感觉很头大,MVP, MVVM等框架都是为了让 Activity 变得职责单一。
    • 架构分层,每一层要有明确的职责
    • 微服务
    • 模块
    • 通信协议
    • 接口等的设计
    • ……

    6. 应用示例1

    比如, 我们在网站首页可以登录、注册和退出登录等操作为例:

    public interface UserOperate {
    
        void login(UserInfo userInfo);
    
        void register(UserInfo userInfo);
    
        void logout(UserInfo userInfo);
    }
    
    
    public class UserOperateImpl implements UserOperate{
        @Override
        public void login(UserInfo userInfo) {
            // 用户登录
        }
    
        @Override
        public void register(UserInfo userInfo) {
            // 用户注册
        }
    
        @Override
        public void logout(UserInfo userInfo) {
            // 用户退出
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    那如果按照单一职责原则拆分, 拆分为如下形式:

    public interface Register {
        void register();
    }
    public interface Login {
        void login();
    }
    
    public interface Logout {
        void logout();
    }
    
    public class RegisterImpl implements Register{
    
        @Override
        public void register() {
        }
    }
    
    public class LoginImpl implements Login{
        @Override
        public void login() {
        }
    }
    
    public class LogoutImpl implements Logout{
    
        @Override
        public void logout() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    具体实践中,上面可以做为参考,是否拆分这种粒度,得结合具体业务来。也可以在项目初期写一个粗略的类,后面根据业务发展再拆分重构,平衡得自己根据业务来把控。

    8 应用示例2(结合组合模式)

    相同的职责放到一起,不同的职责分解到不同的接口和实现中去,这个是最容易也是最难运用的原则,关键还是要从业务出发,从需求出发,识别出同一种类型的职责。

    人类的行为分成了两个接口:生活行为接口、工作行为接口,以及两个实现类。

    如果都用一个实现类来承担这两个接口的职责,就会导致代码臃肿,不易维护,如果以后再加上其他行为,例如学习行为接口,将会产生变更风险(这里用到了组合模式)。

    • 定义一个行为接口

      /**
       * 行为包括两种: 生活、工作
       */
      public interface IBehavior {
          
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    这里面定义了一个空的接口, 行为接口. 具体这个行为接口下面有哪些接口呢? 有生活和工作两方面的行为。

    • 定义生活和工作接口, 并且他们都是行为接口的子类

      生活行为接口:

      public interface LivingBehavior extends IBehavior{
          /** 吃饭 */
          void eat();
      
          /** 跑步 */
          void running();
      
          /** 睡觉 */
          void sleeping();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      工作行为接口:

      public interface WorkingBehavior extends IBehavior{
          /** 上班 */
          void goToWork();
      
          /** 下班 */
          void goOffWork();
      
          /** 开会 */
          void meeting();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 上述两个接口的实现类

      public class LivingBehaviorImpl implements LivingBehavior{
          @Override
          public void eat() {
              System.out.println("吃饭");
          }
      
          @Override
          public void running() {
              System.out.println("跑步");
          }
      
          @Override
          public void sleeping() {
              System.out.println("睡觉");
          }
      }
      public class WorkingBehaviorImpl implements WorkingBehavior{
      
          @Override
          public void goToWork() {
              System.out.println("上班");
          }
      
          @Override
          public void goOffWork() {
              System.out.println("下班");
          }
      
          @Override
          public void meeting() {
              System.out.println("开会");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
    • 行为组合的调用

      接下来会定义一个行为集合. 不同的用户拥有的行为是不一样 , 有的用户只用生活行为, 有的用户既有生活行为又有工作行为。

      我们并不知道具体用户到底会有哪些行为, 所以,通常使用一个集合来接收用户的行为. 用户有哪些行为, 就往里面添加哪些行为。

      • 行为组合接口

        public interface BehaviorComposer {
            void add(IBehavior behavior);
        }
        
        • 1
        • 2
        • 3
      • 实现组合接口

        public class IBehaviorComposerImpl implements BehaviorComposer {
        
            private List<IBehavior> behaviors = new ArrayList<>();
            @Override
            public void add(IBehavior behavior) {
                System.out.println("添加行为");
                behaviors.add(behavior);
            }
        
            public void doSomeThing() {
                behaviors.forEach(b->{
                    if(b instanceof LivingBehavior) {
                        LivingBehavior li = (LivingBehavior)b;
                        // 处理生活行为
                    } else if(b instanceof WorkingBehavior) {
                        WorkingBehavior wb = (WorkingBehavior) b;
                        // 处理工作行为
                    }
                });
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
    • 客户端调用

      public static void main(String[] args) {
              //  张三--全职妈妈 (只有生活行为)
              LivingBehavior zslivingBehavior = new LivingBehaviorImpl();
              BehaviorComposer zsBehaviorComposer = new IBehaviorComposerImpl();
              zsBehaviorComposer.add(zslivingBehavior);
      
              // 李四--职场妈妈 (生活、工作行为都有)
              LivingBehavior lsLivingBehavior = new LivingBehaviorImpl();
              WorkingBehavior lsWorkingBehavior = new WorkingBehaviorImpl();
      
              BehaviorComposer lsBehaviorComposer = new IBehaviorComposerImpl();
              lsBehaviorComposer.add(lsLivingBehavior);
              lsBehaviorComposer.add(lsWorkingBehavior);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      示例中体现了单一职责的拆分与组合使用,仅参考。

    9. 小结

    不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等。我们在考虑应用某一个设计原则是否合理的时候,也可以以此作为最终的考量标准。

    参考:

    设计模式之基-六大设计原则

    单一职责原则

    《设计模式之美》

    《重学 Java 设计模式》

  • 相关阅读:
    golang数据结构与算法——递归、迷宫回溯和二叉树的遍历
    操作系统 —— 信号
    设计一个实用好看的餐边柜或者酒柜需要知道这5点。福州中宅装饰,福州装修
    免费的 ChatGPT 网站(六个)
    MySQL进阶02_索引概述_结构_分类_语法
    01- ROS初识
    大厂前端面试考什么?
    如何利用人工智能减少塑料垃圾?
    SpringBoot开发网页即时聊天室/IM通信/WebSocket/即时通讯
    当别人的话很刺耳时怎么办?
  • 原文地址:https://blog.csdn.net/jun5753/article/details/126861167