日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

设计原则——设计模式基础

發布時間:2024/5/8 84 豆豆
生活随笔 收集整理的這篇文章主要介紹了 设计原则——设计模式基础 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在講設計原則之前,我先強制灌輸大家一波雞湯,提倡

面向接口編程,代碼的設計更重要的是考慮以后的擴展和可維護性

大家帶著這樣的思維來學習設計模式以及設計原則,慢慢就意會這波毒雞湯了。
先聲明一點就是老衲的blog,也是邊學習,邊記錄,而后以討論交流的方式敘述,有什么不對的地方大家多多擔待。

設計原則

單一職責原則(Single Responsibility Principle, 簡稱是SRP)

老衲是一位粗鄙之人,所以描述也是盡可能的白話哈~

定義

白話單一職責:首先,顧名思義,什么是單一職責?就是某玩意,專門負責某個東西,就譬如說,你的手機屏幕,他就只負責顯示,不管顯示app還是視頻還是小黃書,只要是顯示的活,他就干,而且他就只干顯示的活,這就是單一職責,那么放到術語里面就是咱們設計的接口或者類,盡量遵循此原則,有什么好處賴?

  • 類的復雜性降低, 實現什么職責都有清晰明確的定義
  • 可讀性提高, 復雜性降低, 那當然可讀性提高了
  • 可維護性提高, 可讀性提高, 那當然更容易維護了
  • 變更引起的風險降低, 變更是必不可少的, 如果接口的單一職責做得好, 一個接口修
    改只對相應的實現類有影響, 對其他的接口無影響, 這對系統的擴展性、 維護性都有非常大
    的幫助

這一波好處摘自設計模式之禪,總結下來就是,看見這個接口所聲明的方法,你就知道功能都有什么,初學者或者初接手的人都可以很快融入到代碼中進行迭代和維護了。

然而道理是這個道理,但是具體在設計代碼的時候,還是要考慮到具體的應用下。用書中的話描述就是

單一職責原則提出了一個編寫程序的標準, 用“職責”或“變化原因”來衡量接口或
類設計得是否優良, 但是“職責”和“變化原因”都是不可度量的, 因項目而異, 因環境而異

code

OK,結合上面說的小Demo,接下來來一杯Java解解渴

interface IScreenDisplay {/*** display image on screen** @param image*/void displayImage(String image);/*** display a text on screen** @param text*/void displayText(String text);/*** display a video on screen** @param video*/void displayVideo(String video); }

上來就是我們的小接口,屏幕顯示,干什么玩意呢?自行翻譯不謝~

然后是我們的實現類

static class Phone implements IScreenDisplay {@Overridepublic void displayImage(String image) {System.out.println("displayImage:" + image);}@Overridepublic void displayText(String text) {System.out.println("displayText:" + text);}@Overridepublic void displayVideo(String video) {System.out.println("displayVideo:" + video);} }

實現類就是干具體的活了,國際慣例sout輸出~

public static void main(String[] args) {IScreenDisplay phone = new Phone();phone.displayImage("ic_launcher.png");phone.displayText("Hello Done!");phone.displayVideo("xiao huang pian.avi"); }

這一套降龍十八掌走下來結果:

displayImage:ic_launcher.png displayText:Hello Done! displayVideo:xiao huang pian.avi

多囂張?多簡單?當后期review一看,咱們的接口告訴你只負責顯示,可以顯示文字,圖片和視頻,至于什么時候顯示,顯示什么內容,那我不管,我就顯示,唯一讓我引起變化的是什么?當然是內容咯~

總結一番便是

接口一定要做到單一職責, 類的設計盡量做到只有一個
原因引起變化

里氏替換原則(LiskovSubstitution Principle, LSP)

此原則相較于上面的單一職責,要復雜一些,這里引用書中的原話(一定要認真閱讀),后面會通過白話做出相關解釋哈~

首先要理解的是繼承的特點

  • 代碼共享, 減少創建類的工作量, 每個子類都擁有父類的方法和屬性
  • 提高代碼的重用性
  • 子類可以形似父類,但又異于父類, “龍生龍, 鳳生鳳,老鼠生來會打洞”是說子擁有父的“種”,“世界上沒有兩片完全相同的葉子”是指明子與父的不同
  • 提高代碼的可擴展性,實現父類的方法就可以“為所欲為”了,君不見很多開源框架的擴展接口都是通過繼承父類來完成的
  • 提高產品或項目的開放性

