1. 程式人生 > >設計原則之單一職責原則(SRP)

設計原則之單一職責原則(SRP)

簡介

單一職責原則是最重要的設計原則,也是最抽象的設計原則。小到函式,大到平臺的設計,都可以使用單一職責原則來指導。也正因為它的抽象性,沒有一個統一的規則,不同的人即使是設計同一個功能,所劃分的函式、類也都是不相同的。

定義

單一職責原則,英文名稱 Single Responsibility Principle,意為每一個模組、類、函式應當只具備一個職責,也即只有一個功能。按照馬丁大叔的說法:“一個類的改變只有一個理由”。

這個原則只給了我們一個方向,就跟“聽過很多道理依然過不好這一生”中的道理一樣,為什麼依然過不好?因為道理僅僅是一個道理而不具備可操作性,沒有辦法按照步驟一二三來得到想要的結果。

單一不需要解釋,關鍵是職責,一個函式、介面、類、模組要幹多少活才算是職責單一?多大的粒度是合適的呢?

按照我現階段的知識水平,單一職責原則背後隱去的關鍵概念是抽象,函式、介面、類需要符合自己所在的抽象層次,在其自身所在的層面上內聚成領域,這就是自己的職責。

實踐

需求:做一個登入功能,要求有過濾黑名單,登入成功後傳送簡訊、郵件等功能。

注:僅示意

public class LoginManager {
    public String login(String userId, String password) {
        List<String> blacklist = blacklistService.findByUserId(userId);
        if(CollectionUtils.isNotEmpty) {
            return "user blocked";
        }
        
        User user = userService.findByUserId(userId);
        if (user == null) {
            return "user not exists";
        }
        
        String passwordMd5 = Md5Utils.md5(password);
        if (!passwordMd5.equals(user.getPassword()) {
            return "user login failed";
        }
        
        String uuid = UUIDUtils.getUUID();
        cacheService.set(uuid, userId);
        setCookie("sessionId", uuid);
        
        // mail related logic
        String mailContent = user.getUserName + "! Welcome back. From mail."
        mailService.send(user.getMail(), mailContent);
        
        // msg related logic
        String smsContent = user.getUserName + "! Welcome back. From sms."
        smsService.send(user.getPhone(), smsContent);
        
        return "success";
    }
}

函式

這個功能從函式名來看,並沒有違反單一職責的原則,登入就是需要做這麼多的事。但是從編碼實現來說,已經違反了SRP。登入包含的職責有過濾、校驗,但是過濾、校驗的具體細節並不在登入函式的職責範圍內,據此重構登入函式

public class LoginManager {
    public String login(String userId, String password) {
        Pair<Boolean, String> check = loginCheck(userId, password);
        if (!check.left()){
            return check.right();
        }
        
        saveUserSesssion(userId);
        afterLogined(userId);
        
        return "success";
    }
    
    private Pair<Boolean, String> loginCheck(String userId, String password) {
        
        Pair<Boolean. String> beforeCheck = loginBeforeCheck(userId);
        if(!before.left()){
            return beforeCheck;
        }
        
        return userCheck(userId, password);
    }
    
    private Pair<Boolean, String> loginBeforeCheck(String userId){
        List<String> blacklist = blacklistService.findByUserId(userId);
        if(CollectionUtils.isNotEmpty) {
            return Pair.of(false, "user blocked");
        }
        
        return Pair.of(true, "");
    }
    
    
    private Pair<Boolean, String> userCheck(String userId, String password){
        User user = userService.findByUserId(userId);
        if (user == null) {
            return Pair.of(false, "user not exists");
        }
        
        String passwordMd5 = Md5Utils.md5(password);
        if (!passwordMd5.equals(user.getPassword()) {
            return Pair.of(false, "user login failed");
        }
        
        return Pair.of(true, "");
    }
    
    private void saveUserSesssion(String userId){
        String uuid = UUIDUtils.getUUID();
        cacheService.set(uuid, userId);
        setCookie("sessionId", uuid);
    }
    
    private void afterLogined(User user) {
        User user = userService.findByUserId(userId);
        sendMail(user);
        sendSms(user);
    }
    
    private void sendMail(User user) {
        // mail related logic
        String mailContent = user.getUserName + "! Welcome back. From mail."
        mailService.send(user.getMail(), mailContent);
    }
    
    private void sendSms(User user) {
        // msg related logic
        String smsContent = user.getUserName + "! Welcome back. From sms."
        smsService.send(user.getPhone(), smsContent);
    }
}

重構完成後,如果需要增加過濾條件,則只需要修改loginBeforeCheck 函式,如果需要增加登入後功能,則只需要修改 afterLogined 函式,每個函式都只有一個修改的理由,也即符合 SRP 原則。

類與介面

當我們將功能從函式的粒度重構之後,每個函式只負責了自己的部分,已經符合了 SRP 原則,但是從類的角度來看,登入類承擔了太多的功能。增加校驗規則需要修改登入類,增加登入後的功能也需要修改登入類,因此類也需要按照 SRP 的原則來進行重構。

在思考函式重構的過程中,我們已經對如何劃分類有了思考。校驗可以抽出來,登入後發簡訊、郵件也可以抽出來,這樣登入類就符合了自己的名稱:僅關心登入的細節。

public interface LoginCheckService {
    public Pair<Boolean, String> check(String userId, String password);
}

public interface LoginListener{
    public void afterLogin(LoginEvent event);
}

光有這兩個類可能是不夠的,我們還需要定義一個登入事件LoginEvent, 事件註冊中心 Registry, 事件分發Dispatcher, LoginCheckService 是有先後順序的要求的,可以實現一個 Order 介面,也可以拆成兩個介面,同一個介面的實現沒有順序要求。這完全取決於我們系統功能的規模,和我們對職責的認識。

模組

雖然登入功能一般不會做成模組,但我們可以站在模組的角度來思考。模組是大家共用的依賴,對於可擴充套件性、可維護性要求會比一個功能要求更高。在 類和介面 小節的描述中,事件、註冊中心等在功能層面上可能不是必須的,在模組層面上,這些是必須的。沒有事件,使用方就不知道如何響應;沒有註冊中心,使用方就不知道如何定製化;沒有事件分發,模組就無法將事件通知到使用方。

缺點

SRP 可以很好的將我們的功能、應用解耦,但是應該看到 SRP 存在的缺點,才可以更好的權衡自己的設計。

