作者已經(jīng)搬遷去隔壁網(wǎng)站图张,也歡迎大家關(guān)注我們的寫作團隊:天星技術(shù)團隊垫挨。
前言
不知道是否有許多萌新跟我一樣韩肝,在看java源碼的時候,腦袋容易暈九榔。通常查一個方法哀峻,要跳幾個類出來,有些類動不動就上千行哲泊。像我這樣血氣方剛的少年剩蟀,哪靜得下心來理解這么多結(jié)構(gòu)復雜的代碼!還不如看點番劇切威,喝點快樂肥宅水育特!
看吧,不知道該如何下手去啃源碼先朦。不看吧缰冤,心里也急得很≡海總不能一直這樣放著吧棉浸,既然選擇了做這行, 還是得好好學習刺彩,畢竟面向工資編程迷郑。于是在某位大神的指導下,推薦我先學習設(shè)計模式迂苛。okk三热!那接下來我們就一起來學習設(shè)計模式鼓择!
什么是設(shè)計模式
- 設(shè)計模式(Design pattern)代表了最佳的實踐三幻,通常被有經(jīng)驗的面向?qū)ο蟮能浖_發(fā)人員所采用。
- 設(shè)計模式是軟件開發(fā)人員在軟件開發(fā)過程中面臨的一般問題的解決方案呐能。這些解決方案是眾多軟件開發(fā)人員經(jīng)過相當長的一段時間的試驗和錯誤總結(jié)出來的念搬。
- 設(shè)計模式是一套被反復使用的、多數(shù)人知曉的摆出、經(jīng)過分類編目的朗徊、代碼設(shè)計經(jīng)驗的總結(jié)
為何要學習設(shè)計模式
- 別人都學你不學,是想當咸魚嗎偎漫?大佬都懂你不懂爷恳,不想混進大佬圈裝逼了?
- 當你用模式描述的時候象踊,其他開發(fā)人員很容易知道你對設(shè)計的想法温亲。
- 使用模式談?wù)撥浖到y(tǒng)棚壁,可以讓你保持在設(shè)計層次上,而不會壓低到對象與類這種瑣碎的事情上栈虚。
- 當用模式名稱交流時袖外,你們之間交流的不只是模式名稱,而是一整套模式背后所象征的質(zhì)量魂务,特性曼验,約束。
設(shè)計原則
提倡使用設(shè)計模式的根本原因是為了代碼復用粘姜,增加可維護性△拚眨現(xiàn)在被命名的23種設(shè)計模式就是遵守了以下六大設(shè)計原則,才達到了代碼復用孤紧,增加可維護性的目的颖杏。
-
單一職責原則:
There should never be more than one reason for a class to change.
不要存在多于一個導致類變更的原因,一個類只承擔一個職責 -
開閉原則:
Software entities like classes,modules and functions should be open for extension but closed for modifications.
類坛芽、模塊留储、函數(shù),對擴展是開放的咙轩,對修改是封閉的获讳。 -
里式替換原則:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
子類可以擴展父類的功能,但不能改變父類原有的功能 -
迪米特原則(最少知識原則):
Only talk to you immediate friends.
盡量減少對象之間的交互活喊,從而減小類之間的耦合丐膝。 -
接口隔離原則:
The dependency of one class to another one should depend on the smallest possible interface.
不要對外暴露沒有實際意義的接口。 -
依賴倒置原則:
High level modules should not depends upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
核心思想:面向接口編程
走進設(shè)計原則
看了這么多理論東西了钾菊,再不來點代碼刺激刺激神經(jīng)帅矗,我都要點右上角了。接下來煞烫,我們就通過一個例子浑此,小寫一點代碼,讓我們更好的理解設(shè)計模式滞详!
現(xiàn)在我們來設(shè)計一個游戲人物凛俱。(為了突出重點,更好的理解料饥,只講部分功能蒲犬,理解要表達的意思即可,并不會真正的把所有功能實現(xiàn)岸啡。)
class Character {
//使用武器
fun useWeapon() {
Log.i("TAG", "fist")
}
}
現(xiàn)在有了一個初步人物模型的設(shè)計原叮,實現(xiàn)了攻擊的方法。當人物到了一定等級,可以轉(zhuǎn)職為魔法師奋隶,道士沛慢,戰(zhàn)士的話,我們可以這樣寫:
class Character {
//使用武器
fun useWeapon(profession : String) {
if(profession.equals("magician")){
Log.i("TAG", "火墻風咆哮")
}
if(profession.equals("taoist")){
Log.i("TAG", "召喚4級寶寶")
}
if(profession.equals("warrior")){
Log.i("TAG", "刀刀烈火")
}
}
}
如果每個方法都這樣寫达布,當方法數(shù)量增多的時候团甲,這樣的寫法就變得很雜亂無章。還導致了影響類變化原因不止一個黍聂,也就違反了“單一職責原則”躺苦。那我們現(xiàn)在換一種方式來寫:創(chuàng)建Magician,Taoist产还,Warrior三個類匹厘。在父類中采用重載的方式來實現(xiàn)useWeapon()。
//魔法師使用武器
fun useWeapon(magician: Magician) {
Log.i("TAG", "火墻風咆哮")
}
//道士使用武器
fun useWeapon(taoist: Taoist) {
Log.i("TAG", "召喚4級寶寶")
}
//戰(zhàn)士使用武器
fun useWeapon(warrior: Warrior) {
Log.i("TAG", "刀刀烈火")
}
這樣做更傻脐区,不僅沒有遵守單一職責原則愈诚,還違反了迪米特法則。
實際上牛隅,當我們一看到這種需求炕柔,有點經(jīng)驗的都知道,應(yīng)該寫三個類媒佣,分別對應(yīng)魔法師匕累,道士,戰(zhàn)士默伍,而不是把所有東西寫進一個類里面來欢嘿。useWeapon方法寫在父類中的缺點已經(jīng)暴露出來了,直覺告訴我們也糊,這個方法是應(yīng)該寫在子類中的炼蹦。那父類中的這個方法留不留呢?里式替換原則告訴我們狸剃,子類可以擴展父類的功能掐隐,但不能改變父類原有的功能。于是乎……
class Magician : Character(){
fun useWeapon(){
Log.i("TAG", "火墻風咆哮")
}
}
class Taoist : Character(){
fun useWeapon(){
Log.i("TAG", "召喚4級寶寶")
}
}
class Warrior : Character() {
fun useWeapon(){
Log.i("TAG", "刀刀烈火")
}
}
現(xiàn)在來看捕捂,好像沒什么問題哦瑟枫。但是卻沒有遵守依賴倒置原則斗搞。我們應(yīng)該盡量的針對接口編程指攒,而不是針對實現(xiàn)編程。而現(xiàn)在我們把實際的行為都寫在子類當中僻焚!這樣的壞處的是你必須去在每一個子類中手寫useWeapon方法允悦,修改起來相當麻煩!而且在代碼執(zhí)行時沒辦法更改具體行為(除非寫更多代碼,但那樣并劃不來)隙弛。
現(xiàn)在這樣寫還有點怪怪的架馋,明明每一個類有useWeapon(),卻不能寫進父類中全闷。很氣有沒有叉寂!
那我們把character寫成一個接口?把方法寫進去总珠?
牛逼屏鳍!真是太聰明了!既不違反單一職責原則局服,也不違反迪米特法則钓瞭,還遵守了里式替換原則。
牛逼個雞兒淫奔!
現(xiàn)在我們只考慮了玩家的角色山涡,游戲里的NPC怎么辦? 你打得過NPC唆迁?
假如我們已經(jīng)把父類寫成了接口鸭丛,再創(chuàng)建一個npc類,看看吧唐责。
interface Character {
//使用武器
fun useWeapon()
}
class NPC :Character {
override fun useWeapon() {
//空方法
}
}
這樣造成了npc類里面有一個空方法系吩。你可能永遠都不會去用它。那放這兒有什么意思妒蔚?這樣寫還違反了接口隔離原則:不要對外暴露沒有實際意義的接口穿挨!不要對外暴露沒有實際意義的接口!不要對外暴露沒有實際意義的接口肴盏!
問題不大科盛!只需要打一個響指!我們重新理一理思緒菜皂!
現(xiàn)在問題在于我們要讓某些子類實現(xiàn)useWeapon()贞绵,而不是全部子類都要去實現(xiàn)useWeapon()。
okk的恍飘!useWeapon()既然不能放進接口里面榨崩,也不能放進父類里面,那我們就把這個方法單獨提出來章母,新寫一個useWeapon接口母蛛!
interface IUseWeapon {
fun useWeapon()
}
然后讓有攻擊功能的子類來實現(xiàn)IUseWeapon接口。
emmmmmmmmm……
這樣使用接口還是得一個個去寫子類實現(xiàn)的具體方法乳怎,而且也沒有遵守依賴倒置原則彩郊,在上面我已經(jīng)寫過了,依賴倒置原則的核心思想就在于面向接口編程,那面向接口編程是個啥意思呢秫逝?
“針對接口編程”真正的意思是“針對超類型編程”恕出。
- “針對接口編程”,關(guān)鍵就在于多態(tài)违帆!利用多態(tài)浙巫,程序可以針對超類型編程,執(zhí)行時會根據(jù)實際狀況執(zhí)行到真正的行為刷后,不會被綁死咋超類型的行為上狈醉。
- “針對超類型編程”這句話才沧,可以更明確地說成變量的聲明類型應(yīng)該是超類型淮椰,通常是一個抽象類或者是一個接口鲤孵。
- 只要是具體實現(xiàn)此超類型的類拍摇,所產(chǎn)生的對象漆弄,都可以指定給這個變量蔓罚。這也意味著眉踱,聲明類時不用理會以后會執(zhí)行時的真正對象裂逐!
//針對實現(xiàn)編程
var magician : Magician = Magician()
//針對接口\超類型編程
var character : Character = Magician()
此時我們已經(jīng)有了一個IUseWeapon接口抱慌,里面只有一個useWeapon()方法逊桦。我們不能用子類直接實現(xiàn)IUseWeapon,也不能用父類直接實現(xiàn)IUseWeapon抑进,那我們就專門創(chuàng)建一個“行為類”來實現(xiàn)行為接口强经!
class UseFireWall : IUseWeapon {
override fun useWeapon() {
Log.i("TAG", "火墻風咆哮")
}
}
class UseDogBaby : IUseWeapon{
override fun useWeapon() {
Log.i("TAG", "召喚4級寶寶")
}
}
class UseFireKnife : IUseWeapon{
override fun useWeapon() {
Log.i("TAG", "刀刀烈火")
}
}
這樣的設(shè)計,就讓使用武器這個行為跟character類無關(guān)了寺渗,還可以被其他對象服復用匿情。而新增一些使用武器行為時候,不會影響到既有的行為類信殊,也不會影響使用到行為類的character類炬称。
現(xiàn)在我們在整合一下整個人物的設(shè)計:
1. 擁有一個父類Character
2. 擁有四個子類,Magician, Taoist, Warrior, NPC
3. 有一個行為接口IUseWeapon
4. 有三個行為類實現(xiàn)了行為接口
目標:遵守六大設(shè)計原則的條件下涡拘,使Magician, Taoist, Warrior 才有useWeapon()
要實現(xiàn)攻擊的功能玲躯,那父類肯定得有調(diào)用useWeapon()的方法,也必須得擁有行為接口鳄乏。所以……
open class Character {
lateinit var iUseWeapon :IUseWeapon
fun coverUseWeapon(){
iUseWeapon.useWeapon()
}
}
在編譯時跷车,已經(jīng)能通過charater.coverUseWeapon()調(diào)用使用武器的方法。在代碼真正執(zhí)行時橱野,子類還沒對iUseWeapon進行聲明朽缴,所以在子類中要做的只是聲明iUseWeapon而已。這個時候仲吏,行為類實現(xiàn)行為接口的好處就體現(xiàn)出來了不铆,我們希望子類做什么樣的攻擊蝌焚,就可以聲明為什么樣的行為類裹唆,要想有新的新的攻擊動作誓斥,再創(chuàng)建一個行為類去實現(xiàn)IUseWeapon就可以了。反正實現(xiàn)的代碼沒有寫在子類中许帐,而是在行為類中劳坑,不用更改之前寫的所有代碼。
class Magician : Character(){
init {
iUseWeapon = UseFireWall()
}
}
美滋滋成畦!就這么簡單的一行代碼距芬!但這樣還是不夠靈活,我們還是在子類中做了一小部分的具體實現(xiàn)(創(chuàng)建iUseWeapon的實例)循帐,也就是沒有完全的做到針對接口編程框仔,所以我們需要有一個可以更改iUseWeapon實例的方法。
java代碼中拄养,我們可以在父類中加入set方法离斩。
public void setIUseWeapon(iUseWeapon : IUseWeapon) {
this.iUseWeapon = iUseWeapon ;
}
kotlin代碼中,直接在聲明character對象處
//讓魔法師召喚4級寶寶
var character : Character = Magician()
character.iUseWeapon = UseDogBaby()
子類里面的init方法瘪匿,就可以完全不寫了跛梗。
最后
相信各位看到這里對六大原則其中五個都有了一定的理解,剩下一個開閉原則沒有提到棋弥,是因為核偿,這玩意兒不好講!
設(shè)計模式就是個經(jīng)驗性的東西顽染,你完全可以不照著這樣去寫你的代碼漾岳,只要遵守六大設(shè)計原則,都是好的設(shè)計粉寞。
以下是我“設(shè)計模式系列”文章蝗羊,歡迎大家關(guān)注留言投幣丟香蕉。
也可以進群跟大神們討論仁锯。qq群:557247785
設(shè)計模式入門
Java與Kotlin的單例模式
Kotlin的裝飾者模式與源碼擴展
由淺到深了解工廠模式
為了學習Rxjava耀找,年輕小伙竟作出這種事!