上面是優點,下面是缺點

  • 繼承是侵入性的。 只要繼承, 就必須擁有父類的所有屬性和方法
  • 降低代碼的靈活性。子類必須擁有父類的屬性和方法,讓子類自由的世界中多了些約束
  • 增強了耦合性。當父類的常量、變量和方法被修改時,需要考慮子類的修改,而且在缺乏規范的環境下,這種修改可能帶來非常糟糕的結果——大段的代碼需要重構

定義

  • 看不懂,繞口定義:

If for each object o1 of type S there is an object o2 oftype T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 issubstituted for o2 then S is a subtype of T.(如果對每一個類型為S的對象o1, 都有類型為T的對象o2, 使得以T定義的所有程序P在所有的對象o1都代換成o2時, 程序P的行為沒有發生變
化, 那么類型S是類型T的子類型。 )

  • 通俗易懂,親民定義:

Functions that use pointers or references to base classes must be able to useobjects of derived classes without knowing it.(所有引用基類的地方必須能透明地使用其子類的對象。 )

只要父類能出現的地方子類就可以出現, 而且
替換為子類也不會產生任何錯誤或異常, 使用者可能根本就不需要知道是父類還是子類。 但
是, 反過來就不行了, 有子類出現的地方, 父類未必就能適應。

里氏替換原則為良好的繼承定義了一個規范,一句簡單的定義包含了4層含義
  • 子類必須完全實現父類的方法

如果子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生“畸變”,則建議斷開父子繼承關系, 采用依賴、聚集、 組合等關系代替繼承。

  • 子類可以有自己的個性

向下轉型(downcast)是不安全的, 從里氏替換原則來看,就是有子類出現的地方父類未必就可以出現

  • 覆蓋或實現父類的方法時輸入參數可以被放大

里氏替換原則也要求制定一個契約, 就是父類或接口,這種設計方法也叫做Design by Contract(契約設計) ,與里氏替換原則有著異曲同工之妙。 契約制定了, 也就同時制定了前置條件和后置條件, 前置條件就是你要讓我執行,就必須滿足我的條件; 后置條件就是我執行完了需要反饋, 標準是什么。

  • 覆寫或實現父類的方法時輸出結果可以被縮小

采用里氏替換原則的目的就是增強程序的健壯性,版本升級時也可以保持非常好的兼容性。即使增加子類,原有的子類還可以繼續運行,每個子類對應不同的業務含義,使用父類作為參數,傳遞不同的子類完成不同的業務邏輯

白話方式總結一下上面的內容:
里氏替換原則更像是一種java繼承的規范用法,"extends"嘛,大家都用過的東西,可以這么用那么用隨便用,想用就用,但是寫完以后發現沒有卵用,用是用了,但是有什么奧妙還是不太清楚,為什么要用它?
咱們經常談到的一個詞:抽取,譬如說activity,這個活動要setContentView,那個活動也要setContentView,這個活動要上下文,那個活動也要上下文,誒~這時候我們就開始搞一個BaseActivity的東西,然后讓所有子類去重寫獲取布局ID,同時父類直接拿到自己的上下文對象,子類直接使用即可。

那么是不是我們的活動程序代碼邏輯無論ams(自行百度)怎么使,你創建的這個活動都好使,符合咱們android體系的健壯性,另外你想實現的黑科技是不是都可以在自己的活動里面去實現,當然這里的設計并不是完全符合里氏替換原則,姑且斷章取義,您就這么斷章取義的理解即可,另外如果老衲說的有什么不對的地方,歡迎大家指正批評~

還記得上面提到過的Java中繼承帶來的優缺點嗎?咱們的里氏替換原則就是一個“揚長避短”的做法,具體怎么搞?code一下見分曉

code

還是手機的例子,這次咱們不說屏幕,說品牌,先來個抽象手機

abstract class AbsPhone {/*** 使用*/public abstract void use(); }

臥槽,就那么簡單,抽象一個使用的方法~
OK,接下來是子類們

/*** 諾基亞*/ class NokiaPhone extends AbsPhone {@Overridepublic void use() {System.out.println("砸核桃 實用戶");} }/*** 錘子*/ class TPhone extends AbsPhone {@Overridepublic void use() {System.out.println("錘子 情懷戶");} }/*** 蘋果*/ class ApplePhone extends AbsPhone {private void ringing() {System.out.println("先讓蘋果特有鈴聲響一陣...嗚嗚嗚");}@Overridepublic void use() {ringing();System.out.println("蘋果 zhuang bi 專業戶");} }

