前端學設計模式

轉自:https://github.com/jacksplwxy/DesignPatterns_TypeScripthtml

 

*標題:
·前端開發者如何學習設計模式
·系統學習設計模式
·TypeScript版設計模式前端


*前言:
·背景:
①前端開發者廣泛缺少設計模式相關知識,但網上相關資料基本都是基於java或c#,不利於前端學習。
②經典23種設計模式來源於靜態語言,部分基於js的設計模式書籍每每又只是單單經過動態語言js來實現,不利於真正理解設計模式。
③由於設計模式很難驗證對錯,網絡上存在大量錯誤的設計模式教程誤導新人。
④設計模式資料廣泛只講述模式的實現步驟,缺少對理解這些步驟的基礎知識進行講解,致使對設計模式的學習只是表面招式。
·項目內容:
①幫助缺少經驗的前端開發者瞭解設計模式的前因後果,補充學習設計模式須要瞭解的基礎知識,如:設計原則、解耦、控制反轉、依賴注入、IoC容器、反射、註解、裝飾器等。
②着重實現TypeScript版設計模式代碼。
③補充實現Java/JavaScript版設計模式代碼。
·理解有誤處歡迎指出。java


*什麼是設計模式?
·狹義的設計模式:指的是GoF四人組在《Design Patterns: Elements of Reusable Object-Oriented Software》一書中提出的23種設計模式。
·廣義的設計模式:最先的設計模式是美國著名建築大師克里斯托夫·亞歷山大在他的著做《建築模式語言:城鎮、建築、構造》中描述了一些常見的建築設計問題,並提出了253種關於對城鎮、鄰里、住宅、花園和房間等進行設計的基本模式。後來軟件界也開始論壇設計模式的話題,由於這也是相似的。因此設計模式指的是解決特定問題的一系列套路,是前輩們的代碼設計經驗的總結,具備必定的廣泛性,是能夠反覆使用的。git


*爲何要學習設計模式?
在弄清楚這個問題以前,咱們先思考什麼纔是高質量的程序?高質量程序的特色:開發週期短、代碼無bug、代碼性能高、代碼易閱讀、代碼夠健壯、代碼易擴展、代碼易重用。
那咱們如何開發出高質量程序呢?一個重要的方式就是遵照前人總結的7大設計原則:①開閉原則:總綱,要對擴展開放,對修改關閉②里氏替換原則:不要破壞繼承體系③依賴倒置原則:要面向接口編程④單一職責原則:實現類要職責單一⑤接口隔離原則:在設計接口的時候要精簡單一⑥迪米特法則:要下降耦合度⑦合成複用原則:要優先使用組合或者聚合關係複用,少用繼承關係複用。
好的程序須要靠7大設計原則完成,可是因爲語言的缺陷致使程序須要按照必定複雜度的步驟去實現這些設計原則,而這些步驟一般都是固定的,就像武功中的套路招式同樣,若是再給這些套路加上好聽的名字,這就成了設計模式。也就是說23種設計模式就是7大設計原則在某些語言的具體實現的一種方式,每一個設計模式的背後咱們都能找到其依靠的一種或多種設計原則。換句話說就是,只要咱們寫代碼遵循設計原則,代碼就會天然而然變成了某些設計模式,這也在王垠的《解密「設計模式」》中獲得證實。
因爲現實問題的複雜性,每每致使代碼不可能同時知足全部的設計原則,甚至要違背部分設計原則,這裏就會有一個最佳實踐的問題了。而設計模式就爲解決特定問題提供了最佳實踐方案,以致於學習了設計模式後,在遇到特定問題時,咱們腦子很容易知道如何在知足設計原則最優解的基礎上實現代碼的編寫。
雖然設計原則爲開發出高質量程序指明瞭方向,但沒有對程序的高性能、易用性等作出指示,而設計模式在這方面作了補充。
總結:①設計模式經過寫代碼的形式幫助咱們更好的學習理解設計原則;②爲實現設計原則的招式統一命名;③爲特定場景問題的提供最優解決方案;④補充了設計原則在構建高性能、易使用程序等方面的內容。es6


