單一職責原則是最重要的設計原則,也是最抽象的設計原則。小到函數,大到平臺的設計,均可以使用單一職責原則來指導。也正由於它的抽象性,沒有一個統一的規則,不一樣的人即便是設計同一個功能,所劃分的函數、類也都是不相同的。java
單一職責原則,英文名稱 Single Responsibility Principle
,意爲每個模塊、類、函數應當只具有一個職責,也即只有一個功能。按照馬丁大叔的說法:「一個類的改變只有一個理由」。編程
這個原則只給了咱們一個方向,就跟「聽過不少道理依然過很差這一輩子」中的道理同樣,爲何依然過很差?由於道理僅僅是一個道理而不具有可操做性,沒有辦法按照步驟一二三來獲得想要的結果。session
單一不須要解釋,關鍵是職責,一個函數、接口、類、模塊要幹多少活纔算是職責單一?多大的粒度是合適的呢?函數
按照我現階段的知識水平,單一職責原則背後隱去的關鍵概念是抽象,函數、接口、類須要符合本身所在的抽象層次,在其自身所在的層面上內聚成領域,這就是本身的職責。ui
需求:作一個登陸功能,要求有過濾黑名單,登陸成功後發送短信、郵件等功能。編碼
注:僅示意spa
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。登陸包含的職責有過濾、校驗,可是過濾、校驗的具體細節並不在登陸函數的職責範圍內,據此重構登陸函數.net
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 的原則來進行重構。code
在思考函數重構的過程當中,咱們已經對如何劃分類有了思考。校驗能夠抽出來,登陸後發短信、郵件也能夠抽出來,這樣登陸類就符合了本身的名稱:僅關心登陸的細節。
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原則,編程是門藝術,設計更是一門藝術。
我的公衆號
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1nov2ydr8zly