很簡單,就是諾基亞,錘子和蘋果三個街機,這里并沒有任何對這些品牌的觀點,這是假借名義,傳輸知識用
再然后必須有一個使用者嘛

class Person {AbsPhone phone;String name;public Person(String name) {this.name = name;}public void setPhone(AbsPhone phone) {this.phone = phone;}void communicate() {System.out.println(name + "掏出手機了...");phone.use();} }

然后我們來用這些兄dei跑一把

public static void main(String[] args) {Person coke = new Person("庫克");coke.setPhone(new ApplePhone());Person laoluo = new Person("老羅");laoluo.setPhone(new TPhone());Person bill = new Person("比爾蓋茨");bill.setPhone(new NokiaPhone());coke.communicate();laoluo.communicate();bill.communicate(); }

sout

庫克掏出手機了... 先讓蘋果特有鈴聲響一陣...嗚嗚嗚 蘋果 zhuang bi 專業戶 老羅掏出手機了... 錘子 情懷戶 比爾蓋茨掏出手機了... 砸核桃 實用戶

爽哉,爽在哪里了?咱們的person對象只知道自己有個手機,這個手機能執行communicate操作,什么手機我不管,我只管用它。術語就是,邏輯代碼不管實現,只需要持有著抽象類,然后執行抽象類提供的方法即可,就算以后擴展了其他的子類,也不影響我之前的業務邏輯,子類完全繼承了父類,同時在不改變方法本身的邏輯下增添了自己的特色,同時也符合父類出現的地方,就可以替換成子類

請大家仔細咀嚼下面這兩段話:

采用里氏替換原則的目的就是增強程序的健壯性, 版本升級時也可以保持非常好的兼容性。 即使增加子類, 原有的子類還可以繼續運行。 在實際項目中, 每個子類對應不同的業務含義, 使用父類作為參數, 傳遞不同的子類完成不同的業務邏輯
對于基類中定義的所有子程序,用在它的任何一個派生類中時的含義都應該是相同的。這樣繼承才不會增加復雜度,基類才能真正被復用,而派生類也能夠在基類的基礎上增加新的行為。如果我們必須要不斷地思考不同派生類的實現在語義上的差異,繼承就只會增加復雜度了。

本著負責人的態度,老衲還是把樹立的這段話copy過來,望大家酌情使用參考:

在項目中,采用里氏替換原則時,盡量避免子類的“個性”,一旦子類有“個性”,這個子類和父類之間的關系就很難調和了,把子類當做父類使用,子類的“個性”被抹殺——委屈了點;把子類單獨作為一個業務來使用,則會讓代碼間的耦合關系變得撲朔迷離——缺乏類替換的標準

依賴倒置原則(Dependence Inversion Principle,DIP)

依賴倒置是什么鬼?純說感覺也說不明白,白話點來說就是各種注入,依賴的接口注入,就是咱們的接口不依賴實現,而具體的實現類去組裝這些接口,簡單粗暴的解釋就是,

面向接口編程(OOD)

祭出官方釋義就是:
High level modules should not depend upon low level modules.Both should depend upon
abstractions.Abstractions should not depend upon details.Details should depend upon abstractions

  • 高層模塊不應該依賴低層模塊, 兩者都應該依賴其抽象
  • 抽象不應該依賴細節
  • 細節應該依賴抽象

定義

你好,我是勤勞的搬運工

抽象就是指接口或抽象類,兩者都是不能直接被實例化的;細節就是實現類,實現接口或繼承抽象類而產生的類就是細節,其特點就是可以直接被實例化,也就是可以加上一個關鍵字new產生一個對象

  • 模塊間的依賴通過抽象發生, 實現類之間不發生直接的依賴關系, 其依賴關系是通過接口或抽象類產生的
  • 接口或抽象類不依賴于實現類
  • 實現類依賴接口或抽象類

那么采用依賴倒置的優勢在哪里?就是

減少類間的耦合性, 提高系統的穩定性,降低并行開發引起的風險,提高代碼的可讀性和可維護性,穩定性較高的設計,在周圍環境頻繁變化的時候,依然可以做到“我自巋然不動”