*解耦與高質量程序之間的是什麼關係?
·解耦(低耦合)在某個角度上說屬於高質量程序的一個重要體現,但不是所有。高質量程序具體還體如今代碼易複用、代碼安全穩定、代碼層次結構清晰、代碼易擴展、代碼性能好等等許多細節方面。
·解耦是一個很模糊的定義,耦合性高低實際上能夠用依賴性、正交性、緊湊性3個指標來衡量,具體參考:https://www.zhihu.com/question/21386172/answer/54476702 。實際上這3個指標大概分別對應着7大設計原則中的迪米特法則、單一職責原則、接口隔離原則等原則,而7大設計原則也就是高質量軟件的編寫原則,因此也印證瞭解耦是屬於高質量程序的一部分體現。github


*設計模式補充:
·學習設計模式的核心是掌握設計模式的意圖是什麼:不一樣的設計模式在不一樣的語言中會有不一樣表現形式,千萬不要被模式中的各類概念迷惑,而只學到表面套路。我看到不少熱門教程是示例代碼甚至是錯誤的,例如這個超過2k star的設計模式項目https://github.com/torokmark/design_patterns_in_typescript ,他的工廠方法的實現就不對,若是要增長新的產品,他就必須必須修改createProduct代碼,但這違背了開閉原則。致使這個錯誤的緣由就是由於做者沒有理解工廠方法的目的:對簡單工廠模式的進一步抽象化,使系統在不修改原來代碼的狀況下引進新的產品,即知足開閉原則。又如這個項目的原型模式的實現也是錯誤的,原型模式的場景是由於複製一個對象比new一個對象更高效。因此學習設計模式的正確姿式應該是:掌握該設計模式的意圖,並在遵照設計原則的狀況下去實現它。
·設計模式的招式並非一成不變的,它在不一樣語言中會有不一樣的表現,但背後的思想和設計原則都是同樣的,例如裝飾器模式在java中須要定義抽象構件、抽象裝飾,而ts只需一個@便可搞定。例如Java8加入lambda後,不少設計模式的實現都會改變。厲害的語言不須要那麼多設計模式也能知足設計原則,參考:https://zhuanlan.zhihu.com/p/19835717
·設計模式在不一樣語言之間的實現原理:GoF的《設計模式》一書是針對面嚮對象語言提煉的技巧,但並不意味着設計模式只能用面嚮對象語言來寫,實際上動態語言也是可使用設計模式的。例如Java這種靜態編譯型語言中,沒法動態給已存在的對象添加職責,全部通常經過包裝類的方式來實現裝飾者模式。可是js這種動態解釋型語言中,給對象動態添加職責是再簡單不過的事情。這就形成了js的裝飾者模式再也不關注給對象動態添加職責,而是關注於給函數動態添加職責。例若有人模擬js版本的工廠模式,而生硬地把建立對象延遲到子類中。實際上,在java等靜態語言中,讓子類來「決定」建立何種對象的緣由是爲了讓程序迎合依賴倒置原則。在這些語言中建立對象時,先解開對象類型之間的耦合關係很是重要,這樣纔有機會在未來讓對象表現出多態性。而在js這類類型模糊的語言中,對象多態性是天生的,一個變量既能夠指向一個類,又能夠隨時指向另外一個類。js不存在類型耦合的問題,天然也沒有刻意去把對象「延遲」到子類建立,也就是說,js其實是不須要工廠方法模式的。模式的存在首先是爲了知足設計原則的。
·分辨模式的關鍵是意圖而不是結構:在設計模式的學習中,有人常常會發出這樣的疑問:代理模式和裝飾着模式,策略模式和狀態模式,這些模式的類圖看起來幾乎如出一轍,他們到底有什麼區別?實際上這種狀況是廣泛存在的,許多模式的類圖看起來都差很少,模式只有放在具體的環境下才有意義。好比咱們的手機,用它當電話的時候它就是電話;用它當鬧鐘的時候它就是鬧鐘;用它玩遊戲的時候他就是遊戲機。有不少模式的類圖和結構確實很類似,但這不過重要,辨別模式的關鍵是這個模式出現的場景,以及爲咱們解決了什麼問題。
·設計模式一直在發展,例如如今逐漸流行起來的模塊模式、沙箱模式等,但真正獲得人們的承認還須要時間的檢驗。
·設計模式的合理使用:
-- 整體來講,使用設計模式的必要性的程度是逐級遞增的:應用程序(Application) < 工具包/類庫(ToolKit/Library) < 框架(Framework)
-- 具體來講,咱們沒必要刻意爲了設計模式而設計模式,例如咱們當前只須要建立一個產品,而將來沒有多大可能增長新產品時,咱們就用簡單工廠模式,而無需爲了符合開閉原則而去選擇工廠方法模式。引用輪子哥的話:「爲了合理的利用設計模式,咱們應該明白一個概念,叫作擴展點。擴展點不是天生就有的,而是設計出來的。咱們設計一個軟件的架構的時候,咱們也要同時設計一下哪些地方之後能夠改,哪些地方之後不能改。假若你的設計不能知足現實世界的須要,那你就要重構,把有用的擴展點加進去,把沒用的擴展點去除掉。這跟你用不用設計模式不要緊,跟你對具體的行業的理解有關係。假若你設計好了每個擴展點的位置,那你就能夠在每個擴展點上應用設計模式,你就不須要去想到底這個擴展點要怎麼實現他纔會真正成爲一個擴展點,你只須要按照套路寫出來就行了。
·設計模式命名:爲了他人能快速理解你使用的設計模式,建議參考模式英文名進行命名,例如:建造者模式——xxxBuilder,單例模式——xxxSingleton,適配器模式——xxxAdapter,狀態模式——xxxState,策略模式——xxxStratege等等。
·對設計模式的誤解:
-- 習慣把靜態語言的設計模式照搬到動態語言中
-- 習慣根據模式名稱去臆測該模式的一切web


