案例:
英雄聯盟的英雄、道具、地圖,每年都會進行頻繁變更
如果沒有使用軟件工程的開發思想,隨便改其中一個道具的屬性,就可能會導致非常嚴重的錯誤
要實現變更/增加英雄時,可選英雄數量和玩家開始一局遊戲時選擇一個英雄的操作
第一版代碼
創建一個英雄,方法代表英雄擁有的技能
玩家輸入英雄名字,代表選擇該英雄
問題:隨着遊戲的英雄越來越多的時候,應該怎麼做???
增加英雄:
又新增一個英雄,又要在main中變更
這個代碼的問題就在於,我每次有新的英雄添加進來的時候,都是需要去修改這塊代碼的,不滿足我們想要實現的開閉原則。我們要保證主體的方法具有一定的穩定性,不要大幅度的去修改它,一修改就會容易引起BUG
JAVA這個語言爲要寫出可維護的代碼實現了很多的特性,這樣子寫出來的代碼,並沒有使用JAVA語言的特性,可以用其他任意的動態語言都是可以代替的
第二版代碼
我們最終的目的就在於,當我們新增一個英雄的時候,儘量能夠少改動main方法裏的代碼,讓代碼保證一定的穩定性
方法:使用interface寫出抽象的代碼
新建一個接口
讓每個英雄都實現這個接口
面向對象的編程思想就是反覆的在做兩件事情:實例化一個對象、調用對象的方法
這一版代碼的缺陷在於:當添加新的英雄時,依舊要在Main函數中添加Case分支,依舊無法統一對象的實例化
但是interface的優勢就在於,它可以統一方法的調用。在第一版代碼中,我們在每個Case中都要調用英雄的方法,而在第二版代碼中,我們在創建完英雄後,統一調用方法
統一方法的調用是非常有意義的,在真實的項目中,對於一個實例方法的調用是非常多的,將實例方法的調用統一起來,意義就非常大了。第二版代碼的switch代碼就不再有業務邏輯,而是隻有對象的創建
所以:抽象的難點就在於如何統一對象的實例化
第三版代碼
我們最終的目的是:在新增一個英雄的時候,不去修改main函數裏的代碼
只有一段代碼不負責對象實例化,代碼才能保持穩定。但是對象的實例化是不可能消除的
最簡單的解決方案就是,把對象實例化的過程,轉移到其他的代碼片段裏
使用工廠模式實現分離對象實例化
新增一個工廠類,讓工廠類負責對象的實例化
現在main函數的代碼就相對穩定了,當我們新增英雄的時候,這塊代碼就不再需要改變了,這塊代碼實現了OCP。
但是存在兩個問題:雖然這塊的代碼塊不需要修改了,但是工廠類裏面的代碼塊卻需要修改。這邊調用了HeroFactory類,如果HeroFactory類變更了,這邊的代碼還是需要進行改動。
對於第一個問題,難道這樣做就沒有意義了嗎?是有意義的。代碼總是會存在不穩定的,將不穩定的代碼隔離起來,保證其他的代碼是穩定的,也是非常有意義的。這樣做就可以保證系統裏面除了Factory方法的代碼外,其他代碼是穩定的,那麼我們的目的就達到了。IOC的思想其實就是將所有不穩定的代碼隔離和封裝到了一塊,保證其他地方的代碼都是穩定的。
對於第二個問題,使用抽象工廠的設計模式就可以解決。如果我們將所有對象的實例化都寫在一個Factory中,就可以看做這個代碼是穩定的
第四版代碼
變化是導致代碼不穩定的本質原因
變化主要體現在1.用戶輸入的不同 2.需求變更
有沒有辦法直接將用戶輸入的字符串轉換成對象?
方法:通過反射機制消除所有的變化
反射的作用就是能夠幫助我們動態的創建類。
總結:
面向對象編程主要做兩件事情:實例化對象和調用方法(完成業務邏輯)
只有一段代碼中內有new的出現,才能保持代碼的相對穩定,才能逐步實現OCP原則。即一段代碼如果要保持穩定,就不應該負責對象的實例化。
想要實現OCP原則,就要面向抽象編程。
- 單純interface可以統一方法的調用,但是它不能統一對象的實例化。
- 對象實例化是不可能消除的。代碼裏總會存在不穩定,隔離這些不穩定,保證其他的代碼是穩定的。解決的方式就是把對象實例化的過程轉移到其他的代碼片段裏,即使用工廠模式,將對象的實例化全部封裝到工廠類中。
- 最後,可以使用反射機制幫助我們動態的創建類。