那么要如何遵循依賴倒置原則去設計代碼呢?首先,我們的始終遵循

抽象不依賴細節
在新增加低層模塊時,只修改了業務場景類,也就是高層模塊,對其他低層模塊如Driver類不需要做任何修改,業務就可以運行,把“變更”引起的風險擴散降到最低
如果兩個類直接存在依賴關系,那么連接他們之間的橋梁就是接口,不依賴具體的低層模塊
抽象是對實現的約束,對依賴者而言,也是一種契約,不僅僅約束自己,還同時約束自己與外部的關系,其目的是保證所有的細節不脫離契約的范疇,確保約束雙方按照既定的契約(抽象)共同發展,只要抽象這根基線在,細節就脫離不了這個圈圈,始終讓你的對象做到“言必信, 行必果”

常用依賴傳遞,只要做到抽象依賴,即使是多層的依賴傳遞也無所畏懼

code

依賴倒置原則的本質就是通過抽象(接口或抽象類) 使各個類或模塊的實現彼此獨立,
不互相影響, 實現模塊間的松耦合

  • 每個類盡量都有接口或抽象類, 或者抽象類和接口兩者都具備
    這是依賴倒置的基本要求, 接口和抽象類都是屬于抽象的, 有了抽象才可能依賴倒置。
  • 變量的表面類型盡量是接口或者是抽象類
  • 任何類都不應該從具體類派生
  • 盡量不要覆寫基類的方法(類間依賴的是抽象, 覆寫了抽象方法, 對依賴的穩定性會產生一定的影響)
  • 結合里氏替換原則使用(接口負責定義public屬性和方法, 并且聲明與其他對象的依賴關系,抽象類負責公共構造部分的實現,實現類準確的實現業務邏輯, 同時在適當的時候對父類進行細化)
    說了那么多,還是需要深刻的在代碼中多多運用“面向接口編程”

OK,接下來是我們的代碼背景,還是不要上面的手機例子了, 老衲也是寫吐了哈哈- -,這次是英雄聯盟,恭喜RNG!
主角是咱們的Uzi和香鍋打野
首先聲明英雄和召喚師的接口

interface IHero {void attack();}interface IPlay {void play();}

緊接著是咱們的兩個英雄低層接口

static class Xiazi implements IHero {@Overridepublic void attack() {System.out.println("瞎子,我用雙手,成就你的夢想");} }static class VN implements IHero {@Overridepublic void attack() {System.out.println("VN,黑夜也會怕我");} }

然后是咱們的上層player接口

static class ADPlayer implements IPlay {IHero hero;public ADPlayer(IHero hero) {this.hero = hero;}@Overridepublic void play() {hero.attack();} }static class AssistPlayer implements IPlay {IHero hero;public void setHero(IHero hero) {this.hero = hero;}@Overridepublic void play() {hero.attack();} }

ok,接下來爽一把
這里注意,咱們的uzi使用構造依賴注入方式,天生的AD,世界第一ADC
咱們的RNG圍繞下路戰術,所以香鍋就一個使命,保護下路,使用setter依賴注入

public static void main(String[] args) {ADPlayer uzi = new ADPlayer(new VN());AssistPlayer mlxg = new AssistPlayer();mlxg.setHero(new Xiazi());uzi.play();mlxg.play(); }輸出 VN,黑夜也會怕我 瞎子,我用雙手,成就你的夢想

再次恭喜RNG集中賽冠軍,不知為何,老衲看小花生就是一臉不爽

接口隔離原則(Interface Splite Principle)

接口 隔離 其實從字面上就能很好的理解,先不看書,簡單從字面上理解一下這個原則

接口:interface(Java 中interface關鍵字修飾,只能在其中聲明方法/接口和靜態變量)
類:class,對外提供的public方法,從外向內看,這其實也是一種接口

隔離:隔離結合單一職責來看,隔離的基礎首先盡可能保證接口的定義符合單一職責原則,依據業務劃分出來的接口功能進行進一步進行拆分細分,類不要去依賴那些他用不到的接口,不然沒有意義啊,依賴那么多搞什么,說白了就是對接口根據依賴關系進行一波“抽取”的騷操作

定義