*開閉原則:
·定義:一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應儘可能在不修改原有代碼的狀況下進行擴展。
·解釋:
當軟件系統須要面對新的需求時,咱們應該儘可能保證系統的設計框架是穩定的。若是一個軟件設計符合開閉原則,那麼能夠很是方便地對系統進行擴展,並且在擴展時無須修改現有代碼,使得軟件系統在擁有適應性和靈活性的同時具有較好的穩定性和延續性。
爲了知足開閉原則,須要對系統進行抽象化設計,抽象化是開閉原則的關鍵。在Java、C#等編程語言中,能夠爲系統定義一個相對穩定的抽象層,而將不一樣的實現行爲移至具體的實現層中完成。在不少面向對象編程語言中都提供了接口、抽象類等機制,能夠經過它們定義系統的抽象層,再經過具體類來進行擴展。若是須要修改系統的行爲,無須對抽象層進行任何改動,只須要增長新的具體類來實現新的業務功能便可,實如今不修改已有代碼的基礎上擴展系統的功能,達到開閉原則的要求。
在軟件開發中,通常不把對配置文件的修改認爲是對系統源代碼的修改。若是一個系統在擴展時只涉及到修改配置文件,而原有的代碼沒有作任何修改,該系統便可認爲是一個符合開閉原則的系統。因此,配置文件+反射技術在java、c#、.net等各種後端語言中大量採用,以實現徹底開閉原則。
·開閉原則的實現由里氏替換原則和依賴倒置原則來完成。算法


*里氏替換原則:
·背景:
繼承的優勢:代碼共享,減小建立類的工做量,每一個子類都擁有父類的方法和屬性、提升代碼的重用性、子類能夠形似父類,但又異於父類、提升代碼的可擴展性、提升產品或項目的開放性。
繼承的缺點:繼承是侵入性的:只要繼承,就必須擁有父類的全部屬性和方法、下降代碼的靈活性:子類必須擁有父類的屬性和方法、加強了耦合性:當父類的常量、變量和方法被修改時,必須要考慮子類的修改,並且在缺少規範的環境下,這種修改可能帶來很是糟糕的結果大片的代碼須要重構
里氏替換原則可以克服繼承的缺點。
·定義:子類能夠擴展父類的功能,但不能改變父類原有的功能。父類能出現的地方均可以用子類來代替,並且換成子類也不會出現任何錯誤或異常,而使用者也無需知道是父類仍是子類,但反過來則不成立。
·解釋:
LSP的原定義比較複雜,咱們通常對里氏替換原則 LSP的解釋爲:子類對象可以替換父類對象,而程序邏輯不變。
里氏代換原則告訴咱們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,若是一個軟件實體使用的是一個子類對象的話,那麼它不必定可以使用基類對象。例如:我喜歡動物,那我必定喜歡狗,由於狗是動物的子類;可是我喜歡狗,不能據此判定我喜歡動物,由於我並不喜歡老鼠,雖然它也是動物。
里氏替換原則有至少如下兩種含義:
①里氏替換原則是針對繼承而言的,若是繼承是爲了實現代碼重用,也就是爲了共享方法,那麼共享的父類方法就應該保持不變,不能被子類從新定義。子類只能經過新添加方法來擴展功能,父類和子類均可以實例化,而子類繼承的方法和父類是同樣的,父類調用方法的地方,子類也能夠調用同一個繼承得來的,邏輯和父類一致的方法,這時用子類對象將父類對象替換掉時,固然邏輯一致,相安無事。
②若是繼承的目的是爲了多態,而多態的前提就是子類覆蓋並從新定義父類的方法,爲了符合LSP,咱們應該將父類定義爲抽象類,並定義抽象方法,讓子類從新定義這些方法,當父類是抽象類時,父類就是不能實例化,因此也不存在可實例化的父類對象在程序裏。也就不存在子類替換父類實例(根本不存在父類實例了)時邏輯不一致的可能。
不符合LSP的最多見的狀況是,父類和子類都是可實例化的非抽象類,且父類的方法被子類從新定義,這一類的實現繼承會形成父類和子類間的強耦合,也就是實際上並不相關的屬性和方法牽強附會在一塊兒,不利於程序擴展和維護。
如何符合LSP?總結一句話 —— 就是儘可能不要從可實例化的父類中繼承,而是要使用基於抽象類和接口的繼承。
·最佳實踐:
①子類必須徹底實現父類的抽象方法,但不能覆蓋父類的非抽象方法;
②子類中能夠增長本身特有的方法;
③當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數要更寬鬆;
④當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。
·里氏代換原則和依賴倒置原則同樣,是開閉原則的具體實現手段之一。typescript

 

