設(shè)計(jì)模式六大原則(一)----單一職責(zé)原則

設(shè)計(jì)模式六大原則之【單一職則原則】

一库物、什么是單一職責(zé)原則

首先, 我們來看單一職責(zé)的定義.

單一職責(zé)原則,全稱Single Responsibility Principle, 簡稱SRP.
A class should have only one reason to change
類發(fā)生更改的原因應(yīng)該只有一個(gè)

就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因颊亮。應(yīng)該只有一個(gè)職責(zé)晌梨。如果一個(gè)類有一個(gè)以上的職責(zé)豆茫,這些職責(zé)就耦合在了一起伸蚯。一個(gè)職責(zé)的變化可能會(huì)削弱或者抑制這個(gè)類完成其他職責(zé)的能力撬码。這會(huì)導(dǎo)致脆弱的設(shè)計(jì)。當(dāng)一個(gè)職責(zé)發(fā)生變化時(shí)掰吕,可能會(huì)影響其它的職責(zé)果覆。另外,多個(gè)職責(zé)耦合在一起殖熟,會(huì)影響復(fù)用性局待。想要避免這種現(xiàn)象的發(fā)生,就要盡可能的遵守單一職責(zé)原則吗讶。

單一職責(zé)原則的核心就是解耦和增強(qiáng)內(nèi)聚性燎猛。

二、為什么要遵守單一職責(zé)原則照皆?

通常 , 我們做事情都要知道為什么要這么做, 才回去做. 做的也有底氣, 那么為什么我們要使用單一職責(zé)原則呢?

1重绷、提高類的可維護(hù)性和可讀寫性
一個(gè)類的職責(zé)少了,復(fù)雜度降低了膜毁,代碼就少了昭卓,可讀性也就好了,可維護(hù)性自然就高了瘟滨。

2候醒、提高系統(tǒng)的可維護(hù)性
系統(tǒng)是由類組成的,每個(gè)類的可維護(hù)性高杂瘸,相對(duì)來講整個(gè)系統(tǒng)的可維護(hù)性就高倒淫。當(dāng)然,前提是系統(tǒng)的架構(gòu)沒有問題败玉。

3敌土、降低變更的風(fēng)險(xiǎn)
一個(gè)類的職責(zé)越多,變更的可能性就越大运翼,變更帶來的風(fēng)險(xiǎn)也就越大

如果在一個(gè)類中可能會(huì)有多個(gè)發(fā)生變化的東西返干,這樣的設(shè)計(jì)會(huì)帶來風(fēng)險(xiǎn), 我們盡量保證只有一個(gè)可以變化,其他變化的就放在其他類中血淌,這樣的好處就是 ** 提高內(nèi)聚矩欠,降低耦合 **。

三. 單一職責(zé)原則應(yīng)用的范圍

單一職責(zé)原則適用的范圍有接口悠夯、方法癌淮、類。按大家的說法疗疟,接口和方法必須保證單一職責(zé)该默,類就不必保證,只要符合業(yè)務(wù)就行策彤。

3.1 【方法層面】單一職責(zé)原則的應(yīng)用

現(xiàn)在有一個(gè)場(chǎng)景, 需要修改用戶的用戶名和密碼. 就針對(duì)這個(gè)功能我們可以有多種實(shí)現(xiàn).
第一種:

/**
 * 操作的類型
 */
public enum OperateEnum {
    UPDATE_USERNAME,
    UPDATE_PASSWORD;
}

public interface UserOperate {
    void updateUserInfo(OperateEnum type, UserInfo userInfo);
}

public class UserOperateImpl implements UserOperate{
    @Override
    public void updateUserInfo(OperateEnum type, UserInfo userInfo) {
        if (type == OperateEnum.UPDATE_PASSWORD) {
            // 修改密碼
        } else if(type == OperateEnum.UPDATE_USERNAME) {
            // 修改用戶名
        }
    }
}

第二種方法:

public interface UserOperate {
    void updateUserName(UserInfo userInfo);

    void updateUserPassword(UserInfo userInfo);
}

