美文网首页
单一职责

单一职责

作者: 凯玲之恋 | 来源:发表于2020-04-11 00:13 被阅读0次

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

    单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。

    这个原则的英文描述是这样的:A class or module should have a single reponsibility。

    一个类或者模块只负责完成一个职责(或者功能)

    注意,这个原则描述的对象包含两个,一个是类(class),一个是模块(module)。

    • 一种理解是:把模块看作比类更加抽象的概念,类也可以看作模块。
    • 另一种理解是:把模块看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块。

    不要设计大而全的类,要设计粒度小、功能单一的类。

    一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。
    (其实在这里,private的就不算了)

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

    我觉得一个是粒度划分,一个是该模块功能所承担功能划分。

    这个例子比较极端,一眼就能看出订单和用户毫不相干。

    UserInfo 类的设计是否满足单一职责原则呢?

    
    public class UserInfo {
      private long userId;
      private String username;
      private String email;
      private String telephone;
      private long createTime;
      private long lastLoginTime;
      private String avatarUrl;
      private String provinceOfAddress; // 省
      private String cityOfAddress; // 市
      private String regionOfAddress; // 区 
      private String detailedAddress; // 详细地址
      // ...省略其他属性和方法...
    }
    

    一种观点是,UserInfo 类包含的都是跟用户相关的信息,所有的属性和方法都隶属于用户这样一个业务模型,满足单一职责原则;

    另一种观点是,地址信息在 UserInfo 类中,所占的比重比较高,可以继续拆分成独立的 UserAddress 类,UserInfo 只保留除 Address 之外的其他信息,拆分之后的两个类的职责更加单一。

    要从中做出选择,我们不能脱离具体的应用场景
    如果在这个社交产品中,用户的地址信息跟其他信息一样,只是单纯地用来展示,那 UserInfo 现在的设计就是合理的。

    如果这个社交产品发展得比较好,之后又在产品中添加了电商的模块,用户的地址信息还会用在电商物流中,那我们最好将地址信息从 UserInfo 中拆分出来,独立成用户物流信息(或者叫地址信息、收货信息等)。

    公司希望支持统一账号系统,也就是用户一个账号可以在公司内部的所有产品中登录。这个时候,我们就需要继续对 UserInfo 进行拆分,将跟身份认证相关的信息(比如,email、telephone 等)抽取成独立的类。

    可以总结出,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的

    在某种应用场景或者当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或着在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。

    • 除此之外,从不同的业务层面去看待同一个类的设计,对类是否职责单一,也会有不同的认识

    评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准,可以说,这是件非常主观、仁者见仁智者见智的事情。

    我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

    • 几条判断原则,比起很主观地去思考类是否职责单一
    • 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分;
    • 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分;
    • 私有方法过多,我们就要考虑能否将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性;
    • 比较难给类起一个合适名字,很难用一个业务名词概括,或者只能用一些笼统的 Manager、Context 之类的词语来命名,这就说明类的职责定义得可能不够清晰;
    • 类中大量的方法都是集中操作类中的某几个属性,比如,在 UserInfo 例子中,如果一半的方法都是在操作 address 信息,那就可以考虑将这几个属性和对应的方法拆分出来。

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

    为了满足单一职责原则,是不是把类拆得越细就越好呢?答案是否定的。

    Serialization 类实现了一个简单协议的序列化和反序列功能,具体代码如下:

    
    /**
     * Protocol format: identifier-string;{gson string}
     * For example: UEUEUE;{"a":"A","b":"B"}
     */
    public class Serialization {
      private static final String IDENTIFIER_STRING = "UEUEUE;";
      private Gson gson;
      
      public Serialization() {
        this.gson = new Gson();
      }
      
      public String serialize(Map<String, String> object) {
        StringBuilder textBuilder = new StringBuilder();
        textBuilder.append(IDENTIFIER_STRING);
        textBuilder.append(gson.toJson(object));
        return textBuilder.toString();
      }
      
      public Map<String, String> deserialize(String text) {
        if (!text.startsWith(IDENTIFIER_STRING)) {
            return Collections.emptyMap();
        }
        String gsonStr = text.substring(IDENTIFIER_STRING.length());
        return gson.fromJson(gsonStr, Map.class);
      }
    }
    

    如果我们想让类的职责更加单一,我们对 Serialization 类进一步拆分,拆分成一个只负责序列化工作的 Serializer 类和另一个只负责反序列化工作的 Deserializer 类。拆分后的具体代码如下所示:

    
    public class Serializer {
      private static final String IDENTIFIER_STRING = "UEUEUE;";
      private Gson gson;
      
      public Serializer() {
        this.gson = new Gson();
      }
      
      public String serialize(Map<String, String> object) {
        StringBuilder textBuilder = new StringBuilder();
        textBuilder.append(IDENTIFIER_STRING);
        textBuilder.append(gson.toJson(object));
        return textBuilder.toString();
      }
    }
    
    public class Deserializer {
      private static final String IDENTIFIER_STRING = "UEUEUE;";
      private Gson gson;
      
      public Deserializer() {
        this.gson = new Gson();
      }
      
      public Map<String, String> deserialize(String text) {
        if (!text.startsWith(IDENTIFIER_STRING)) {
            return Collections.emptyMap();
        }
        String gsonStr = text.substring(IDENTIFIER_STRING.length());
        return gson.fromJson(gsonStr, Map.class);
      }
    }
    

    虽然经过拆分之后,Serializer 类和 Deserializer 类的职责更加单一了,但也随之带来了新的问题。如果我们修改了协议的格式,数据标识从“UEUEUE”改为“DFDFDF”,或者序列化方式从 JSON 改为了 XML,那 Serializer 类和 Deserializer 类都需要做相应的修改,代码的内聚性显然没有原来 Serialization 高了。

    4 考量标准

    不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等

    在考虑应用某一个设计原则是否合理的时候,也可以以此作为最终的考量标准。

    参考

    15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?

    相关文章

      网友评论

          本文标题:单一职责

          本文链接:https://www.haomeiwen.com/subject/poajmhtx.html