*依賴倒置原則:
·定義:高層模塊不該該依賴低層模塊,二者都應該依賴其抽象;抽象不該該依賴細節,細節應該依賴抽象。
·解釋:
高層模塊不該該直接依賴於底層模塊的具體實現,而應該依賴於底層的抽象。換言之,模塊間的依賴是經過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是經過接口或抽象類產生的。
接口和抽象類不該該依賴於實現類,而實現類依賴接口或抽象類。這一點其實不用多說,很好理解,「面向接口編程」思想正是這點的最好體現。
·解釋:
常規咱們認爲上層模塊應該依賴下層,可是這也有個問題就是,下層變更將致使「牽一髮動全身」。依賴倒置就是反常規思惟,將本來的高層建築依賴底層建築「倒置」過來,變成底層建築依賴高層建築。高層建築決定須要什麼,底層去實現這樣的需求,可是高層並不用管底層是怎麼實現的。這樣就不會出現前面的「牽一髮動全身」的狀況。固然,嚴格的來講上層不該該依賴下層,而依賴自身接口,經過注入的方式依賴其餘接口。
·控制反轉(Inversion of Control):就是依賴倒置原則的一種代碼設計的思路。具體採用的實現方法就是所謂的依賴注入(Dependency Injection)。
·控制反轉容器(IoC Container):,由於採用了依賴注入,在初始化的過程當中就不可避免的會寫大量的new,而且還要要管理各個對象之間依賴關係,因此這裏使用工廠方法仍是比較麻煩。而IoC容器就解決以上2個問題。這個容器能夠自動對你的代碼進行初始化,你只須要維護一個Configuration(能夠是xml能夠是一段代碼),而不用每次初始化一實例都要親手去寫那一大段初始化的代碼。另一個好處是:咱們在建立實例的時候不須要了解其中的依賴細節。
·java的反射機制:
-- 類也是對象,類是java.lang.Class類的實例對象
-- 編譯時刻加載類是靜態加載類,運行時刻加載類是動態加載類。new建立對象是靜態加載類,在編譯時刻就要加載全部可能用到的類,這樣致使某一個類(的類型)出現問題,其餘全部類都將沒法使用(沒法經過編譯)。經過動態加載類能夠解決這個問題。動態加載的方法,即經過類的類類型建立該類的對象,示例:Class c=Class.forName(args[0]);c.newInstance();
-- 獲取類的方法信息,代碼示例:Class c=obj.getClass();Method[] ms=c.getMethods();//參數列表獲取省略
-- 獲取類的成員變量信息,代碼示例:Class c=obj.getClass();Field[] fs=c.getFields();
-- 獲取類的構造方法信息信息,代碼示例:Class c=obj.getClass();Constructor[] cs=c.getConstructors();//參數列表獲取省略
-- 方法的反射,操做步驟:①根據方法的名稱和參數列表獲取惟一的方法;②拿到方法後,method.invoke(對象,參數列表)。
-- 反射的操做都是編譯成字節碼之後的操做
-- java中集合的泛型是防止錯誤輸入的,只在編譯階段有效,繞過編譯就無效了。(其實typescirpt也是這樣的,類型檢查只發生在編譯時)
-- 由於反射的操做是編譯後的,因此不存在類型檢查問題了
-- 小結:Java反射機制容許程序在運行時透過Reflection APIs取得任意一個已知名稱的class的內部信息,包括modifiers(如public、static等)、superclass(如Object)、實現的interfaces(如Serializable)、fields(屬性)和methods(方法)(但不包括methods定義),可於運行時改變fields的內容,也可調用methods.
-- 配置文件+反射機制實現開閉原則:在引入配置文件和反射機制後,須要更換或增長新的具體類將變得很簡單,只需增長新的具體類並修改配置文件便可,無須對現有類庫和客戶端代碼進行任何修改,徹底符合開閉原則。在不少設計模式中均可以經過引入配置文件和反射機制來對客戶端代碼進行改進,如在抽象工廠模式中能夠將具體工廠類類名存儲在配置文件中,在適配器模式中能夠將適配器類類名存儲在配置文件中,在策略模式中能夠將具體策略類類名存儲在配置文件中等等。經過對代碼的改進,可讓系統具備更好的擴展性和靈活性,更加知足各類面向對象設計原則的要求。
·java自定義註解:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description{
String desc();
String author();
int age() default 18;
}
-- 使用@interface關鍵詞定義註解
-- 成員以無參無異常方式聲明
-- 能夠用default爲成員指定一個默認值
-- 成員類型是受限制的,合法的類型包括原始類型及String,Class,Annotation,Enumeration
-- 若是註解只有一個成員,則成員名必須取名爲value(),在使用時能夠忽略成員名和賦值號(=)
-- 註解類能夠沒有成員,沒有成員的註解成爲標識註解
-- @Target、@Retention、@Inherited、@Documented爲註解Description的註解,即元註解
-- @Target(options)中的options包括註解的做用域,該例中表示註解能夠在方法和類中使用。完整的做用域項包括:CONSTRUCTOR(構造方法)、FIELD(字段)、LOCAL_VARIABLE(局部變量)、METHOD(方法)、PACKAGE(包)、PARAMETER(參數)、TYPE(類,接口)
-- @Retention():註解生命週期,包括SOURCE(只在源碼顯示,編譯時丟棄)、CLASS(編譯時會記錄到class中,運行時忽略)、RUNTIME(運行時存在,能夠經過反射讀取)
-- @Inherited:容許子註解繼承,子類只能繼承父類的類上的註解,不會繼承成員上的註解
-- @Documented:生成javadoc時會包含註解信息
--使用自定義註解:
@Description(desc='I am eyeColor',author='Jacksplwxy',age=18)
public String eyeColor(){
return 'red';
}
·元數據:指用來描述數據的數據。
·元數據和註解:註解能夠用來描述數據,全部註解是元數據的一種實現方式。
·註解的做用:註解有不少做用,其中最重要的一個就是搭配反射實現開閉原則。由於註解能夠被反射解析出來,此時的註解至關於一個配置文件。另外,在java中,除了註解充當配置文件,還能夠用xml做爲配置文件,但註解優勢明顯:①在class文件中,能夠下降維護成本,annotation的配置機制很明顯簡單;②不須要第三方的解析工具,利用java反射技術就能夠完成任務;③編輯期能夠驗證正確性,差錯變得容易;④ 提升開發效率
·ts中的註解:ts中其實沒有註解的概念,可是前端界曾經仍是有語言借鑑了註解:Angular2的AtScript語言,它能完徹底全的單純附加元數據。例如:
@Component({
selector: 'app'
})
class AppComponent {}
等價於
class AppComponent {}
AppComponent.annotations = [
new Component({
selector: 'app'
})
]
·註解和裝飾器區別:
-- 註解(Annotation):java中元數據的一種實現方式。僅提供附加元數據支持,並不能實現任何操做。須要另外的 Scanner 根據元數據執行相應操做。
-- 裝飾器(Decorator):ES6中增長的對裝飾器模式的簡單實現。其僅提供定義劫持,可以對類及其方法的定義並無提供任何附加元數據的功能。
他們語法類似,都是@符號。但註解僅僅爲數據提供一些更加細節的屬性描述,咱們能夠利用反射等方式來獲取這些描述再進行函數操做。而裝飾器能夠至關於直接附加函數操做。實際上,二者在實現上均可以相互模擬。
·ts的反射機制實現:
-- 回顧反射:反射就是根據類名獲取其更詳細信息
-- Function.prototype.toString實現反射:Function.prototype.toString這個原型方法能夠幫助你得到函數的源代碼,經過合適的正則, 咱們能夠從中提取出豐富的信息。可是並不方便,也不優雅。
-- JavaScript自己爲動態語言,天生具有反射能力,例如遍歷對象內全部屬性、判斷數據類型。ES6中新增了新的api:Reflect,來把這些操做歸結到一塊兒。Reflect可以獲取到類中的成員變量和方法,可是因爲js迎合了web的壓縮特色,因此其沒法獲取到參數名。解決方案就是經過在方法上添加定義了參數相關的裝飾器,再解析裝飾器便可獲取參數名了。惋惜ES6的Reflect也沒法獲取到究竟有哪些裝飾器添加到這個類/方法上。爲了獲取到裝飾器,Reflect Metadata應運而生,它是ES7的一個提案,它主要用來在聲明的時候添加和讀取裝飾器的。
·依賴注入的三種方式:
-- 構造函數注入
-- 屬性注入
-- 接口注入
·ts實現IoC容器:
-- 《使用Typescript實現依賴注入(DI)》:https://blog.csdn.net/HaoDaWang/article/details/79776021
·里氏代換原則和依賴倒置原則同樣,是開閉原則的具體實現手段之一。
·最佳實踐:
-- 每一個類儘可能都有接口或抽象類,或者抽象類和接口二者都具有:這是依賴倒置的基本要求,接口和抽象類都是屬於抽象的,有了抽象纔可能依賴倒置。
-- 變量的顯示類型儘可能是接口或者是抽象類:不少書上說變量的類型必定要是接口或者是抽象類,這個有點絕對化了,好比一個工具類,xxxUtils通常是不須要接口或是抽象類的。還有,若是你要使用類的clone方法,就必須使用實現類,這個是JDK提供一個規範。
-- 任何類都不該該從具體類派生:若是一個項目處於開發狀態,確實不該該有從具體類派生出的子類的狀況,但這也不是絕對的,由於人都是會犯錯誤的,有時設計缺陷是在所不免的,所以只要不超過兩層的繼承都是能夠忍受的。特別是作項目維護的同志,基本上能夠不考慮這個規則,爲何?維護工做基本上都是作擴展開發,修復行爲,經過一個繼承關係,覆寫一個方法就能夠修正一個很大的Bug,何須再要去繼承最高的基類呢?
-- 儘可能不要覆寫基類的方法:若是基類是一個抽象類,並且這個方法已經實現了,子類儘可能不要覆寫。類間依賴的是抽象,覆寫了抽象方法,對依賴的穩定性會產生必定的影響。
-- 結合里氏替換原則使用:里氏替換原則要求父類出現的地方子類就能出現,再依賴倒置原則,咱們能夠得出這樣一個通俗的規則: 接口負責定義public屬性和方法,而且聲明與其餘對象的依賴關係,抽象類負責公共構造部分的實現,實現類準確的實現業務邏輯,同時在適當的時候對父類進行細化。
·關於接口和抽象類:實際開發中90%的狀況使用接口,由於其簡潔、靈活。而抽象類只在既起約束做用又須要複用代碼時才使用。
·文檔:
-- 《Spring IoC有什麼好處呢?》:https://www.zhihu.com/question/23277575/answer/169698662
-- 《依賴倒置原則》:https://www.cnblogs.com/cbf4life/archive/2009/12/15/1624435.html
-- 《小話設計模式原則之:依賴倒置原則DIP》:https://zhuanlan.zhihu.com/p/24175489編程