public class UserOperateImpl implements UserOperate {
    @Override
    public void updateUserName(UserInfo userInfo) {
        // 修改用戶名邏輯
    }

    @Override
    public void updateUserPassword(UserInfo userInfo) {
        // 修改密碼邏輯
    }
}

來看看這兩種實(shí)現(xiàn)的區(qū)別:
第一種實(shí)現(xiàn)是根據(jù)操作類型進(jìn)行區(qū)分, 不同類型執(zhí)行不同的邏輯. 把修改用戶名和修改密碼這兩件事耦合在一起了. 如果客戶端在操作的時(shí)候傳錯(cuò)了類型, 那么就會(huì)發(fā)生錯(cuò)誤.
第二種實(shí)現(xiàn)是我們推薦的實(shí)現(xiàn)方式. 修改用戶名和修改密碼邏輯分開. 各自執(zhí)行各自的職責(zé), 互不干擾. 功能清晰明了.

由此可見, 第二種設(shè)計(jì)是符合單一職責(zé)原則的. 這是在方法層面實(shí)現(xiàn)單一職責(zé)原則.

3.2 【接口層面】單一職責(zé)原則的應(yīng)用

我們假設(shè)一個(gè)場(chǎng)景, 大家一起做家務(wù), 張三掃地, 李四買菜. 李四買完菜回來還得做飯. 這個(gè)邏輯怎么實(shí)現(xiàn)呢?

方式一

/**
 * 做家務(wù)
 */
public interface HouseWork {
    // 掃地
    void sweepFloor();

    // 購物
    void shopping();
}

public class Zhangsan implements HouseWork{
    @Override
    public void sweepFloor() {
        // 掃地
    }

    @Override
    public void shopping() {

    }
}

public class Lisi implements HouseWork{
    @Override
    public void sweepFloor() {

    }

    @Override
    public void shopping() {
        // 購物
    }
}

首先定義了一個(gè)做家務(wù)的接口, 定義兩個(gè)方法掃地和買菜. 張三掃地, 就實(shí)現(xiàn)掃地接口. 李四買菜, 就實(shí)現(xiàn)買菜接口. 然后李四買完菜回來還要做飯, 于是就要在接口類中增加一個(gè)方法cooking. 張三和李四都重寫這個(gè)方法, 但只有李四有具體實(shí)現(xiàn).

這樣設(shè)計(jì)本身就是不合理的.
首先: 張三只掃地, 但是他需要重寫買菜方法, 李四不需要掃地, 但是李四也要重寫掃地方法.
第二: 這也不符合開閉原則. 增加一種類型做飯, 要修改3個(gè)類. 這樣當(dāng)邏輯很復(fù)雜的時(shí)候, 很容易引起意外錯(cuò)誤.

上面這種設(shè)計(jì)不符合單一職責(zé)原則, 修改一個(gè)地方, 影響了其他不需要修改的地方.

方法二

/**
 * 做家務(wù)
 */
public interface Hoursework {
}

public interface Shopping extends Hoursework{
    // 購物
    void shopping();
}

public interface SweepFloor extends Hoursework{
    // 掃地
    void sweepFlooring();
}

public class Zhangsan implements SweepFloor{

    @Override
    public void sweepFlooring() {
        // 張三掃地
    }
}

public class Lisi implements Shopping{
    @Override
    public void shopping() {
        // 李四購物
    }
}

上面做家務(wù)不是定義成一個(gè)接口, 而是將掃地和做家務(wù)分開了. 張三掃地, 那么張三就實(shí)現(xiàn)掃地的接口. 李四購物, 李四就實(shí)現(xiàn)購物的接口. 后面李四要增加一個(gè)功能做飯. 那么就新增一個(gè)做飯接口, 這次只需要李四實(shí)現(xiàn)做飯接口就可以了.

public interface Cooking extends Hoursework{ 
    void cooking();
}

public class Lisi implements Shopping, Cooking{
    @Override
    public void shopping() {
        // 李四購物
    }

    @Override
    public void cooking() {
        // 李四做飯
    }
}

