JAVA初探設(shè)計(jì)模式的六大原則
前言
我想用貼近生活的語句描述一下自己對六種原則的理解。也就是不做專業(yè)性的闡述,而是描述一種自己學(xué)習(xí)后的理解和感受,因?yàn)槟芰σ话愣宜接邢?,也許舉的例子不盡妥當(dāng),還請諒解原本我是想用JavaScript編寫的,但是JavaScript到現(xiàn)在還沒有提出接口的概念,而用TypeScript寫又感覺普及度還不算特別高,所以還是決定用Java語言編寫
首先要提的是:六大原則的靈魂是面向接口,以及如何合理地運(yùn)用接口
P1.單一職責(zé)原則(Single Responsibility Principle)
應(yīng)該有且僅有一個(gè)原因引起類的變更(There should never be more than one reason for a class to change)。為了達(dá)到這個(gè)目標(biāo),我們需要對類和業(yè)務(wù)邏輯進(jìn)行拆分。劃分到合適的粒度,讓這些各自執(zhí)行單一職責(zé)的類,各司其職。讓每個(gè)類盡量行使單一的功能,實(shí)現(xiàn)“高內(nèi)聚”,這個(gè)結(jié)果也使得類和類之間不會(huì)有過多冗余的聯(lián)系,從而“低耦合”。比如我們現(xiàn)在有了這樣一個(gè)類
public class People {
public void playCnBlogs () {
System.out.println("刷博客");
}
public void doSports () {
System.out.println("打乒乓球");
}
public void work () {
System.out.println("工作");
}
}
現(xiàn)在看起來有點(diǎn)混亂,因?yàn)檫@個(gè)類里面混合了三個(gè)職責(zé):
- 刷博客園,這是博主的職責(zé)
- 打乒乓球,這是業(yè)余運(yùn)動(dòng)愛好者的職責(zé)
- 工作,這是“普普通通上班族”的職責(zé)
OK,正如你所見,既然我們要遵循單一職責(zé),那么怎么做呢?當(dāng)然是要拆分了我們要根據(jù)接口去拆,拆分成三個(gè)接口去約束People類(不是把People類拆了哈)
// 知乎er
public interface Blogger {
public void playCnBlogs();
}
// 上班族
public interface OfficeWorkers {
public void work();
}
// 業(yè)余運(yùn)動(dòng)愛好者
public interface AmateurPlayer {
public void doSports();
}
然后在People中繼承這幾個(gè)接口
public class People implements Blogger,AmateurPlayer,OfficeWorkers{
public void playCnBlogs () {
System.out.println("刷博客園");
}
public void doSports () {
System.out.println("打乒乓球");
}
public void work () {
System.out.println("工作");
}
}
最后創(chuàng)建實(shí)例運(yùn)行一下
public class Index {
public static void main (String args []) {
People people = new People();
Blogger blogger = new People();
blogger.playCnBlogs(); // 輸出:刷博客園
OfficeWorkers workers = new People();
workers.work(); // 輸出: 工作
AmateurPlayer players = new People();
players.doSports(); // 輸出:打乒乓球
}
}
備注:這個(gè)原則不是死的,而是活的,在實(shí)際開發(fā)中當(dāng)然還要和業(yè)務(wù)相結(jié)合,不會(huì)純粹為了理論貫徹單一職責(zé),就像數(shù)據(jù)庫開發(fā)時(shí)候,不會(huì)完全遵循“三大范式”,而是允許一定冗余的
P2.里氏替換原則(liskov substitution principle)
里氏替換原則,一種比較好的理解方式是: 所有引用基類的地方必須能透明地使用其子類的對象。 換句話說,子類必須完全實(shí)現(xiàn)父類的功能。凡是父類出現(xiàn)的地方,就算完全替換成子類也不會(huì)有什么問題。以上描述來自《設(shè)計(jì)模式之禪》,剛開始看的時(shí)候我有些疑惑,因?yàn)橐婚_始覺得:只要繼承了父類不都可以調(diào)用父類的方法嗎?為什么還會(huì)有里氏替換所要求的:子類必須完全實(shí)現(xiàn)父類的功能呢, 難不成繼承的子類還可以主動(dòng)“消除”父類的方法?還真可以,請看
父類
public abstract class Father {
// 認(rèn)真工作
public abstract void work();
// 其他方法
}
子類
public class Son extends Father {
@Override
public void work() {
// 我實(shí)現(xiàn)了爸爸的work方法,旦我什么也不做!
}
}
子類雖然表面上實(shí)現(xiàn)了父類的方法,但是他實(shí)際上并沒有實(shí)現(xiàn)父類要求的邏輯。里氏替換原則要求我們避免這種“塑料父子情”,如果出現(xiàn)子類不得不脫離父類方法范圍的情況, 采取其他方式處理,詳情參考《設(shè)計(jì)模式之禪》
(其實(shí)個(gè)人覺得《禪》的作者其實(shí)講的“父類”其實(shí)著重指的是抽象類)
P3.依賴倒置原則 (dependence inversion principle)
很多文章闡述依賴倒置原則都會(huì)闡述為三個(gè)方面
- 高層的模塊不應(yīng)該依賴于低層的模塊,這兩者都應(yīng)該依賴于其抽象
- 抽象不應(yīng)該依賴細(xì)節(jié)
- 細(xì)節(jié)應(yīng)該依賴抽象
換句話說, 高層次的類不應(yīng)該依賴于,或耦合于低層次的類,相反,這兩者都應(yīng)該通過相關(guān)的接口去實(shí)現(xiàn)。要面向接口編程,而不是面向?qū)崿F(xiàn)編程,所以編程的時(shí)候并不是按照符合我們邏輯思考的“依賴關(guān)系”去編程掉的,這種不符,就是依賴倒置舉個(gè)例子,類好比是道德,接口好比是法律。道德呢,有上層的也有下層的,春秋時(shí)代,孔圣人提出了上層道德理論:“仁”的思想,并進(jìn)一步細(xì)化為低層道德理論:“三綱五常”(高層模塊和底層模塊),想要以此規(guī)約眾生,實(shí)現(xiàn)天下大同。可是奈何民眾的道德終究還是靠不?。]有接口約束的類,可能被混亂修改),何況道德標(biāo)準(zhǔn)是會(huì)隨物質(zhì)經(jīng)濟(jì)的變化而變化的,孔子時(shí)代和我們今天的已經(jīng)大有不同了。(類可能會(huì)發(fā)生變化)所以才需要法律來進(jìn)一步框定和要求道德。(我們用接口來約束和維護(hù)“類”,就好比用法律來維護(hù)和規(guī)約道德一樣。)假如未來道德倫理的標(biāo)桿發(fā)生了變化,肯定是先修繕法律,然后再次反向規(guī)制和落實(shí)道德(面向接口編程,而不是面向?qū)崿F(xiàn)編程)。我們看下下面沒有遵循依賴倒置原則的代碼是怎樣的,我們設(shè)計(jì)了兩個(gè)類:Coder類和Linux類,并且讓它們之間產(chǎn)生交互:Coder對象的develop方法接收Linux對象并且輸出系統(tǒng)名
// 底層模塊1:開發(fā)者
public class Coder {
public void develop (Linux linux) {
System.out.printf("開發(fā)者正在%s系統(tǒng)上進(jìn)行開發(fā)%n",linux.getSystemName());
}
}
// 底層模塊2:Linux操作系統(tǒng)
public class Linux {
public String name;
public Linux(String name){
this.name = name;
}
public String getSystemName () {
return this.name;
}
}
// 高層模塊
public class Index {
public static void main (String args []) {
Coder coder = new Coder();
Linux ubuntu = new Linux("ubuntu系統(tǒng)"); // ubuntu是一種linux操作系統(tǒng)
coder.develop(ubuntu);
}
}
輸出
開發(fā)者正在ubuntu系統(tǒng)系統(tǒng)上進(jìn)行開發(fā)
但是我們能發(fā)現(xiàn)其中的問題:
操作系統(tǒng)不僅僅有Linux家族,還有Windows家族,如果我們現(xiàn)在需要讓開發(fā)者在windows系統(tǒng)上寫代碼怎么辦呢? 我們可能要新建一個(gè)Windows類,但是問題來了,Code.develop方法的入?yún)?shù)類型是Linux,這樣以來改造就變得很麻煩。讓我們利用依賴倒置原則改造一下,我們定義OperatingSystem接口,將windows/Linux抽象成操作系統(tǒng),這樣,OperatingSystem類型的入?yún)⒕涂梢越邮誛indows或者Linux類型的參數(shù)了
// 程序員接口
public interface Programmer {
public void develop (OperatingSystem OS);
}
// 操作系統(tǒng)接口
public interface OperatingSystem {
public String getSystemName ();
}
// 低層模塊:Linux操作系統(tǒng)
public class Linux implements OperatingSystem{
public String name;
public Linux (String name) {
this.name = name;
}
@Override
public String getSystemName() {
return this.name;
}
}
// 低層模塊:Window操作系統(tǒng)
public class Window implements OperatingSystem {
String name;
public Window (String name) {
this.name = name;
}
@Override
public String getSystemName() {
return this.name;
}
}
// 低層模塊:開發(fā)者
public class Coder implements Programmer{
@Override
public void develop(OperatingSystem OS) {
System.out.printf("開發(fā)者正在%s系統(tǒng)上進(jìn)行開發(fā)%n",OS.getSystemName());
}
}
// 高層模塊:測試用
public class Index {
public static void main (String args []) {
Programmer coder = new Coder();
OperatingSystem ubuntu = new Linux("ubuntu系統(tǒng)"); // ubuntu是一種linux操作系統(tǒng)
OperatingSystem windows10 = new Window("windows10系統(tǒng)"); // windows10
coder.develop(ubuntu);
coder.develop(windows10);
}
}
雖然接口的加入讓代碼多了一些,但是現(xiàn)在擴(kuò)展性變得良好多了,即使有新的操作系統(tǒng)加入進(jìn)來,Coder.develop也能處理
P4. 接口隔離原則(interface segregation principle)
接口隔離原則的要求是:類間的依賴關(guān)系應(yīng)該建立在最小的接口上。這個(gè)原則又具體分為兩點(diǎn)
1.接口要足夠細(xì)化,當(dāng)然了,這會(huì)讓接口的數(shù)量變多,但是每個(gè)接口會(huì)具有更加明確的功能
2.在1的前提下,類應(yīng)該依賴于“最小”的接口上
舉個(gè)例子,中秋節(jié)其實(shí)只過了一個(gè)多月,現(xiàn)在假設(shè)你有一大盒“五仁月餅”想帶回家喂豬,但是無奈的是包包太小放不下,而且一盒沉重的月餅對瘦弱的你是個(gè)沉重的負(fù)擔(dān)。這個(gè)時(shí)候,我們可以把月餅盒子拆開,選出一部分自己需要(wei zhu)的月餅,放進(jìn)包包里就好啦,既輕便又靈活。還是上代碼吧,比如我們有這樣一個(gè)Blogger的接口,里面涵蓋了一些可能的行為。大多數(shù)博客用戶會(huì)保持友善,同時(shí)根據(jù)自己的專業(yè)知識認(rèn)真寫文章。但也有少數(shù)的人會(huì)把生活中的負(fù)面能量帶到網(wǎng)絡(luò)中
public interface Blogger {
// 認(rèn)真撰文
public void seriouslyWrite();
// 友好評論
public void friendlyComment();
// 無腦抬杠
public void argue();
// 鍵盤攻擊
public void keyboardAttack ();
}
我們發(fā)現(xiàn),這個(gè)接口可以進(jìn)一步拆分成兩個(gè)接口,分別命名為PositiveBlogger,NegativeBlogger。這樣,我們就把接口細(xì)化到了一個(gè)合理的范圍
public interface PositiveBlogger {
// 認(rèn)真撰文
public void seriouslyWrite();
// 友好評論
public void friendlyComment();
}
public interface NegativeBlogger {
// 無腦抬杠
public void argue();
// 鍵盤攻擊
public void keyboardAttack ();
}
>> 備注:妥善處理 單一職責(zé)原則 和 接口隔離原則的關(guān)系事實(shí)上,有兩點(diǎn)要說明一下
1.單一職責(zé)原則和接口隔離原則雖然看起來有點(diǎn)像,好像都是拆分,但是其實(shí)側(cè)重點(diǎn)是不一樣的,“職責(zé)”的粒度其實(shí)是比“隔離接口”的粒度要大的
2.基于1中闡述的原因,其實(shí) 單一職責(zé)原則 和 接口隔離原則是可能會(huì)產(chǎn)生沖突的,因?yàn)榻涌诟綦x原則要求粒度盡可能要細(xì),但是單一職責(zé)原則卻不同,它要求拆分既不能過粗,但也不能過細(xì),如果把原本單一職責(zé)的接口分成了“兩個(gè)0.5職責(zé)的接口”,那么這就是單一職責(zé)所不能允許的了。
3.當(dāng)兩者沖突時(shí),優(yōu)先遵循 單一職責(zé)原則
P5.迪米特原則 (law of demeter)
迪米特原則又叫最少知道原則,在實(shí)現(xiàn)功能的前提下,一個(gè)對象接觸的其他對象應(yīng)該盡可能少,也即類和類之間的耦合度要低。舉個(gè)例子,我們經(jīng)常說要“減少無效社交”,不要總是一昧的以交朋友的數(shù)量衡量自己的交際能力,否則會(huì)讓自己很累的,也會(huì)難以打理好復(fù)雜的人際關(guān)系。對于并不很外向的人,多數(shù)時(shí)候和自己有交集的朋友交往就可以了。我們看下代碼:有如下場景,現(xiàn)在你和你的朋友想要玩一個(gè)活動(dòng),也許是斗地主等游戲,這個(gè)時(shí)候需要再喊一個(gè)人,于是你讓你的朋友幫你再叫一個(gè)人,有代碼如下
// 我的直接朋友
public class MyFriend {
// 找他的朋友
public void findHisFriend (FriendOfMyFriend fof) {
System.out.println("這是朋友的朋友:"+ fof.name);
}
}
// 朋友的朋友,但不是我的朋友
public class FriendOfMyFriend {
public String name;
public FriendOfMyFriend(String name) {
this.name = name;
}
}
// 我
public class Me {
public void findFriend (MyFriend myFriend) {
System.out.println("我找我朋友");
// 注意這段代碼
FriendOfMyFriend fmf = new FriendOfMyFriend("陌生人");
myFriend.findHisFriend(fmf);
};
}
這時(shí)我們發(fā)現(xiàn)一個(gè)問題,你和你朋友的朋友并不認(rèn)識,但是他卻出現(xiàn)在了你的“找朋友”的動(dòng)作當(dāng)中(在findFriend方法內(nèi)),這個(gè)時(shí)候,我們認(rèn)為這違反了迪米特原則(最少知道原則),迪米特原則我們對于對象關(guān)系的處理,要減少“無效社交”,具體原則是
- 一個(gè)類只和朋友類交流,朋友類指的是出現(xiàn)在成員變量、方法的輸入輸出參數(shù)中的類
- 一個(gè)類不和陌生類交流,即沒有出現(xiàn)在成員變量、方法的輸入輸出參數(shù)中的類
所謂的“不交流”,就是不要在代碼里看到他們我們改造一下上面的代碼
// 我朋友
public class MyFriend {
public void findHisFriend () {
FriendOfMyFriend fmf = new FriendOfMyFriend("陌生人");
System.out.println("這是朋友的朋友:"+ fmf.name);
}
}
// 朋友的朋友,但不是我的朋友
public class FriendOfMyFriend {
public String name;
public FriendOfMyFriend(String name) {
this.name = name;
}
}
// 我
public class Me {
public void findFriend (MyFriend myFriend) {
System.out.println("我找我朋友");
myFriend.findHisFriend();
};
}
P6. 開閉原則(open closed principle)
開閉原則的意思是,軟件架構(gòu)要:對修改封閉,對擴(kuò)展開放舉個(gè)例子比如我們現(xiàn)在在玩某一款喜歡的游戲,A鍵攻擊,F(xiàn)鍵閃現(xiàn)。這個(gè)時(shí)候我們想,如果游戲能額外給我定制一款“K”鍵,殘血時(shí)解鎖從而一擊OK對手完成5殺,那豈不美哉,這就好比是“對擴(kuò)展開放”。但是呢,如果游戲突然搞個(gè)活動(dòng),把閃現(xiàn)/攻擊/技能釋放的鍵盤通通換個(gè)位置,給你一個(gè)“雙十一的驚喜”,這恐怕就給人帶來慘痛的回憶了。所以我們希望已有的結(jié)構(gòu)不要?jiǎng)?,也不能?dòng),要“對修改封閉”(本人不玩游戲,這些是自己查到的,如果錯(cuò)誤還請指正)
總結(jié)
1.原則不是死板的而是靈活的
2.一些原則其實(shí)是存在一定的沖突的,重要的是權(quán)衡,是掌握好度
3.六大原則是23種設(shè)計(jì)模式的靈魂,六大原則指導(dǎo)了設(shè)計(jì)模式,設(shè)計(jì)模式體現(xiàn)了六大原則
以上就是JAVA初探設(shè)計(jì)模式的六大原則的詳細(xì)內(nèi)容,更多關(guān)于JAVA設(shè)計(jì)模式六大原則的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java的非阻塞隊(duì)列ConcurrentLinkedQueue解讀
這篇文章主要介紹了Java的非阻塞隊(duì)列ConcurrentLinkedQueue解讀,在并發(fā)編程中,有時(shí)候需要使用線程安全的隊(duì)列,如果要實(shí)現(xiàn)一個(gè)線程安全的隊(duì)列有兩種方式:一種是使用阻塞算法,另一種是使用非阻塞算法,需要的朋友可以參考下2023-12-12
淺談一下maven優(yōu)缺點(diǎn)及使用和特點(diǎn)
這篇文章主要介紹了淺談一下maven優(yōu)缺點(diǎn)及使用和特點(diǎn),一個(gè)項(xiàng)目管理工具軟件,那么maven項(xiàng)目有什么優(yōu)缺點(diǎn)呢,讓我們一起來看看吧2023-03-03
Java 和 Javascript 的 Date 與 .Net 的 DateTime 之間的相互轉(zhuǎn)換
這篇文章主要介紹了Java 和 Javascript 的 Date 與 .Net 的 DateTime 之間的相互轉(zhuǎn)換的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Kotlin 基礎(chǔ)教程之注解與java中的注解比較
這篇文章主要介紹了Kotlin 基礎(chǔ)教程之注解與java中的注解比較的相關(guān)資料,需要的朋友可以參考下2017-06-06
Spring boot 整合CXF開發(fā)web service示例
這篇文章主要介紹了Spring boot 整合CXF開發(fā)web service示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05

