自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:我不想聽(tīng)單例、工廠了,跟我說(shuō)說(shuō)裝飾器模式吧!

開(kāi)發(fā) 前端
如果數(shù)學(xué)和繪畫(huà)課程又新增了需求,需要額外展示對(duì)應(yīng)的輔修資料,但英語(yǔ)課程則不需要展示這類(lèi)信息,那按照繼承的方式應(yīng)該如何實(shí)現(xiàn)呢?

我草草地估算了一下,基本上80% Java 候選人的簡(jiǎn)歷上,在專業(yè)技能欄上都會(huì)寫(xiě)上這么一條:

熟悉常用的GOF設(shè)計(jì)模式,可在實(shí)際業(yè)務(wù)場(chǎng)景中進(jìn)行合理運(yùn)用;

如果面試官恰好看到了這條專業(yè)技能,問(wèn)道:“那你說(shuō)一下,都熟悉并使用過(guò)哪些設(shè)計(jì)模式呢?”

然后,絕大多數(shù)候選人都會(huì)回答說(shuō):“嗯,熟悉單例模式和工廠模式?!?/span>

面試官接著問(wèn)道:“還有其他的嗎?”

候選人一般會(huì)說(shuō):“嗯,還有代理模式、策略模式這些吧,其實(shí)平時(shí)用到的也不是很多?!?/span>

此時(shí),若面試官繼續(xù)追問(wèn):“裝飾器模式有沒(méi)有了解過(guò)?”

候選人往往會(huì)發(fā)愣一下,然后說(shuō):“嗯,這個(gè)設(shè)計(jì)模式也聽(tīng)過(guò),但沒(méi)太深入了解?!?/span>

嗯,本文我們以真實(shí)場(chǎng)景帶入的方式來(lái)講解一下,有用且有趣的“裝飾器”模式。

接下來(lái)話不多說(shuō),Show me the case。

業(yè)務(wù)背景

某大型在線教育學(xué)習(xí)平臺(tái),其學(xué)生端最重要的功能就是展示學(xué)生的課程列表,學(xué)生可點(diǎn)擊課程列表中的某個(gè)課程進(jìn)教室上課,還可以查看這節(jié)課對(duì)應(yīng)的課件、課前預(yù)習(xí)和課后作業(yè)等。

如下圖所示:

圖片

當(dāng)然,真實(shí)的業(yè)務(wù)場(chǎng)景還是要復(fù)雜很多的,比如:英語(yǔ)課程的 PC 端按照上圖中的展示方式即可,而英語(yǔ)課程 iPad 端的產(chǎn)品經(jīng)理,則希望在已經(jīng)上過(guò)的課程中,加上老師給學(xué)生的打分。

數(shù)學(xué)課程與英語(yǔ)課程也有不同的地方,課程卡片上需要展示教師的標(biāo)簽,如:名師、活躍、名校畢業(yè)、教齡長(zhǎng)、好評(píng)多等;

最近新推出的繪畫(huà)課程,不但需要在課程卡片上展示教師的標(biāo)簽,而且為了鼓勵(lì)學(xué)生更多地上課學(xué)習(xí),會(huì)在課程卡片上展示一個(gè)完課獎(jiǎng)品。

當(dāng)然,我僅僅是舉個(gè)例子,實(shí)際的課表展示邏輯會(huì)復(fù)雜很多。

代碼質(zhì)量問(wèn)題

說(shuō)說(shuō)目前這塊的代碼實(shí)現(xiàn)情況。

最開(kāi)始的時(shí)候,公司只有英語(yǔ)課程,且 PC 端和 iPad 端的課表展示邏輯是一樣的。

代碼demo如下:

public class Curriculum {


    public void query(int studentID) {
    
        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");
    }
}

后來(lái),英語(yǔ)課程的 PC 端和 iPad 端的課表展示邏輯不一樣了,iPad 端的課表展示需要加上老師給學(xué)生的打分,代碼實(shí)現(xiàn)如下:

public class Curriculum {


    public void query(int studentID, int origin) {


        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");


        //英語(yǔ)課程iPad端
        if(origin == 1){
            System.out.println("展示對(duì)應(yīng)的學(xué)生評(píng)分");
        }
    }
}

再后來(lái),又增加了需要展示老師標(biāo)簽的數(shù)學(xué)課程,以及增加老師標(biāo)簽和完課獎(jiǎng)品的繪畫(huà)課程,都在一個(gè)類(lèi)中以 if else 分支判斷的方式來(lái)實(shí)現(xiàn),代碼的可讀性和可維護(hù)性就太差了。

于是,負(fù)責(zé)維護(hù)這塊業(yè)務(wù)代碼的工程師干脆一刀切,直接寫(xiě)成了四套代碼。

英語(yǔ)課程PC端:

public class EnglishPCCurriculum {


    public List query(int studentID) {
      
        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");
    }
}

英語(yǔ)課程iPad端:

public class EnglishIPadCurriculum {


    public void query(int studentID) {
    
        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");
        
        System.out.println("展示對(duì)應(yīng)的學(xué)生評(píng)分");
    }
}