如上, 我們看到張三沒有實(shí)現(xiàn)多余的接口, 李四也沒有. 而且當(dāng)新增功能的時(shí)候, 只影響了李四, 并沒有影響張三.
這就是符合單一職責(zé)原則. 一個(gè)類只做一件事. 并且他的修改不會(huì)帶來其他的變化.

3.3 【類層面】單一職責(zé)原則的應(yīng)用

從類的層面來講, 沒有辦法完全按照單一職責(zé)原來來拆分. 換種說法, 類的職責(zé)可大可小, 不想接口那樣可以很明確的按照單一職責(zé)原則拆分. 只要符合邏輯有道理即可.

比如, 我們?cè)诰W(wǎng)站首頁可以注冊(cè), 登錄, 微信登錄.注冊(cè)登錄等操作. 我們通常的做法是:

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) {
        // 用戶注冊(cè)
    }

    @Override
    public void logout(UserInfo userInfo) {
        // 用戶登出
    }
}

那如果按照單一職責(zé)原則拆分, 也可以拆分為下面的形式


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() {

    }
}

像上面這樣寫可不可以呢? 其實(shí)也可以, 就是類很多. 如果登錄栓袖、注冊(cè)匣摘、注銷操作代碼很多, 那么可以這么寫.

四、如何遵守單一職責(zé)原則

4.1 合理的職責(zé)分解

相同的職責(zé)放到一起裹刮,不同的職責(zé)分解到不同的接口和實(shí)現(xiàn)中去音榜,這個(gè)是最容易也是最難運(yùn)用的原則,關(guān)鍵還是要從業(yè)務(wù)出發(fā)捧弃,從需求出發(fā)赠叼,識(shí)別出同一種類型的職責(zé)。

例子:人的行為分析违霞,包括了生活和工作等行為的分析嘴办,生活行為包括吃、跑买鸽、睡等行為涧郊,工作行為包括上下班,開會(huì)等行為眼五,如下圖所示:
<image src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7b2e0fea47ec4264aeb760aeb8aef76d~tplv-k3u1fbpfcp-zoom-1.image" width="500px">

人類的行為分成了兩個(gè)接口:生活行為接口妆艘、工作行為接口,以及兩個(gè)實(shí)現(xiàn)類看幼。如果都用一個(gè)實(shí)現(xiàn)類來承擔(dān)這兩個(gè)接口的職責(zé)批旺,就會(huì)導(dǎo)致代碼臃腫,不易維護(hù)诵姜,如果以后再加上其他行為汽煮,例如學(xué)習(xí)行為接口,將會(huì)產(chǎn)生變更風(fēng)險(xiǎn)(這里還用到了組合模式)棚唆。

4.2 來看看簡單的代碼實(shí)現(xiàn)

第一步: 定義一個(gè)行為接口


/**
 * 人的行為
 * 人的行為包括兩種: 生活行為, 工作行為
 */
public interface IBehavior {
    
}

這里面定義了一個(gè)空的接口, 行為接口. 具體這個(gè)行為接口下面有哪些接口呢?有生活和工作兩方面的行為.

第二步: 定義生活和工作接口, 并且他們都是行為接口的子類

生活行為接口:

public interface LivingBehavior extends IBehavior{
    /** 吃飯 */
    void eat();

    /** 跑步 */
    void running();

    /** 睡覺 */
    void sleeping();
}

工作行為接口:

public interface WorkingBehavior extends IBehavior{

    /** 上班 */
    void goToWork();

    /** 下班 */
    void goOffWork();

    /** 開會(huì) */
    void meeting();
}

第三步: 定義工作行為接口和生活行為接口的實(shí)現(xiàn)類

生活行為接口實(shí)現(xiàn)類:

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("睡覺");
    }
}

工作行為接口實(shí)現(xiàn)類:

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("開會(huì)");
    }
}

第四步: 行為組合調(diào)用.

行為接口定義好了. 接下來會(huì)定義一個(gè)行為集合. 不同的用戶擁有的行為是不一樣 , 有的用戶只用生活行為, 有的用戶既有生活行為又有工作行為.ewgni

我們并不知道具體用戶到底會(huì)有哪些行為, 所以,通常使用一個(gè)集合來接收用戶的行為. 用戶有哪些行為, 就往里面添加哪些行為.