*單一職責原則:
·定義:一個類只負責一個功能領域中的相應職責,或者就一個類而言,應該只有一個引發它變化的緣由。
·解釋:一個類不能太「累」!在軟件系統中,一個類(大到模塊,小到方法)承擔的職責越多,它被複用的可能性就越小,並且一個類承擔的職責過多,就至關於將這些職責耦合在一塊兒,當其中一個職責變化時,可能會影響其餘職責的運做,所以要將這些職責進行分離,將不一樣的職責封裝在不一樣的類中,即將不一樣的變化緣由封裝在不一樣的類中,若是多個職責老是同時發生改變則可將它們封裝在同一類中。


*接口隔離原則:
·定義:一個類對另外一個類的依賴應該創建在最小的接口上,即要爲各個類創建它們須要的專用接口,而不要試圖去創建一個很龐大的接口供全部依賴它的類去調用。
·解釋:
-- 接口儘可能小,可是要有限度。一個接口只服務於一個子模塊或業務邏輯。
-- 爲依賴接口的類定製服務。只提供調用者須要的方法,屏蔽不須要的方法。
-- 瞭解環境,拒絕盲從。每一個項目或產品都有選定的環境因素,環境不一樣,接口拆分的標準就不一樣深刻了解業務邏輯。
-- 提升內聚,減小對外交互。使接口用最少的方法去完成最多的事情。


