導(dǎo)語
讓你的代碼更加優(yōu)美。
主要內(nèi)容
- 單一職責(zé)原則——優(yōu)化代碼的第一步
- 開閉原則——讓程序更穩(wěn)定靈活
- 里氏替換原則——構(gòu)建擴(kuò)展性更好的系統(tǒng)
- 依賴倒置原則——讓項(xiàng)目擁有變化的能力
- 接口隔離原則——讓系統(tǒng)有更高的靈活性
- 迪米特原則——更好的可擴(kuò)展性
具體內(nèi)容
單一職責(zé)原則
單一職責(zé)原則的英文名名稱是Single Responsibility Principle,縮寫是SRP。SRP的定義是:就一個(gè)類而言稚补,應(yīng)該僅有一個(gè)引起它變化的原因性穿。簡(jiǎn)單來說,一個(gè)類中應(yīng)該是一組相關(guān)性很高的函數(shù)鹉胖、數(shù)據(jù)的封裝所宰。
比如Pear是一家電子產(chǎn)品商绒尊,它要生產(chǎn)pad,phone仔粥,watch等設(shè)備婴谱,但是有一些重復(fù)的功能,如果分別設(shè)計(jì)一套,很顯然并不劃算勘究,那么接口定義上我們就可以根據(jù)功能劃分設(shè)定單一職責(zé)的接口:
接口的定義
//可以撥打電話
interface Callable{
void call ();
}
//可以觸摸控制
interface Touchable{
void touch();
}
//可以消息提醒
interface MessagePromptable{
void prompt();
}
//可以接入鍵盤
interface KeyBoardMatchable{
void match();
}
實(shí)現(xiàn)接口的類依舊單一職責(zé)
class StandardCall implements Callable{
@Override
public void call() {
System.out.println("Call to somebody!");
}
}
class StandardTouch implements Touchable{
@Override
public void touch() {
System.out.println("touch to press the button!");
}
}
class StandardPromt implements MessagePromptable{
@Override
public void prompt() {
System.out.println(" someone contact to you,sir!");
}
}
class StandardMatch implements KeyBoardMatchable{
@Override
public void match() {
System.out.println("The keyBoard is ready to work!");
}
}
產(chǎn)品的生產(chǎn)
我們?nèi)绻谖覀儸F(xiàn)有的技術(shù)生產(chǎn)一部手機(jī),那么我們需要它能打電話斟冕,觸屏控制和消息提醒:
//在聲明這臺(tái)手機(jī)時(shí)我們就明確知道了它的功能
class MyPhone implements Callable, MessagePromptable, Touchable{
//無需重復(fù)研發(fā)已有的技術(shù)口糕,直接裝載即可
private Callable caller = new StandardCall();
private MessagePromptable prompter = new StandardPromt();
private Touchable toucher = new StandardTouch();
@Override
public void call() {
caller.call();
}
@Override
public void prompt() {
prompter.prompt();
}
@Override
public void touch() {
toucher.touch();
}
}
public class SRPTest {
public static void main ( String [] args ){
MyPhone phone = new MyPhone();
phone.call();
phone.prompt();
phone.touch();
}
}
假如我們需要出一款新的手機(jī),但是我們只是擁有了新的呼叫技術(shù)磕蛇,那么只需要在實(shí)現(xiàn)這項(xiàng)技術(shù)時(shí)繼承Callable接口景描,然后在之前手機(jī)new的Callable的具體實(shí)例換成新的技術(shù)即可,只需要修改一行代碼秀撇,是不是感覺棒棒的超棺。職責(zé)的單一,對(duì)于我們對(duì)于現(xiàn)有類的修改造成的影響有了約束呵燕。
那么如果我想生產(chǎn)一個(gè)Pad呢棠绘,同理啊,只需要在已有技術(shù)上裝載即可啊再扭,Pad類依舊只是單一的整合技術(shù)形成產(chǎn)品的職責(zé)氧苍,整合成產(chǎn)品和研發(fā)出技術(shù)的職責(zé)分離,為我們的類的拓展帶來了方便泛范。
class MyPad implements Touchable,KeyBoardMatchable{
Touchable toucher = new StandardTouch();
KeyBoardMatchable matcher = new StandardMatch();
@Override
public void match() {
toucher.touch();
}
@Override
public void touch() {
matcher.match();
}
}
下面一個(gè)例子让虐,我們的接口依舊單一職責(zé),但是接聽和撥打電話的功能往往是不可分的罢荡,他們會(huì)同時(shí)發(fā)生變化赡突,所以我們可以提供一個(gè)同時(shí)繼承兩個(gè)接口的實(shí)現(xiàn)類。
class CallAndPrompt implements Callable,MessagePromptable{
@Override
public void call() {
System.out.println("Hello, I have some thing to tell you!");
}
@Override
public void prompt() {
System.out.println("Hello,what do you want to tell me!");
}
}
//在聲明這臺(tái)手機(jī)時(shí)我們就明確知道了它的功能
class MyPhone implements Callable,MessagePromptable,Touchable{
//無需重復(fù)研發(fā)已有的技術(shù)区赵,直接裝載即可
private Callable caller = new CallAndPrompt();
//不同的接口調(diào)用同一個(gè)實(shí)現(xiàn)類的不同功能
private MessagePromptable prompter = (MessagePromptable)caller;
private Touchable toucher = new StandardTouch();
@Override
public void call() {
caller.call();
}
@Override
public void prompt() {
prompter.prompt();
}
@Override
public void touch() {
toucher.touch();
}
}
開閉原則
開閉原則的英文全稱是Open Close Principle惭缰,縮寫是OCP,它是Java世界里最基礎(chǔ)的設(shè)計(jì)原則笼才,它指導(dǎo)我們?nèi)绾谓⒁粋€(gè)穩(wěn)定的从媚、靈活的系統(tǒng)。開閉原則的定義是:軟件中的對(duì)象(類患整、模塊拜效、函數(shù)等)應(yīng)該對(duì)于擴(kuò)展是開放的,但是各谚,對(duì)于修改是封閉的紧憾。
優(yōu)點(diǎn):按照OCP原則設(shè)計(jì)出來的系統(tǒng),降低了程序各部分之間的耦合性昌渤,其適應(yīng)性赴穗、靈活性、穩(wěn)定性都比較好。當(dāng)已有軟件系統(tǒng)需要增加新的功能時(shí)般眉,不需要對(duì)作為系統(tǒng)基礎(chǔ)的抽象層進(jìn)行修改了赵,只需要在原有基礎(chǔ)上附加新的模塊就能實(shí)現(xiàn)所需要添加的功能。增加的新模塊對(duì)原有的模塊完全沒有影響或影響很小甸赃,這樣就無須為原有模塊進(jìn)行重新測(cè)試柿汛。
如何實(shí)現(xiàn)“開-閉”原則
在面向?qū)ο笤O(shè)計(jì)中,不允許更改的是系統(tǒng)的抽象層埠对,而允許擴(kuò)展的是系統(tǒng)的實(shí)現(xiàn)層络断。換言之,定義一個(gè)一勞永逸的抽象設(shè)計(jì)層项玛,允許盡可能多的行為在實(shí)現(xiàn)層被實(shí)現(xiàn)貌笨。
解決問題關(guān)鍵在于抽象化,抽象化是面向?qū)ο笤O(shè)計(jì)的第一個(gè)核心本質(zhì)襟沮。
對(duì)一個(gè)事物抽象化锥惋,實(shí)質(zhì)上是在概括歸納總結(jié)它的本質(zhì)。抽象讓我們抓住最最重要的東西开伏,從更高一層去思考净刮。這降低了思考的復(fù)雜度,我們不用同時(shí)考慮那么多的東西硅则。換言之淹父,我們封裝了事物的本質(zhì),看不到任何細(xì)節(jié)怎虫。
在面向?qū)ο缶幊讨惺钊希ㄟ^抽象類及接口,規(guī)定了具體類的特征作為抽象層大审,相對(duì)穩(wěn)定蘸际,不需更改,從而滿足“對(duì)修改關(guān)閉”徒扶;而從抽象類導(dǎo)出的具體類可以改變系統(tǒng)的行為粮彤,從而滿足“對(duì)擴(kuò)展開放”。
對(duì)實(shí)體進(jìn)行擴(kuò)展時(shí)姜骡,不必改動(dòng)軟件的源代碼或者二進(jìn)制代碼导坟。關(guān)鍵在于抽象。
接口的定義
public interface ImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bmp);
}
實(shí)現(xiàn)接口
// 內(nèi)存緩存MemoryCache類
public class MemoryCache implements ImageCache {
private LruCache<String, Bitmap> mMemeryCache;
public MemoryCache() {
// 初始化LRU緩存
}
@Override
public Bitmap get(String url) {
return mMemeryCache.get(url);
}
@Override
public void put(String url, Bitmap bmp) {
mMemeryCache.put(url, bmp);
}
}
// SD卡緩存DiskCache類
public class DiskCache implements ImageCache {
@Override
public Bitmap get(String url) {
// 改為從本地文件獲取圖片
return null;
}
@Override
public void put(String url, Bitmap bmp) {
// 將Bitmap寫入文件中
}
}
// 雙緩存DoubleCache類
public class DoubleCache implements ImageCache {
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new mDiskCache();
// 先從內(nèi)存中獲取圖片圈澈,如果沒有再?gòu)腟D卡中獲取
public Bitmap get(String url) {
public Bitmap bitmap = mMemoryCache.get(url);
if(bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
// 將圖片緩存到內(nèi)存和SD卡中
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
mDiskCache.put(url, bmp);
}
}
實(shí)現(xiàn)圖片加載器類
public class ImageLoader {
// 圖片緩存惫周,并設(shè)置了內(nèi)存緩存為默認(rèn)方式
private ImageCache mImageCache = new MemoryCache();
// 注入緩存實(shí)現(xiàn) 利用了向上轉(zhuǎn)型
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = mImageCache.get(imageUrl);
if(bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 圖片沒有緩存,提交到線程池中下載圖片
submitLoadRequest(imageUrl, imageView)
}
public void submitLoadRequest(final String imageUrl, final ImageView imageView) {
// 下載圖片
// 緩存
mImageCache.put(imageUrl, bitmap);
}
// 省略其他成員變量和方法
}
調(diào)用方法
// 使用方法 只是通過傳入不同實(shí)現(xiàn)就可以切換緩存方式
ImageLoader loader = new ImageLoader();
loader.setImageCache(new MemoryCache()); // 使用內(nèi)存緩存
loader.setImageCache(new DiskCache()); // 使用SD卡緩存
loader.setImageCache(new DoubleCache()); // 使用雙緩存
// 使用自定義的圖片緩存實(shí)現(xiàn)
loader.setImageCache(new ImageCache() {
@Override
public Bitmap get(String url) {
// 改為從本地文件獲取圖片
return null;
}
@Override
public void put(String url, Bitmap bmp) {
// 將Bitmap寫入文件中
}
});
我們看得到通過setImageCache(ImageCache imageCache) 方式注入不同的緩存實(shí)現(xiàn)康栈,使得ImageLoader代碼變得更簡(jiǎn)單递递,健壯喷橙,提升高了它的靈活性和可擴(kuò)展性,如果還有還有新的緩存方式登舞,只需要去實(shí)現(xiàn)ImageCachej接口就可以使用了贰逾。
所以當(dāng)需求發(fā)生變化時(shí),應(yīng)該盡量通過擴(kuò)展的方式來實(shí)現(xiàn)變化菠秒,而不是通過修改已有代碼來實(shí)現(xiàn)疙剑,但要做到開閉原則,首先我們應(yīng)該先寫出更易擴(kuò)展的代碼稽煤。
里氏替換原則
里氏替換原則英文全稱是Liskov Substitution Principle,縮寫是LSP囚戚。LSP的第一種定義是:如果對(duì)每一個(gè)類型為S的對(duì)象O1酵熙,都有類型為T的對(duì)象O2,使得以T定義的所有程序P在所有的對(duì)象O1都代換成O2時(shí)驰坊,程序P的行為沒有發(fā)生變化匾二,那么類型S是類型T的子類型。
或者說是:所有引用基類的地方必須能透明地使用其子類的對(duì)象拳芙。
就像開閉原則中舉的例子察藐,創(chuàng)建了一個(gè)ImageCache,而其他緩存類都是他的實(shí)現(xiàn)類舟扎,而setImageCache(ImageCache imageCache) 需要的就是ImageCache類型分飞,這時(shí)候我們就可以使用MemoryCache,DiskCache睹限,DoubleCache來替換ImageCache的工作譬猫。ImageCache確定了規(guī)范,而新的緩存需求都可以通過實(shí)現(xiàn)它然后替換ImageCache來工作羡疗,從而保證了可擴(kuò)展性染服。
也可以看一下系統(tǒng)代碼
// 窗口類
public class Window {
public void show(View child) {
child.draw();
}
}
// 建立視圖抽象,測(cè)量視圖寬高為公用代碼叨恨,繪制實(shí)現(xiàn)交給具體的子類
public abstract class View {
public abstract void draw();
public void measure(int width, int height) {
// 測(cè)量視圖大小
}
}
// 按鈕類的具體實(shí)現(xiàn)
public class Button extends View {
public void draw() {
// 繪制按鈕
}
}
// TextView的具體實(shí)現(xiàn)
public class TextView extends View {
public void draw() {
// 繪制文本
}
}
故里氏替換原則就是通過建立抽象柳刮,建立規(guī)范,然后在運(yùn)行時(shí)通過具體實(shí)現(xiàn)來替換掉抽象痒钝,從而保證了系統(tǒng)的擴(kuò)展性和靈活性秉颗。可見送矩,在開發(fā)過程中運(yùn)用抽象是走向代碼優(yōu)化的重要一步站宗。
開閉原則和里氏替換原則往往都是一同出現(xiàn)的,通過里氏替換原則達(dá)到對(duì)擴(kuò)展的開發(fā)益愈,對(duì)修改關(guān)閉的效果梢灭。
依賴倒置原則
依賴倒置原則英文全稱是Dependence Inversion Principle夷家,縮寫是DIP。依賴倒置原則指代了一種特定的解耦形式敏释,使得高層次的模塊不依賴于低層次的模塊的實(shí)現(xiàn)細(xì)節(jié)的目的库快,依賴模塊被顛倒了。
依賴倒置原則的三個(gè)關(guān)鍵點(diǎn):
- 高層次模塊不應(yīng)該依賴于底層模塊钥顽,兩者都應(yīng)該依賴其抽象义屏。
- 抽象不應(yīng)依賴細(xì)節(jié)。
- 細(xì)節(jié)應(yīng)該依賴抽象蜂大。
抽象就是指接口或者抽象類闽铐;細(xì)節(jié)就是實(shí)現(xiàn)類;高層模塊就是調(diào)用端奶浦,低層模塊就是具體實(shí)現(xiàn)類兄墅。
依賴倒置原則在Java中表現(xiàn)就是:模塊間依賴是通過抽象發(fā)生的,實(shí)現(xiàn)類之間并不產(chǎn)生直接依賴關(guān)系澳叉,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的隙咸。
一句話概括:面向接口編程,或者說面向抽象編程成洗。
我們依然可以通過上面的例子繼續(xù)說明五督,代碼如下:
// 如果在ImageLoader中直接這樣寫的話
// 就是直接依賴于細(xì)節(jié)(直接依賴實(shí)現(xiàn)類)
private DoubleCache mCache = new DoubleCache();
public void setImageCache(DoublieCache cache) {
mCache = cache;
}
而我們的代碼卻直接完成1.2.3.4這四個(gè)原則
// 依賴于抽象,通過向上轉(zhuǎn)型瓶殃,有一個(gè)默認(rèn)的實(shí)現(xiàn)類
private ImageCache mImageCache = new MemoryCache();
// 設(shè)置緩存策略充包,依賴于抽象
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
依賴于抽象,依賴于基類遥椿,這樣當(dāng)需求發(fā)生變化误证,只需要實(shí)現(xiàn)ImageCache或者繼承已實(shí)現(xiàn)的之類都可以完成緩存功能,然后將實(shí)現(xiàn)注入到setImageCache(ImageCache imageCache)就可以了修壕。
接口隔離原則
接口隔離原則英文全稱是InterfaceSegregation Principles愈捅,縮寫是ISP。ISP的定義是:客戶端不應(yīng)該依賴它不需要的接口慈鸠±督鳎或者說類的依賴關(guān)系應(yīng)該將在最小的接口上。
接口隔離的目的是系統(tǒng)接口耦合青团,從而容易重構(gòu)譬巫、更改和重新部署。一句話:讓客戶端依賴的接口盡可能小督笆。
舉一個(gè)例子芦昔,當(dāng)我們?cè)谑褂昧鞯臅r(shí)候我們需要在finally中判斷是否為空,如果不為空需要close()它娃肿,但每次使用流咕缎,都這么寫珠十,也會(huì)讓代碼變得不優(yōu)美,這個(gè)時(shí)候我們考慮借助外力凭豪,就比如Java為我們提供了一個(gè)Closeable接口焙蹭,而它有100多個(gè)實(shí)現(xiàn)類,所以那些類都可以使用它嫂伞,代碼如下:
// 這就是修改之前的代碼 try/catch中還有try/catch
FileOutputStream fileOutputStream = null;
try {
// 邏輯省略
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 寫了個(gè)CloseUtil類孔厉,然后西面提供這個(gè)靜態(tài)方法,所有實(shí)現(xiàn)了Closeable的類都可以調(diào)用這個(gè)方法
public static void closeQuietly (Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 我們只需要在finally中調(diào)用這一句話就好了
CloseUtil.closeQuietly(xxx);
不僅讓代碼的可讀性增加了帖努,還保證了它的重用性撰豺,這里也用到了依賴倒置原則,closeQuietly()方法的參數(shù)就一個(gè)抽象拼余,做到了我只需要知道這個(gè)對(duì)象是可關(guān)閉的污桦,其他一概不管辛,也就是作者所說的接口隔離原則姿搜。
迪米特原則
迪米特原則英文全稱為L(zhǎng)aw of Demeter寡润,縮寫是LOD捆憎,也稱為最少知識(shí)原則(Least Knowledge Principle)舅柜。LOD的定義是:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解。
通俗的講躲惰,一個(gè)類應(yīng)該對(duì)自己需要耦合或者調(diào)用的類知道的最少致份,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒有關(guān)系,只需要知道它需要的方法即可础拨,其他的一概不管氮块,類與類之間的關(guān)系越密切,耦合度也就越大诡宗。
迪米特原則還有一個(gè)英文解釋:Only talk to your immediate friends.翻譯過來也就是說之與直接朋友進(jìn)行通信滔蝉。
還是前面的ImageLoder,緩存這塊是已經(jīng)搞定了塔沃。假如在某次加載圖片中,蝠引,緩存沒找到就需要聯(lián)網(wǎng)去服務(wù)器拿圖片,并且需要存到緩存中以備下次直接從緩存加載蛀柴,ok螃概,很快可以寫出這樣的代碼:
public class ImageLoder {
private ImageCache mImageCache = new DoubleCache();
//...
public void dispalyImage(String url, ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
HttpImage4Service.down4Service(url, imageView, mImageCache);
}
}
HttpImage4Service下載類中從網(wǎng)絡(luò)中加載圖片方法:
public static void down4Service(String url, ImageView imageView, ImageCache imageCache) {
//...從網(wǎng)絡(luò)拉取圖片
//回調(diào)↓
imageView.setImageBitmap(bitmap4Service);//顯示圖片
imageCache.put(url, bitmap4Service);//存到緩存中
}
分析下這樣設(shè)計(jì)的耦合情況,
- ImageLoder調(diào)用ImageCache和HttpImage4Service。
- HttpImage4Service調(diào)用ImageCache鸽疾。
三個(gè)類之間是否知道的最少吊洼?試想一下,從網(wǎng)絡(luò)拉取圖片跟緩存這樣兩個(gè)類應(yīng)該有關(guān)聯(lián)嗎制肮?實(shí)際上是沒必要的冒窍,根據(jù)最少知識(shí)原則递沪,改進(jìn)之后應(yīng)該是下面這樣的:
public class ImageLoder {
private ImageCache mImageCache = new DoubleCache();
//...
public void dispalyImage(String url, ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
bitmap=HttpImage4Service.down4Service(url);//只負(fù)責(zé)下載圖片
imageView.setImageBitmap(bitmap);
imageCache.put(url, bitmap);//存到緩存中
}
}
改進(jìn)后三個(gè)類中只有ImageLoder調(diào)用HttpImage4Service和ImageCache中的方法,其余沒有任何調(diào)用關(guān)系超燃,耦合度降低区拳。
在MVP中,View層和Model層拒絕通信意乓,也是符合最少知識(shí)原則的樱调,達(dá)到降低耦合效果,同時(shí)可擴(kuò)展性會(huì)大大增加届良。
總結(jié)
應(yīng)用開發(fā)笆凌,最難的不是完成開發(fā)工作,而是維護(hù)和升級(jí)士葫。為了后續(xù)能夠很好的維護(hù)和升級(jí)乞而,我們的系統(tǒng)需要在滿足穩(wěn)定性的前提下保持以下三個(gè)特性:
- 高可擴(kuò)展性
- 高內(nèi)聚
- 低耦合