1. 行為組合接口BehaviorComposer

public interface BehaviorComposer {
    void add(IBehavior behavior);
}

2. 行為組合接口實(shí)現(xiàn)類IBehaviorComposerImpl

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;
                // 處理工作行為
            }

        });
    }
}

第五步: 客戶端調(diào)用

用戶在調(diào)用的時(shí)候, 根據(jù)實(shí)際情況調(diào)用就可以了, 比如下面的代碼: 張三是全職媽媽, 只有生活行為, 李四是職場(chǎng)媽媽, 既有生活行為又有工作行為.

public static void main(String[] args) {
        //  張三--全職媽媽
        LivingBehavior zslivingBehavior = new LivingBehaviorImpl();
        BehaviorComposer zsBehaviorComposer = new IBehaviorComposerImpl();
        zsBehaviorComposer.add(zslivingBehavior);

        // 李四--職場(chǎng)媽媽
        LivingBehavior lsLivingBehavior = new LivingBehaviorImpl();
        WorkingBehavior lsWorkingBehavior = new WorkingBehaviorImpl();

        BehaviorComposer lsBehaviorComposer = new IBehaviorComposerImpl();
        lsBehaviorComposer.add(lsLivingBehavior);
        lsBehaviorComposer.add(lsWorkingBehavior);
    }

可以看出單一職責(zé)的好處.

五逗物、單一職責(zé)原則的優(yōu)缺點(diǎn)

  • 類的復(fù)雜性降低: 一個(gè)類實(shí)現(xiàn)什么職責(zé)都有清晰明確的定義了, 復(fù)雜性自然就降低了

  • 可讀性提高: 復(fù)雜性降低了,可讀性自然就提高了

  • 可維護(hù)性提高: 可讀性提高了瑟俭,代碼就更容易維護(hù)了

  • 變更引起的風(fēng)險(xiǎn)降低: 變更是必不可少的,如果接口的單一職責(zé)做得好契邀,一個(gè)接口修改只對(duì)相應(yīng)的實(shí)現(xiàn)類有影響摆寄,對(duì)其他的接口和類無影響,這對(duì)系統(tǒng)的擴(kuò)展性坯门、維護(hù)性都有非常大的幫助

四微饥、最佳實(shí)踐
https://baijiahao.baidu.com/s?id=1646244620500787383&wfr=spider&for=pc

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市古戴,隨后出現(xiàn)的幾起案子欠橘,更是在濱河造成了極大的恐慌,老刑警劉巖现恼,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肃续,死亡現(xiàn)場(chǎng)離奇詭異黍檩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)始锚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門刽酱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瞧捌,你說我怎么就攤上這事棵里。” “怎么了姐呐?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵殿怜,是天一觀的道長。 經(jīng)常有香客問我曙砂,道長头谜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任麦轰,我火速辦了婚禮乔夯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘款侵。我一直安慰自己末荐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布新锈。 她就那樣靜靜地躺著甲脏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妹笆。 梳的紋絲不亂的頭發(fā)上块请,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音拳缠,去河邊找鬼墩新。 笑死,一個(gè)胖子當(dāng)著我的面吹牛窟坐,可吹牛的內(nèi)容都是我干的海渊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哲鸳,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼臣疑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起徙菠,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤讯沈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后婿奔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缺狠,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡问慎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了儒老。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝴乔。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驮樊,靈堂內(nèi)的尸體忽然破棺而出薇正,到底是詐尸還是另有隱情,我是刑警寧澤囚衔,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布挖腰,位于F島的核電站,受9級(jí)特大地震影響练湿,放射性物質(zhì)發(fā)生泄漏猴仑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一肥哎、第九天 我趴在偏房一處隱蔽的房頂上張望辽俗。 院中可真熱鬧,春花似錦篡诽、人聲如沸崖飘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朱浴。三九已至,卻和暖如春达椰,著一層夾襖步出監(jiān)牢的瞬間翰蠢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工啰劲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梁沧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓蝇裤,卻偏偏與公主長得像趁尼,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猖辫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容