ok,山寨白話解釋完畢,下面來對下文檔,接口描述正確,我們就看一下隔離的解釋

  • Clients should not be forced to depend upon interfaces that they don’t use.(客戶端不應該依賴它不需要的接口。)
  • The dependency of one class to another one should depend on the smallest possible interface.(類間的依賴關系應該建立在最小的接口上。)

好吧,在下認為這個解釋還不如白話來的直接明了,類間的依賴關系是什么?其實對于接口最直接的定義便是類之間進行通信使用的,那么既然他們之間進行通信,那么兩個類之間就存在了耦合關系,耦合達到最低要怎么做?就是盡量使這個耦合接口簡單明了,那么搬出書上的解釋:

建立單一接口,不要建立臃腫龐大的接口。再通俗一點講:接口盡量細化,同時接口中的方法盡量少

code

代碼背景還是咱們LOL,之前是hero,那今天就換成NPC吧
首先來兩個接口,分別是魔法攻擊和物理攻擊,也就是咱們的接口隔離

public interface IMagicAttack {void magicAttack();}public interface IPhysicalAttack {void physicalAttack(); }

接下來是兩個咱們的法拉利和遠程兵,分別實現魔法和物理攻擊接口

public static class YuanChenBing implements IMagicAttack {@Overridepublic void magicAttack() {System.out.println("遠程兵,用魔法攻擊轟你家大燈");}}public static class FaLaLi implements IPhysicalAttack {@Overridepublic void physicalAttack() {System.out.println("法拉利炮車,用大炮物理攻擊轟你家大燈");} }

最后登場的是大龍,大龍的話就比較囂張了,必須兩個攻擊的接口都實現

public static class DaLong implements IMagicAttack, IPhysicalAttack {@Overridepublic void magicAttack() {System.out.println("大龍向你吐了一口魔法濃痰");}@Overridepublic void physicalAttack() {System.out.println("大龍用尾巴懟你");} }

接下來run一把瞅瞅

public static void main(String[] args) {DaLong daLong = new DaLong();daLong.magicAttack();daLong.physicalAttack();YuanChenBing yuanChenBing = new YuanChenBing();yuanChenBing.magicAttack();FaLaLi faLaLi = new FaLaLi();faLaLi.physicalAttack();}大龍向你吐了一口魔法濃痰 大龍用尾巴懟你 遠程兵,用魔法攻擊轟你家大燈 法拉利炮車,用大炮物理攻擊轟你家大燈

代碼擼完了,接下來搬出書上的總結,說的灰常準確,請大家注意

  • 接口要盡量小,根據接口隔離原則拆分接口時,首先必須滿足單一職責原則

  • 接口要高內聚,什么是高內聚?高內聚就是提高接口、類、模塊的處理能力,減少對外的交互

  • 定制服務,一個系統或系統內的模塊之間必然會有耦合,有耦合就要有相互訪問的接口(并不一定就是Java中定義的Interface,也可能是一個類或單純的數據交換),我們設計時就需要為各個訪問者(即客戶端)定制服務,什么是定制服務?定制服務就是單獨為一個個體提供優良的服務

  • 接口設計是有限度的,接口的設計粒度越小,系統越靈活,這是不爭的事實。但是,靈活的同時也帶來了結構的復雜化,開發難度增加,可維護性降低,這不是一個項目或產品所期望看到的,所以接口設計一定要注意適度

  • 一個接口只服務于一個子模塊或業務邏輯

  • 通過業務邏輯壓縮接口中的public方法,接口時常去回顧,盡量讓接口達到“滿身筋骨肉”,而不是“肥嘟嘟”的一大堆方法

  • 已經被污染了的接口,盡量去修改,若變更的風險較大,則采用適配器模式進行轉化處理

  • 了解環境,拒絕盲從。每個項目或產品都有特定的環境因素,別看到大師是這樣做的你就照抄。千萬別,環境不同,接口拆分的標準就不同。深入了解業務邏輯

迪米特法則(Law of Demeter, LoD)

迪米特法則主要表達的是當類與類之間產生耦合的情況下,類對外公布的方法將遵循怎樣的規則,其實說白了就是當前類持有的耦合類,那么當前類只關心自己要調用的方法,具體內部有怎樣的實現則不關心,這些不關心的方法或者變量都與我無關,這樣寫有什么好處呢?相當于以后當實現邏輯發生了變化,但是結果不變,我們只需要更改耦合類的內部實現即可,外部無需改動,就是private,protect等修飾符的使用~

定義