  • 不明確。職責的含義沒有明確界定,如何界定是門藝術。
  • 無評判標準。界定出來的職責是好是壞?沒有標準,只有經驗。
  • 易濫用。職責劃分到最後可能就是一個介面一個方法,看似符合 SRP,實則是 SRP 的濫用。
  • 函式、介面、類爆炸。
  • 知識比較支離。資訊分佈在各個類中,不如放在一起集中。

後記

要做一個符合SRP 原則的設計是很困難的,需要我們在實踐中總結經驗。對一個領域有了充分的瞭解,我們才能更加遊刃有餘的應用SRP 原則。同時不要濫用 SRP原則,程式設計是門藝術,設計更是一門藝術。

個人

個人公眾號

相關推薦

設計原則單一職責原則(SRP)

簡介 單一職責原則是最重要的設計原則,也是最抽象的設計原則。小到函式,大到平臺的設計,都可以使用單一職責原則來指導。也正因為它的抽

學習設計模式 - 六大基本原則單一職責原則

enc more ref 組合 代碼 aso HERE ali 不可   設計模式總共有六大基本原則,統稱為SOLID (穩定)原則,分別是S-單一職責原則(Single Responsibility Principle), O-開閉原則(Open closed Pri

面向物件的五大設計原則單一職責原則

我們都知道,面向物件是一種高度抽象的思維,我們在面向物件設計中,類是最基本的單位,我們的各種設計都是圍繞著類來進行的,可以這麼說,類與類之間的關係,構成了設計模式的大部分內容,我麼可能認為,類是屬性+函式構成的,事實上在底層儲存上確實也是這麼來搞的,但是這些僅僅只是確定一個獨立的類,而類與類之間

嘻哈說:設計模式單一職責原則

1、定義 首先呢,我們來看一下單一職責原則的定義。 就一個類而言,應該只有一個引起它變化的原因 這個說法不是很好懂,有一些抽象,不過呢,我們依舊可以嘗試著理解一下。 就一個類而言,只有一個引起它變化的原因,也就是說,除此之外,不能有其它引起變化的原因。 這樣就

大話設計模式單一職責原則

引用的3篇部落格,都很詳細的例子,這篇部落格是通過對參考內容的總結,便於自己理解,例子可以看參考的3篇部落格,這裡不寫 什麼是單一職責原則 單一職責原則:就一個類而言,應該僅有一個引起它變化的原因  簡單記憶:術業有專攻,我是專門搬磚的!!           

6大設計原則單一職責原則

方法 接口設計 sta 其他 一個 src 沒有 不同的 可維護性 單一職責原則 如果有一個用戶管理類,類圖如下 我想,任誰也能看的出這個接口設計的有問題,用戶的屬性和用戶的行為沒有分開,應該把用戶的信息抽取成一個業務對象,把用戶的行為抽取成一個業務對象,按照這個思

Android 面向物件六大設計原則單一職責原則

1.單一職責原則簡介單一職責原則(SRP:Single responsibility principle)又稱單一功能原則,面向物件六個基本原則(SOLID)之一。它規定一個類應該只有一個發生變化的原因

深入淺出系列第一篇(設計模式單一職責原則)—— 從純小白到Java開發的坎坷經歷

各位看官大大們,晚上好。好久不見,我想死你們了...     先說說寫這個系列文章的背景: 工作了這麼久了,每天都忙著寫業務,好久沒有好好靜下心來好好總結總結了。正好這段時間公司組織設計模式的分享分,所以我才有機會在這裡和大家嘮嘮嗑。 也許因為自己是小白自學的吧,所以磕磕絆絆走了好多彎路。

《大話設計模式》——單一職責原則

有一個 導致 完成 如果能 原因 如果 分離 破壞 一個 單一職責原則(SRP):就一個類而言,應該僅有一個能引起它變化的原因。 如果一個類承擔的職責過多,就等於把這些職責耦合在一起,一個職責的變化可能會削弱或抑制這個類完成其他職責的能力。這種耦合會導致脆弱的設計,當變化發

七大設計原則之一單一職責原則

  單一職責:一個類應該有且只有一個變化的原因。通俗的說,即一個類只負責一項職責。   單一職責原則在實際使用中即容易也非常難。我們通常賦予一個類過多相關功能,使這個類非常累。職責過多也引起很多問題。過多的職責,使類本身混亂。 參考:1.https://www.c

設計模式:單一職責原則、開放-封閉原則以及依賴倒置原則

在設計程式碼中,我們有許多可以依照的設計模式,讓我把整個專案的邏輯結構變得清晰易於維護。當然,在設計模式中我們不只有各種模式,還有許多設計的原則,雖然他們不是程式碼架構的模板,但是這些原則卻時刻提醒我們提高程式碼質量和防止未來麻煩。這次我就將單一職責原則、開放-封閉原則以及依賴倒轉原則進行解釋。

設計模式原則1----單一職責原則

個人部落格:開啟連結 1、官方定義 單一職責原則,英文縮寫SRP,全稱Single Responsibility Principle。 原始定義:There should never be more than one reason for a clas

設計模式的七大原則(1) --單一職責原則

前言 最近工作中備受打擊,之前設計的很多程式都被老大否決,需要重構,讓我好好看看設計模式。之前對這一塊內容的確不怎麼重視,感覺枯燥無聊又派不上用場。後來沉下心來研究了一番... 我靠,原來如此,之前寫程式碼的時候怎麼這麼傻逼,很多問題其實在一開始設計的時候就能避免。之前寫的都是些什麼鬼。 我們踩過的坑,歷代前

面向對象五大原則_1.單一職責原則&amp;2.裏氏替換原則

解決 一次 cti prot 輸入 名稱 enter wid col 單一職責原則:Single Responsibility Principle (SRP) 一個類。僅僅有一個引起它變化的原因。應該僅僅有一個職責。每個職責都是變化的一個軸線。假設一個類有一個以

設計模式單一職責

想要精通設計模式,必須要先搞清楚設計模式的六大原則。 在開始設計模式之前,先來談談設計模式的六大設計原則,第一個便是單一職責原則(Single Responsibility Principle)了。 單一職責原則定義 There should never

C#軟體設計——小話設計模式原則單一職責原則SRP

前言:上篇C#軟體設計——小話設計模式原則之:依賴倒置原則DIP簡單介紹了下依賴倒置的由來以及使用,中間插了兩篇WebApi的文章,這篇還是迴歸正題,繼續來寫寫設計模式另一個重要的原則:單一職責原則。 軟體設計原則系列文章索引 一、原理介紹 1、官方定義 單一職責原則,英文縮寫SRP,全稱Sing

小話設計模式原則單一職責原則SRP(C#篇)

正文 前言:上篇C#軟體設計——小話設計模式原則之:依賴倒置原則DIP簡單介紹了下依賴倒置的由來以及使用,中間插了兩篇WebApi的文章,這篇還是迴歸正題,繼續來寫寫設計模式另一個重要的原則:單一職責原則。 軟體設計原則系列文章索引 回到頂部 一、原理介紹 回到頂部

設計模式學習筆記(二) 設計基本原則單一職責原則

code 分享 開發者 實際應用 需要 ret ext file類 tor 單一職責原則(SRP: Single Responsibility Principle) 名詞解釋: 1) 職責:是指類變化的原因。 2) 職責擴散:就是因為某種原因,職責P被分化為粒度更細的職責P

面向對象設計原則一:單一職責原則SRP

能夠 實現 update 之間 關註 linq 好處 相互 並且 單一職責原則(SRP) 定義:系統中的每一個類都應該只有一個職責。 好處:高內聚、低耦合。 解釋說明: 單一職責也就是說我們應該讓一個類或一個對象只做一件事情,每個類所要關註的就是自己要完成的

西遊記設計模式原則——單一職責原則

void 可能 equals main person 方法 隱患 客戶端代碼 p s 單一職責原則 ——專心致誌只做一件事 1 package danyizhize; 2 3 class SunWuKong { 4 public void XiangM