*迪米特法則:
·定義:一個軟件實體應當儘量少地與其餘實體發生相互做用。
·解釋:
-- 保證類之間單向依賴(jacksplwxy)
-- 從依賴者的角度來講,只依賴應該依賴的對象。
-- 從被依賴者的角度說,只暴露應該暴露的方法。
-- 在類的劃分上,應該建立弱耦合的類。類與類之間的耦合越弱,就越有利於實現可複用的目標。
-- 在類的結構設計上,儘可能下降類成員的訪問權限。
-- 在類的設計上,優先考慮將一個類設置成不變類。
-- 在對其餘類的引用上,將引用其餘對象的次數降到最低。
-- 不暴露類的屬性成員,而應該提供相應的訪問器(set 和 get 方法)。
-- 謹慎使用序列化(Serializable)功能。


*合成複用原則:
·定義:儘可能使用對象組合,而不是繼承來達到複用的目的。
·解釋:
在面向對象設計中,能夠經過兩種方法在不一樣的環境中複用已有的設計和實現,即經過組合/聚合關係或經過繼承,但首先應該考慮使用組合/聚合,組合/聚合可使系統更加靈活,下降類與類之間的耦合度,一個類的變化對其餘類形成的影響相對較少;其次才考慮繼承,在使用繼承時,須要嚴格遵循里氏代換原則,有效使用繼承會有助於對問題的理解,下降複雜度,而濫用繼承反而會增長系統構建和維護的難度以及系統的複雜度,所以須要慎重使用繼承複用。
經過繼承來進行復用的主要問題在於繼承複用會破壞系統的封裝性,由於繼承會將基類的實現細節暴露給子類,因爲基類的內部細節一般對子類來講是可見的,因此這種複用又稱「白箱」複用,若是基類發生改變,那麼子類的實現也不得不發生改變;從基類繼承而來的實現是靜態的,不可能在運行時發生改變,沒有足夠的靈活性;並且繼承只能在有限的環境中使用(如類沒有聲明爲不能被繼承)。
因爲組合或聚合關係能夠將已有的對象(也可稱爲成員對象)歸入到新對象中,使之成爲新對象的一部分,所以新對象能夠調用已有對象的功能,這樣作可使得成員對象的內部實現細節對於新對象不可見,因此這種複用又稱爲「黑箱」複用,相對繼承關係而言,其耦合度相對較低,成員對象的變化對新對象的影響不大,能夠在新對象中根據實際須要有選擇性地調用成員對象的操做;合成複用能夠在運行時動態進行,新對象能夠動態地引用與成員對象類型相同的其餘對象。

 