數(shù)學(xué)課程:

public class MathCurriculum {


    public void query(int studentID) {
    
        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");
        
        System.out.println("展示對(duì)應(yīng)的老師標(biāo)簽");
    }
}

繪畫(huà)課程:

public class DrawCurriculum {


     public void query(int studentID) {
     
        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");
        
        System.out.println("展示對(duì)應(yīng)的老師標(biāo)簽");
        System.out.println("展示對(duì)應(yīng)的完課獎(jiǎng)品");
      }
}

劃重點(diǎn),代碼按照上述方式實(shí)現(xiàn),有何問(wèn)題?

在《重構(gòu)—改善既有代碼的設(shè)計(jì)》一書(shū)中,有兩種非常常見(jiàn)的Bad Smell(糟糕的代碼),叫做 “過(guò)長(zhǎng)的方法” 和 “重復(fù)的代碼” 。

其實(shí)問(wèn)題還是挺大的,我們上面的代碼只是實(shí)現(xiàn)了一個(gè)demo而已,如果是真實(shí)的代碼,這個(gè)查詢課表的query()方法實(shí)現(xiàn)了太多的業(yè)務(wù)邏輯,一定命中了“過(guò)長(zhǎng)的方法”這個(gè)Bad Semll。

而且,上面這四個(gè)類(lèi)中的query()方法,在實(shí)現(xiàn)展示課表、作業(yè)、預(yù)習(xí)、課件業(yè)務(wù)無(wú)邏輯的時(shí)候,也命中了“重復(fù)的代碼”這個(gè)Bad Semll。

除此之外,這段代碼還命中了一種叫做 “發(fā)散式變化” 的 Bad Smell。

發(fā)散式變化的定義是,一個(gè)類(lèi)被錨定了多個(gè)變化,當(dāng)這些變化中的任意一個(gè)發(fā)生時(shí),就必須對(duì)類(lèi)進(jìn)行修改。這說(shuō)明該類(lèi)承擔(dān)的職責(zé)過(guò)多,不符合單一職責(zé)的設(shè)計(jì)原則。

而上面這四個(gè)類(lèi)的query()方法中,從頭到尾實(shí)現(xiàn)了整個(gè)課表展示的邏輯,只要課表、作業(yè)、預(yù)習(xí)、課件等任意邏輯發(fā)生變化都需要對(duì)這個(gè)類(lèi)進(jìn)行修改,確實(shí)承擔(dān)的職責(zé)過(guò)多了。

接下來(lái),我們看看如何這塊代碼進(jìn)行重構(gòu),使其實(shí)現(xiàn)方式更具可維護(hù)性和可擴(kuò)展性。

裝飾器模式

裝飾器模式(Decorator Pattern),在不改變一個(gè)現(xiàn)有對(duì)象結(jié)構(gòu)的情況下,為其動(dòng)態(tài)地增加一些額外的職責(zé)。

裝飾器模式的優(yōu)點(diǎn)在于:

  1. 可動(dòng)態(tài)地為現(xiàn)有對(duì)象增加額外的職責(zé),無(wú)需改動(dòng)原來(lái)的代碼,具備更好的靈活性和可擴(kuò)展性,且符合開(kāi)閉原則。
  2. 每種額外的職責(zé)都被實(shí)現(xiàn)為一個(gè)單獨(dú)且通用的裝飾器,符合單一職責(zé),解決了“過(guò)長(zhǎng)的方法”、“重復(fù)的代碼”和“發(fā)散式變化”等Bad Smell。

其類(lèi)結(jié)構(gòu)圖如下:

圖片圖片

Component:定義了被裝飾對(duì)象和裝飾器都需要實(shí)現(xiàn)的接口。

ConcreteComponent:被裝飾對(duì)象,需要提供業(yè)務(wù)邏輯的核心功能。

Decorator:抽象裝飾器,可通過(guò)其子類(lèi)進(jìn)行額外功能職責(zé)的擴(kuò)展。

ConcreteDecorator:具體裝飾類(lèi),對(duì)被裝飾對(duì)象進(jìn)行額外功能職責(zé)的擴(kuò)展。

代碼重構(gòu)優(yōu)化

接下來(lái)我們通過(guò)裝飾器模式將代碼進(jìn)行重構(gòu)優(yōu)化。

Curriculum接口:

public interface Curriculum {


    public void query(int studentID);
}

Curriculum具體實(shí)現(xiàn):

public class ConcreteCurriculum implements Curriculum {


    public void query(int studentID) {


        System.out.println("展示課表");
        System.out.println("展示對(duì)應(yīng)的課前預(yù)習(xí)");
        System.out.println("展示對(duì)應(yīng)的課后作業(yè)");
        System.out.println("展示對(duì)應(yīng)的課件");
    }
}

Curriculum的抽象裝飾器:

public abstract class CurriculumDecorator implements Curriculum {
    protected Curriculum curriculum;


    public CurriculumDecorator(Curriculum curriculum){
        this.curriculum = curriculum;
    }


    public void query(int studentID){
        curriculum.query(studentID);
    }
}

Curriculum的評(píng)分裝飾器:

public class ScoreDecorator extends CurriculumDecorator{


    public ScoreDecorator(Curriculum curriculum) {
        super(curriculum);
    }


    @Override
    public void query(int studentID) {
        curriculum.query(studentID);
        System.out.println("展示對(duì)應(yīng)的學(xué)生評(píng)分");


    }
}

Curriculum的老師標(biāo)簽裝飾器:

public class LabelDecorator extends CurriculumDecorator{


    public LabelDecorator(Curriculum curriculum) {
        super(curriculum);
    }


    @Override
    public void query(int studentID) {
        curriculum.query(studentID);
        System.out.println("展示對(duì)應(yīng)的老師標(biāo)簽");


    }
}

Curriculum的獎(jiǎng)品裝飾器:

public class GiftDecorator extends CurriculumDecorator{


    public GiftDecorator(Curriculum curriculum) {
        super(curriculum);
    }


    @Override
    public void query(int studentID) {
        curriculum.query(studentID);
        System.out.println("展示對(duì)應(yīng)的完課獎(jiǎng)品");


    }
}

Demo:

public class Demo {
    public static void main(String[] args) {
        Curriculum curriculum = new ConcreteCurriculum();
        CurriculumDecorator scoreDecorator = new ScoreDecorator(new ConcreteCurriculum());
        CurriculumDecorator labelDecorator = new LabelDecorator(new ConcreteCurriculum());
        CurriculumDecorator giftLabelDecorator = new GiftDecorator(labelDecorator);




        System.out.println("英語(yǔ)PC端課表展示");
        curriculum.query(123);
        System.out.println();


        System.out.println("英語(yǔ)iPad端課表展示");
        scoreDecorator.query(123);
        System.out.println();


        System.out.println("數(shù)學(xué)課表展示");
        labelDecorator.query(123);
        System.out.println();


        System.out.println("繪畫(huà)課表展示");
        giftLabelDecorator.query(123);


    }
}

執(zhí)行結(jié)果:

英語(yǔ)PC端課表展示
展示課表
展示對(duì)應(yīng)的課前預(yù)習(xí)
展示對(duì)應(yīng)的課后作業(yè)
展示對(duì)應(yīng)的課件


英語(yǔ)iPad端課表展示
展示課表
展示對(duì)應(yīng)的課前預(yù)習(xí)
展示對(duì)應(yīng)的課后作業(yè)
展示對(duì)應(yīng)的課件
展示對(duì)應(yīng)的學(xué)生評(píng)分


數(shù)學(xué)課表展示
展示課表
展示對(duì)應(yīng)的課前預(yù)習(xí)
展示對(duì)應(yīng)的課后作業(yè)
展示對(duì)應(yīng)的課件
展示對(duì)應(yīng)的老師標(biāo)簽


繪畫(huà)課表展示
展示課表
展示對(duì)應(yīng)的課前預(yù)習(xí)
展示對(duì)應(yīng)的課后作業(yè)
展示對(duì)應(yīng)的課件
展示對(duì)應(yīng)的老師標(biāo)簽
展示對(duì)應(yīng)的完課獎(jiǎng)品

至此,展示課表業(yè)務(wù)場(chǎng)景的代碼改造完畢。

有的同學(xué)可能會(huì)問(wèn),為什么不通過(guò)繼承的方式進(jìn)行實(shí)現(xiàn)呢?

其原因在于,繼承的方式不如這種動(dòng)態(tài)組合的方式靈活,也很難實(shí)現(xiàn)這種細(xì)粒度的代碼復(fù)用。

舉個(gè)例子:如果數(shù)學(xué)和繪畫(huà)課程又新增了需求,需要額外展示對(duì)應(yīng)的輔修資料,但英語(yǔ)課程則不需要展示這類(lèi)信息,那按照繼承的方式應(yīng)該如何實(shí)現(xiàn)呢?


責(zé)任編輯:武曉燕 來(lái)源: 托尼學(xué)長(zhǎng)
相關(guān)推薦

2021-11-02 22:04:58

模式

2020-08-03 07:38:12

單例模式

2021-11-03 14:10:28

工廠模式場(chǎng)景

2020-07-20 07:48:53

單例模式

2020-07-02 07:52:11

RedisHash映射

2021-02-16 10:53:19

單例模式面試

2021-09-10 06:50:03

TypeScript裝飾器應(yīng)用

2024-03-06 13:19:19

工廠模式Python函數(shù)

2021-05-28 11:18:50

MySQLbin logredo log

2024-08-22 10:39:50

@Async注解代理

2024-03-05 10:33:39

AOPSpring編程

2024-05-30 08:04:20

Netty核心組件架構(gòu)

2025-03-05 08:04:31

2024-02-29 16:49:20

volatileJava并發(fā)編程

2024-11-19 15:13:02

2024-08-29 16:30:27

2023-12-27 18:16:39

MVCC隔離級(jí)別幻讀

2025-04-16 00:00:01

JWT客戶端存儲(chǔ)加密令

2025-04-08 00:00:00

@AsyncSpring異步

2024-08-12 17:36:54

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)