一個對象應該對其他對象有最少的了解。通俗地講,

一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何復雜都和我沒關系,那是你的事情,我就知道你提供的這么多public方法,我就調用這么多,其他的我一概不關心。

類之間的低耦合要求:

  • 類與類之間的關系是建立在類間的,而不是方法間,因此一個方法盡量不引入一個類中不存在的對象
  • 迪米特法則要求類“羞澀”一點,盡量不要對外公布太多的public方法和非靜態的public變量,盡量內斂,多使用private、package-private、protected等訪問權限
  • 如果一個方法放在本類中,既不增加類間關系,也對本類不產生負面影響,那就放置在本類中
  • 迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了以后,類的復用率才可以提高。其要求的結果就是產生了大量的中轉或跳轉類,導致系統的復雜性提高,同時也為維護帶來了難度。讀者在采用迪米特法則時需要反復權衡,既做到讓結構清晰,又做到高內聚低耦合

code

慣例做一個代碼背景介紹
相信大家都聽說過five five open這位兄臺,那么咱們就以這位玩家作為咱們此次Demo的主角來編寫代碼

注意,不對此人自任何評判,純粹是講解需要,謝謝

首先咱們來一波接口,聲明GB該實現的方法,包括什么自動攻擊啊,自動躲避技能啊什么的

public interface IShellMethod {void autoAttack();void stopAutoAttack();void autoDucking();void stopAutoDucking();void autoUseSkill();void stopAutoUseSkill();void autoChangeChangeEquipage();void stopAutoChangeChangeEquipage(); }

接下來就是咱們的RMB玩家需要持有的接口就比較簡單,遵循迪特米法則,就一個啟動和停止

public interface IUseShell {void enableShell();void disableShell(); }

然后是咱們的腳本實現類,此處同時實現RMB玩家的接口,簡單包裝一下,注意這里增加了一個內部方法為計算躲避最佳路線,符合咱們迪特米法則,外部不關心內部的實現邏輯

private static class ShellImpl implements IShellMethod, IUseShell {@Overridepublic void autoAttack() {System.out.println("auto attack hero or NPC");}@Overridepublic void stopAutoAttack() {System.out.println("stop auto attack");}@Overridepublic void autoDucking() {calculateDuckingPath();System.out.println("auto dodge attacks");}private void calculateDuckingPath() {System.out.println("calculate best ducking path!");}@Overridepublic void stopAutoDucking() {System.out.println("stop auto dodge attacks");}@Overridepublic void autoUseSkill() {System.out.println("auto use hero's skill");}@Overridepublic void stopAutoUseSkill() {System.out.println("stop auto use hero's skill");}@Overridepublic void autoChangeChangeEquipage() {System.out.println("auto buy best equipage");}@Overridepublic void stopAutoChangeChangeEquipage() {System.out.println("stop auto buy best equipage");}@Overridepublic void enableShell() {this.autoAttack();this.autoDucking();this.autoUseSkill();this.autoChangeChangeEquipage();}@Overridepublic void disableShell() {this.stopAutoAttack();this.stopAutoDucking();this.stopAutoUseSkill();this.stopAutoChangeChangeEquipage();} }

最后則是我們用戶的包裝類,持有RMB接口對象即可

private static class ShellUser {private IUseShell useShell;public ShellUser() {useShell = new ShellImpl();}public void startGB() {System.out.println("開始上分");useShell.enableShell();}public void stopGB() {System.out.println("臥槽,對面要舉報我");useShell.disableShell();} }

run一把爽一下

ShellUser lubenwei = new ShellUser(); lubenwei.startGB(); lubenwei.stopGB();... 開始上分 auto attack hero or NPC calculate best ducking path! auto dodge attacks auto use hero's skill auto buy best equipage 臥槽,對面要舉報我 stop auto attack stop auto dodge attacks stop auto use hero's skill stop auto buy best equipage

好了,通過這個小Demo大家也可以大致輕松愉快的了解到迪特米法則,迪特米法則不同于上面說的幾個法則,更注重類間規范,是以后耦合類間的書寫規范,很多設計模式也是遵循的這些法則組合完成的設計。

開閉原則(Open-Closed Principle, OCP)

不管你是Java開發還是Android開發,只要你曾經或者正在學習的路上,那么或多或少會在網上看到這樣的一句話:“對修改關閉,對擴展開放”,OK, what is mean ?

定義