*設計模式:
·建立型模式(5種):
-- 單例(Singleton)模式:某個類只能生成一個實例,該類提供了一個全局訪問點供外部獲取該實例,其拓展是有限多例模式。
-- 原型(Prototype)模式:將一個對象做爲原型,經過對其進行復制而克隆出多個和原型相似的新實例。
-- 工廠方法(FactoryMethod)模式:定義一個用於建立產品的接口,由子類決定生產什麼產品。
-- 抽象工廠(AbstractFactory)模式:提供一個建立產品族的接口,其每一個子類能夠生產一系列相關的產品。
-- 建造者(Builder)模式:將一個複雜對象分解成多個相對簡單的部分,而後根據不一樣須要分別建立它們,最後構建成該複雜對象。
·結構型模式(7種):
-- 代理(Proxy)模式:爲某對象提供一種代理以控制對該對象的訪問。即客戶端經過代理間接地訪問該對象,從而限制、加強或修改該對象的一些特性。
-- 適配器(Adapter)模式:將一個類的接口轉換成客戶但願的另一個接口,使得本來因爲接口不兼容而不能一塊兒工做的那些類能一塊兒工做。
-- 橋接(Bridge)模式:將抽象與實現分離,使它們能夠獨立變化。它是用組合關係代替繼承關係來實現的,從而下降了抽象和實現這兩個可變維度的耦合度。
-- 裝飾(Decorator)模式:動態地給對象增長一些職責,即增長其額外的功能。
-- 外觀(Facade)模式:爲多個複雜的子系統提供一個一致的接口,使這些子系統更加容易被訪問。
-- 享元(Flyweight)模式:運用共享技術來有效地支持大量細粒度對象的複用。
-- 組合(Composite)模式:將對象組合成樹狀層次結構,使用戶對單個對象和組合對象具備一致的訪問性。
·行爲型模式(11種):
-- 模板方法(Template Method)模式:定義一個操做中的算法骨架,將算法的一些步驟延遲到子類中,使得子類在能夠不改變該算法結構的狀況下重定義該算法的某些特定步驟。
-- 策略(Strategy)模式:定義了一系列算法,並將每一個算法封裝起來,使它們能夠相互替換,且算法的改變不會影響使用算法的客戶。
-- 命令(Command)模式:將一個請求封裝爲一個對象,使發出請求的責任和執行請求的責任分割開。
-- 職責鏈(Chain of Responsibility)模式:把請求從鏈中的一個對象傳到下一個對象,直到請求被響應爲止。經過這種方式去除對象之間的耦合。
-- 狀態(State)模式:容許一個對象在其內部狀態發生改變時改變其行爲能力。
-- 觀察者(Observer)模式:多個對象間存在一對多關係,當一個對象發生改變時,把這種改變通知給其餘多個對象,從而影響其餘對象的行爲。
-- 中介者(Mediator)模式:定義一箇中介對象來簡化原有對象之間的交互關係,下降系統中對象間的耦合度,使原有對象之間沒必要相互瞭解。
-- 迭代器(Iterator)模式:提供一種方法來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
-- 訪問者(Visitor)模式:在不改變集合元素的前提下,爲一個集合中的每一個元素提供多種訪問方式,即每一個元素有多個訪問者對象訪問。
-- 備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取並保存一個對象的內部狀態,以便之後恢復它。
-- 解釋器(Interpreter)模式:提供如何定義語言的文法,以及對語言句子的解釋方法,即解釋器。

 

*主要參考文獻:·《Java設計模式:23種設計模式全面解析》,http://c.biancheng.net/design_pattern/·《設計模式|菜鳥教程》,http://www.runoob.com/design-pattern/design-pattern-tutorial.html·《劉偉技術博客》,https://blog.csdn.net/lovelion·Carson_Ho,《設計模式》,https://blog.csdn.net/carson_ho/column/info/14783·劉偉,《設計模式Java版》,https://gof.quanke.name/·曾探,《JavaScript設計模式與開發實踐》,北京:人民郵電出版社,2015.5·Addy Osmani,《JavaScript設計模式》,徐濤 譯,北京:人民郵電出版社,2013.6·codeTao,《23種設計模式全解析》,http://www.cnblogs.com/geek6/p/3951677.html·me115,《圖說設計模式》,https://design-patterns.readthedocs.io/zh_CN/latest/index.html·vczh,《爲何咱們須要學習(設計)模式》,https://zhuanlan.zhihu.com/p/19835717·楊博,《代碼耦合是怎麼回事呢?》,https://www.zhihu.com/question/21386172/answer/54476702·知乎問題,《爲什麼大量設計模式在動態語言中不適用》,https://www.zhihu.com/question/63734103·Mingqi,《Spring IoC有什麼好處呢?》,https://www.zhihu.com/question/23277575/answer/169698662·阮一峯,《ECMAScript 6 入門》,http://es6.ruanyifeng.com/·蕭蕭,《設計模式在實際開發中用的多嗎》,https://www.zhihu.com/question/29477933/answer/586378235·DD菜,《淺析Typescript設計模式》,https://zhuanlan.zhihu.com/p/43283016·semlinker,https://github.com/semlinker/typescript-design-patterns

相關文章
相關標籤/搜索