一個軟件實體應該通過擴展來實現變化,而不是通過修改已有的代碼來實現變化。軟件實體包括以下幾個部分:

  • 項目或軟件產品中按照一定的邏輯規則劃分的模塊
  • 抽象和類
  • 方法

一個軟件產品只要在生命期內,都會發生變化,既然變化是一個既定的事實,我們就應該在設計時盡量適應這些變化,以提高項目的穩定性和靈活性,真正實現“擁抱變化”。

對于突如其來的變化,我們不是以修改原有代碼來適配新的變化,而是通過增寫擴展的方式來應對這個新變化。

書上為這些變化做了一個歸類,如下:

  • 邏輯變化
    只變化一個邏輯,而不涉及其他模塊,比如原有的一個算法是ab+c,現在需要修改為ab*c,可以通過修改原有類中的方法的方式來完成,前提條件是所有依賴或關聯類都按照相同的邏輯處理

  • 子模塊變化
    一個模塊變化,會對其他的模塊產生影響,特別是一個低層次的模塊變化必然引起高層模塊的變化,因此在通過擴展完成變化時,高層次的模塊修改是必然的

  • 可見視圖變化

注意:

在業務規則改變的情況下高層模塊必須有部分改變以適應新業務,改變要盡量地少,防止變化風險的擴散。開閉原則對擴展開放,對修改關閉,并不意味著不做任何修改,低層模塊的變更,必然要有高層模塊進行耦合,否則就是一個孤立無意義的代碼片段

項目開發、重構、測試、投產、運維,其中的重構可以對原有的設計和代碼進行修改,運維盡量減少對原有代碼的修改,保持歷史代碼的純潔性,提高系統的穩定性。

書中對開閉原則做了一個非常好的總結,這里就搬過來了:

開閉原則是最基礎的一個原則,前五章節介紹的原則都是開閉原則的具體形態,也就是說前五個原則就是指導設計的工具和方法,而開閉原則才是其精神領袖。換一個角度來理解,依照Java語言的稱謂,開閉原則是抽象類,其他五大原則是具體的實現類

那么開閉原則又會帶來哪些好處呢?

  • 首先是“測試”,在擴展的基礎上,測試只需要測試新增加的接口就可以,無需對之前已經穩定可靠的代碼進行重復測試
  • 通過縮小業務邏輯粒度從而達到代碼復用的作用,從原子邏輯組合成業務邏輯,那么原子的拼接組合自然而然能夠產生新的業務邏輯,復用的是久經測試的穩定代碼,效率得到很大提升
  • 軟件更多的工作其實是在維護中,我們寫代碼的目的也是為了今后更好的迭代和維護來對代碼進行架構,那么在迭代的過程中,開發人員可以盡可能的少參與之前代碼的觀看和理解就能在原有的基礎上進行功能的擴展,那么這樣的代碼才是良性的代碼,相信同學們對閱讀之前的代碼也是或多或少的有過經歷,能深刻體會其中“奧妙”。
  • 代碼的設計并不能僅僅局限于當前的需求,而是要考慮到將來的擴展和可能的變化,預留出擴展的余地

OK,說了這么多關于開閉原則的好處,那么接下來應該提到的是開閉原則的使用。

老規矩,這次的code主角是王者榮耀游戲商城

public interface IGameHero {int getHeroPrice();int getDressUpPrice();String getName(); }

怒上3個接口規定商城售賣英雄的行為,也是定義實體行為,分別是獲取英雄價格,獲取英雄皮膚價格,獲取英雄名字

然后是英雄接口實現類

public static class Hero implements IGameHero {private int mPrice;private int mDressPrice;private String mName;public Hero(int mPrice, int mDressPrice, String mName) {this.mPrice = mPrice;this.mDressPrice = mDressPrice;this.mName = mName;}@Overridepublic int getHeroPrice() {return mPrice;}@Overridepublic int getDressUpPrice() {return mDressPrice;}@Overridepublic String getName() {return mName;}@Overridepublic String toString() {return "英雄:" + getName() + "\t英雄價格:" + getHeroPrice() + "\t皮膚價格:" + getDressUpPrice();} }

接下來上商店邏輯類,這里就簡單寫下,通俗易懂

public static class GameStore {private List<IGameHero> heroes;public GameStore() {this.heroes = new ArrayList<>();heroes.add(new Hero(13888, 888, "白起"));heroes.add(new Hero(10888, 388, "莊周"));heroes.add(new Hero(13888, 288, "程咬金"));heroes.add(new Hero(18888, 688, "貂蟬"));}public List<IGameHero> getHeroes() {return heroes;} }

意思通俗易懂,大家自行參閱這些中文式代碼哈~
然后就是我們的main咯~

List<IGameHero> heroes = gameStore.getHeroes();System.out.println("-----------進入商店----------");final String storeMessage = "售:";for (IGameHero hero : heroes) {System.out.println(storeMessage + hero.toString()); }//輸出 //-----------進入商店---------- //售:英雄:白起 英雄價格:13888 皮膚價格:888 //售:英雄:莊周 英雄價格:10888 皮膚價格:388 //售:英雄:程咬金 英雄價格:13888 皮膚價格:288 //售:英雄:貂蟬 英雄價格:18888 皮膚價格:688

這就是完美的構建了我們的某榮耀的簡單商城了
好了,接下來TX要出活動了,刺激消費,掙一波,咋整
很簡單,針對咱們的擴展開放原則,新建一個英雄實現接口類

public static class OffHero extends Hero {private float mDiscount = 1.0F;public OffHero(float discount, int price, int dressPrice, String name) {super(price, dressPrice, name);mDiscount = discount;}@Overridepublic int getHeroPrice() {int ret = (int) (super.getHeroPrice() * mDiscount);return ret;}@Overridepublic int getDressUpPrice() {int ret = (int) (super.getDressUpPrice() * mDiscount);return ret;}@Overridepublic String toString() {return "折扣英雄:" + super.getName() +"\t英雄價格:" + super.getHeroPrice() + ",折扣價格:" + (int) (super.getHeroPrice() * mDiscount)+ "\t皮膚價格:" + super.getDressUpPrice() + ",折扣價格:" + (int) (super.getDressUpPrice() * mDiscount);} }

其實也就是集成原有的英雄類,增加一個折扣屬性,重寫獲取英雄價格和皮膚價格
然后再略微動一下商城類,增加商城類的方法,這一步也是不可避免的,上層增加實體的獲取和實現這些代碼是必須要寫的。

public void startSale() {System.out.println("商店開始活動,88折");this.heroes.clear();heroes.add(new OffHero(0.88F, 13888, 888, "白起"));heroes.add(new OffHero(0.88F, 10888, 388, "莊周"));heroes.add(new OffHero(0.88F, 13888, 288, "程咬金"));heroes.add(new OffHero(0.88F, 18888, 688, "貂蟬")); }public void resetPrice() {System.out.println("商店折扣活動截止");this.heroes.clear();heroes.add(new Hero(13888, 888, "白起"));heroes.add(new Hero(10888, 388, "莊周"));heroes.add(new Hero(13888, 288, "程咬金"));heroes.add(new Hero(18888, 688, "貂蟬")); }

增加兩個方法,折扣為88折,你買不了吃虧,買不了上當~
然后在main里面進行調用

gameStore.startSale(); heroes = gameStore.getHeroes(); for (IGameHero hero : heroes) {System.out.println(storeMessage + hero.toString()); } gameStore.resetPrice(); heroes = gameStore.getHeroes(); for (IGameHero hero : heroes) {System.out.println(storeMessage + hero.toString()); }//log //商店開始活動,88折 //售:折扣英雄:白起 英雄價格:13888,折扣價格:12221 皮膚價格:888,折扣價格:781 //售:折扣英雄:莊周 英雄價格:10888,折扣價格:9581 皮膚價格:388,折扣價格:341 //售:折扣英雄:程咬金 英雄價格:13888,折扣價格:12221 皮膚價格:288,折扣價格:253 //售:折扣英雄:貂蟬 英雄價格:18888,折扣價格:16621 皮膚價格:688,折扣價格:605 //商店折扣活動截止 //售:英雄:白起 英雄價格:13888 皮膚價格:888 //售:英雄:莊周 英雄價格:10888 皮膚價格:388 //售:英雄:程咬金 英雄價格:13888 皮膚價格:288 //售:英雄:貂蟬 英雄價格:18888 皮膚價格:688

顯而易見,很輕松的就達到了折扣的目的,這就是所謂的抽象原則的擁抱開放,關閉修改

總結

以上是生活随笔為你收集整理的设计原则——设计模式基础的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。