以下內(nèi)容來自:《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
說起面對對象的六大原則茅郎,可能大部分人都能說出一二來蜗元。但是如何應(yīng)用到自己的代碼中卻是一個不小的難題。這篇文章會用一個實(shí)際的例子系冗,并用六大原則改造奕扣,在改造的過程中體會。
我們來看一個Android中常見的功能模塊——圖片加載掌敬。在不使用任何現(xiàn)有框架的前提下惯豆,可能會這樣寫:
public class ImageLoader {
//圖片緩存
LruCache<String, Bitmap> mImageCache;
//固定線程數(shù)的線程池,線程數(shù)為CPU的數(shù)量
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public ImageLoader() {
initImageCache();
}
private void initImageCache() {
//計(jì)算可使用的最大內(nèi)存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//使用1/4作為圖片緩存
int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() * 1024;
}
};
}
public void displayImage(final String url, final ImageView imageView) {
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
這樣可以完成圖片加載的功能奔害,但是這個ImageLoader嚴(yán)重耦合楷兽,毫無設(shè)計(jì)可言,更不用說擴(kuò)展性华临、靈活性了芯杀。所有的功能都寫在一個類里,隨著功能的增加,ImageLoader類會越來越大揭厚,代碼也越來越復(fù)雜却特,圖片加載系統(tǒng)就越來越脆弱。接下來我們嘗試用單一職責(zé)原則改造以下這個ImageLoader棋弥。
單一職責(zé)原則
**Single Responsibility Principle **
定義:就一個類而言核偿,應(yīng)該僅有一個引起它變化的原因。簡單來說顽染,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)漾岳、數(shù)據(jù)的封裝。
雖然如何劃分一個類粉寞,一個函數(shù)的職責(zé)尼荆,每個人都有自己的看法,這需要根據(jù)個人經(jīng)驗(yàn)唧垦、具體的業(yè)務(wù)邏輯而定捅儒。但完全兩個不一樣的功能就不應(yīng)該放在一個類中。因此從單一職責(zé)來看ImageLoader振亮,明顯它可以分為兩個巧还,一個是圖片加載;另一個是圖片緩存坊秸。因此我們這樣改造:
public class ImageLoader {
//圖片緩存
ImageCache mImageCache = new ImageCache();
//固定線程數(shù)的線程池麸祷,線程數(shù)為CPU的數(shù)量
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
抽出ImageCache用于處理圖片緩存:
public class ImageCache {
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
private void initImageCache() {
//計(jì)算可使用的最大內(nèi)存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//使用1/4作為圖片緩存
int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() * 1024;
}
};
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
}
改造后的ImageLoader只負(fù)責(zé)圖片加載的邏輯,而ImageCache只負(fù)責(zé)處理圖片緩存的邏輯褒搔,這樣ImageLoader的代碼量變少了阶牍,職責(zé)也清晰了;當(dāng)緩存相關(guān)的邏輯需要改變時星瘾,不需要修改ImageLoader類走孽,而圖片的加載邏輯需要修改時也不會影響到緩存處理邏輯。
開閉原則
Open Close Principe
定義:軟件中的對象(類琳状、模塊磕瓷、函數(shù)等)應(yīng)該對于擴(kuò)展是開放的,但是對于修改是封閉的念逞。在軟件的生命周期內(nèi)困食,因?yàn)樽兓⑸壓途S護(hù)等原因需要對軟件原有代碼進(jìn)行修改時肮柜,可能會將錯誤引入原本已經(jīng)經(jīng)過測試的舊代碼中,破壞原有系統(tǒng)倒彰。因此审洞,當(dāng)軟件需要變化時,我們應(yīng)該盡量通過擴(kuò)展的方式來實(shí)現(xiàn)變化么人不是通過修改已有的代碼來實(shí)現(xiàn)。當(dāng)然芒澜,在現(xiàn)實(shí)開發(fā)中仰剿,只通過繼承的方式來升級、維護(hù)原有系統(tǒng)只是一個理想化的愿景痴晦,因此南吮,在實(shí)際的開發(fā)過程中,修改原有代碼誊酌、擴(kuò)展代碼往往是同時存在的部凑。
我們再來看看ImageLoader,雖然通過內(nèi)存緩存解決了每次從網(wǎng)絡(luò)加載圖片的問題碧浊,但是涂邀,Android應(yīng)用的內(nèi)存很有限,且具有易失性箱锐,即當(dāng)應(yīng)用重新啟動之后比勉,原來已經(jīng)加載過的圖片將會失去,這樣重啟之后就需要重新下載驹止!這又會導(dǎo)致加載緩慢浩聋、耗費(fèi)用戶流量的問題。引入SD卡緩存可以解決這一個問題臊恋,我們在來添加一個SD卡緩存類:
public class DiskCache {
static String cacheDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
然后相應(yīng)的衣洁,改動ImageLoader的代碼,添加SD卡緩存:
public class ImageLoader {
//內(nèi)存緩存
ImageCache mImageCache = new ImageCache();
//SD卡緩存
DiskCache mDiskCache = new DiskCache();
//是否使用SD卡緩存
boolean isUseDiskCache = false;
//固定線程數(shù)的線程池捞镰,線程數(shù)為CPU的數(shù)量
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
//判斷使用哪種緩存
Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
if (isUseDiskCache) {
mDiskCache.put(url, bitmap);
} else {
mImageCache.put(url, bitmap);
}
}
});
}
public void useDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
這里增加了一個DiskCache類闸与,用戶可以通過useDiskCache方法來使用哪種緩存進(jìn)行設(shè)置,例如:
ImageLoader imageLoader = new ImageLoader();
//使用SD卡緩存
imageLoader.useDiskCache(true);
//使用內(nèi)存緩存
imageLoader.useDiskCache(false);
但是這樣又存在不能同時使用內(nèi)存緩存和SD卡緩存岸售。應(yīng)該優(yōu)先使用內(nèi)存緩存践樱,如果內(nèi)存緩存中沒有圖片再使用SD卡緩存,如果SD卡中也沒有圖片最后才去網(wǎng)絡(luò)上獲取凸丸,這才是最好的緩存策略拷邢。我們在添加一種雙緩存的類DoubleCache:
public class DoubleCache {
ImageCache mMemoryCache = new ImageCache();
DiskCache mDiskCache = new DiskCache();
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}
并且同樣應(yīng)用到ImageLoader中:
public class ImageLoader {
//內(nèi)存緩存
ImageCache mImageCache = new ImageCache();
//SD卡緩存
DiskCache mDiskCache = new DiskCache();
//雙緩存
DoubleCache mDoubleCache = new DoubleCache();
//使用SD卡緩存
boolean isUseDiskCache = false;
//使用雙緩存
boolean isUseDoubleCache = false;
//固定線程數(shù)的線程池,線程數(shù)為CPU的數(shù)量
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
//判斷使用哪種緩存
Bitmap bitmap = null;
if (isUseDoubleCache) {
bitmap = mDoubleCache.get(url);
} else if (isUseDiskCache) {
bitmap = mDiskCache.get(url);
} else {
bitmap = mImageCache.get(url);
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
//沒有緩存屎慢,則提交給線程池進(jìn)行異步下載
}
public void useDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
public void useDoubleCache(boolean useDoubleCache) {
isUseDoubleCache = useDoubleCache;
}
}
現(xiàn)在即可以使用內(nèi)存緩存瞭稼,也可以使用SD卡緩存,或者兩者同時使用腻惠,但還是存在一些問題环肘。我們來分析一下現(xiàn)在的代碼,ImageLoader通過boolean變量來讓用戶選擇使用哪種緩存集灌,因此存在各種if-else判斷語句悔雹,通過這些判斷來確定使用哪種緩存。隨著這些邏輯的引入,代碼變得越來越復(fù)雜腌零、脆弱梯找,如果一不小心寫錯了某個if條件,那就需要更多的時間來排除益涧,整個ImageLoader類也會變得越來越臃腫锈锤。還有一點(diǎn),用戶不能自己實(shí)現(xiàn)緩存注入到ImageLoader中闲询,可擴(kuò)展性差久免。
這樣的代碼明顯不滿足開閉原則,為了滿足上面說到的需求嘹裂,我們可以采用策略模式繼續(xù)改造妄壶,先來看下UML圖:
接著,我們把代碼按照UML圖改造一下,ImageCache變成了接口寄狼,并且有3個實(shí)現(xiàn)類:MemoryCache丁寄、DiskCache、DoubleCache泊愧。
public interface ImageCache {
void put(String url, Bitmap bitmap);
Bitmap get(String url);
}
public class MemoryCache implements ImageCache {
...
}
public class DiskCache implements ImageCache {
...
}
public class DoubleCache implements ImageCache {
...
}
而ImageLoader變成這樣:
public class ImageLoader {
//緩存
ImageCache mImageCache = new MemoryCache();
//固定線程數(shù)的線程池伊磺,線程數(shù)為CPU的數(shù)量
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
//圖片沒有緩存,提交到線程池中下載
submitLoadRequest(url, imageView);
}
private void submitLoadRequest(final String url, final ImageView imageView) {
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
這里的增加了一個setImageCache(ImageCache imageCache)
函數(shù)删咱,用戶可以通過該函數(shù)設(shè)置緩存實(shí)現(xiàn)屑埋,也就是通常說的依賴注入:
//默認(rèn)使用內(nèi)存緩存
ImageLoader imageLoader = new ImageLoader();
//使用SD卡緩存
imageLoader.setImageCache(new DiskCache());
//使用雙緩存
imageLoader.setImageCache(new DoubleCache());
//使用自定義緩存
imageLoader.setImageCache(new ImageCache(){
@Ovrride
public void put(String url, Bitmap bitmap){
//緩存圖片
}
@Ovrride
public Bitmap get(String url){
retrun /*從緩存獲取圖片*/;
}
});
現(xiàn)在我們再來看看,無論之后新增加什么緩存痰滋,都可以實(shí)現(xiàn)ImageCache接口摘能,然后通過setImageCache方法注入,在這個過程中我們都不用修改ImageLoader類的代碼敲街,這就是開閉原則:對擴(kuò)展是開放的团搞,對修改是封閉的。而設(shè)計(jì)模式是前人總結(jié)的能更好地遵守原則的方式多艇。
里氏替換原則
Liskov Substitution Principle
定義:如果對每一個類型為S的對象O1逻恐,都有類型為T的獨(dú)享O2,使得以T定義的所有程序P在所有的對象O1都代替成O2時峻黍,程序P的行為沒有發(fā)生變化复隆,那么類型S是類型T的子類型。
看定義的話不是很好理解姆涩,換一種描述方式就是:所有引用基類的地方必須能透明地使用其子類的對象挽拂。
我們來看看Android中Window與View的關(guān)系:
Window依賴于View,而View定義了一個視圖抽象骨饿,measure是各個子類共享的方法亏栈,子類通過復(fù)寫View的draw方法實(shí)現(xiàn)具有各自特色的功能洪鸭,即繪制自身內(nèi)容。任何繼承自View類的子類都可以設(shè)置給show方法仑扑,就是所說的里式替換。通過里式替換置鼻,就可以自定義各式各樣镇饮、千變?nèi)f化的View怜校,然后傳遞給Window屁桑,Window負(fù)責(zé)組織View酬滤,并且將View顯示到屏幕上格嗅。
里式替換的核心原理是抽象粟耻,抽象又依賴于繼承這個特性释移,在OOP當(dāng)中祖秒,繼承的優(yōu)缺點(diǎn)都相當(dāng)明顯:優(yōu)點(diǎn)有以下幾點(diǎn):
- 代碼重用究反,減少創(chuàng)建類的成本聂喇,每個子類都擁有父類的方法和屬性辖源;
- 子類與父類基本相似,但又與父類有所區(qū)別希太;
- 提高代碼的可擴(kuò)展性克饶。
繼承的缺點(diǎn):
- 繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法誊辉;
- 可能造成子類代碼冗余矾湃、靈活性降低,因?yàn)樽宇惐仨殦碛懈割惖膶傩院头椒ā?/li>
事物總是具有兩面性堕澄,如何權(quán)衡利弊都是需要根據(jù)具體情況來做出選擇并加以處理邀跃。
前面ImageLoader的例子也體現(xiàn)了里式替換原則,即MemoryCache蛙紫、DiskCache拍屑、DoubleCache都可以替換ImageCache的工作,并且能夠保證行為的正確性惊来。ImageCache建立獲取緩存圖片丽涩、保存緩存圖片的接口規(guī)范,MemoryCache等根據(jù)接口規(guī)范實(shí)現(xiàn)了相應(yīng)的功能裁蚁,用戶只需要在使用時指定具體的緩存對象就可以動態(tài)地替換ImageLoader中的緩存策略矢渊。這就使得ImageLoader的緩存系統(tǒng)具有無限的可能性,也就保證了可擴(kuò)展性枉证。
開閉原則和里式替換原則往往是生死相依矮男,不離不棄的,通過里式替換來達(dá)到對擴(kuò)展開放室谚,對修改關(guān)閉的效果毡鉴。然而崔泵,這兩個原則都同時強(qiáng)調(diào)了一個OOP的重要特性——抽象,因此猪瞬,在開發(fā)過程中運(yùn)用抽象是走向代碼優(yōu)化的重要一步憎瘸。
依賴倒置原則
Dependence Inversion Principe
定義:是一種特殊的解耦形式,使得高層次的模塊不依賴于低層次的模塊的實(shí)現(xiàn)細(xì)節(jié)的目的陈瘦,依賴模塊被顛倒了幌甘。
這到底是什么意思呢?
依賴倒置原則有以下幾個關(guān)鍵點(diǎn):
- 高層模塊不應(yīng)該依賴低層模塊痊项,兩者都應(yīng)該依賴其抽象锅风;
- 抽象不應(yīng)該依賴細(xì)節(jié);
- 細(xì)節(jié)應(yīng)該依賴抽象鞍泉。
在Java中皱埠,抽象就是指接口或抽象類,兩者都是不能直接實(shí)例化的咖驮;細(xì)節(jié)就是實(shí)現(xiàn)類边器,實(shí)現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié),其特點(diǎn)就是托修,可以直接被實(shí)例化饰抒。高層模塊就是調(diào)用端,低層模塊就是具體實(shí)現(xiàn)類诀黍。依賴倒置原則在Java語言中的表現(xiàn)就是:模塊間的依賴通過抽象發(fā)生袋坑,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的眯勾。通俗點(diǎn)說就是面對接口(抽象)編程枣宫。
如果類與類直接依賴于細(xì)節(jié),那么他們之間就有直接的耦合吃环,當(dāng)具體實(shí)現(xiàn)需要變化時也颤,意味著要同時修改該依賴者的代碼,這限制了系統(tǒng)的可擴(kuò)展性郁轻。ImageLoader的例子一開始直接依賴MemoryCache翅娶,是一個具體實(shí)現(xiàn),而不是一個抽象類或者接口好唯。這導(dǎo)致了我們后面修改其他緩存實(shí)現(xiàn)就需要修改ImageLoader類的代碼竭沫。
最后版本的ImageLoader就很好的體現(xiàn)了依賴抽象,我們抽出的ImageCache的接口骑篙,并且定義了兩個方法蜕提。而ImageLoader依賴的是抽象(ImageCache接口),而不是具體的某個實(shí)現(xiàn)類靶端。當(dāng)需求發(fā)生變化時谎势,我們可以使用其他的實(shí)現(xiàn)替換原有的實(shí)現(xiàn)凛膏。
接口隔離原則
InterfaceSegregation Principe
定義:客戶端不應(yīng)該依賴它不需要的接口。另一種定義:類間的依賴關(guān)系應(yīng)該建議在最小的接口上脏榆。接口隔離原則將非常龐大猖毫、臃腫的接口拆分成更小的和更具體的接口,這樣客戶將會只需要知道他們感興趣的方法须喂。接口隔離原則的目的是系統(tǒng)解開耦合鄙麦,從而容易重構(gòu)、更改和重新部署镊折。
接口隔離原則說白了就是讓客戶端依賴的接口盡可能的小,這樣說可能還是有點(diǎn)抽象介衔,我們還是以一個例子來說明:
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這段代碼的可讀性非常差恨胚,各種try-catch嵌套都是些簡單的代碼,但是會嚴(yán)重影響代碼的可讀性炎咖,并且多層級的大括號很容易將代碼寫到錯誤的層級中赃泡。我們來看看如何解決這類問題。
我們可能知道Java中有一個Closeable接口乘盼,該接口標(biāo)識了一個可關(guān)閉的對象升熊,它只有一個close方法。FileOutputStream就實(shí)現(xiàn)這個接口绸栅。我們可以嘗試寫一個工具類级野,專門用于關(guān)閉Closeable接口的實(shí)現(xiàn)。
public final class CloseUtils {
private CloseUtils() {
}
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我們把這個工具類引用到上述的例子中看看效果:
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtils.closeQuietly(fileOutputStream);
}
}
是不是簡潔多了粹胯!而且這個closeQuietly方法可以運(yùn)用到各類可關(guān)閉的對象中蓖柔,保證了代碼的重用性。我們可以想想风纠,為什么close方法定義在FileOutputStream(或者它的父類)中况鸣,而是單獨(dú)用一個接口承載這個方法呢?從使用者的角度來看竹观,也就是這個CloseUtils工具類镐捧,其實(shí)只關(guān)心close方法,而不關(guān)心FileOutputStream的其他方法臭增,如果沒有這樣一個Closeable接口懂酱,closeQuietly(Closeable closeable)
方法的形參就得定義成FileOutputStream,會將沒有必要的其他方法暴露出來誊抛,并且其他同樣擁有close方法的類無法使用closeQuietly來關(guān)閉玩焰。
想一想Android中的OnClickListener以及OnLongClickListener,雖然都是點(diǎn)擊芍锚,但是并沒有定義在一個接口中昔园,而是分為兩個蔓榄,因?yàn)楹芏鄷r候,用戶只關(guān)心其中的一種默刚。
迪米特原則
Law of Demeter 或者也叫做最少知識原則(Least Konwledge Principe)
定義:一個對象應(yīng)該對其他對象有最少的了解甥郑。通俗點(diǎn)說,一個類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少荤西,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒有關(guān)系澜搅。類與類之間的關(guān)系越密切,耦合度越大邪锌,當(dāng)一個類發(fā)生改變時勉躺,對另一個類的影響也越大。
下面我們用租房為例來講講迪米特原則的應(yīng)用觅丰。
房間:
public class Room {
public float area;
public float price;
public Room(float area, float price) {
this.area = area;
this.price = price;
}
@Override
public String toString() {
return "Room [area=" + area + ",price=" + price + "]";
}
}
房間:
public class Mediator {
List<Room> mRooms = new ArrayList<>();
public Mediator() {
for (int i = 0; i < 5; i++) {
mRooms.add(new Room(14 + i, (14 + i) * 1500));
}
}
public List<Room> getAllRooms() {
return mRooms;
}
}
租客:
public class Tenant {
public float roomArea;
public float roomPrice;
public static final float diffPrice = 100.0001f;
public static final float diffArea = 0.00001f;
public void rentRoom(Mediator mediator) {
List<Room> allRooms = mediator.getAllRooms();
for (Room room : allRooms) {
if (isSuitable(room)) {
System.out.println("租到房間啦饵溅!" + room);
}
}
}
private boolean isSuitable(Room room) {
return Math.abs(room.price - roomPrice) < diffPrice
&& Math.abs(room.area - roomArea) < diffArea;
}
}
從代碼中可以看到,Tenant不僅依賴了Mediator類妇萄,還需要頻繁地與Room類打交道蜕企。如果把檢測條件都放在Tenant類中,那么中介類的功能就被弱化了冠句,導(dǎo)致Tenant與Room的耦合轻掩,因?yàn)門enant必須知道許多關(guān)于Room的細(xì)節(jié),當(dāng)Room變化時Tenant也必須跟著變化懦底。就像下面UML描述的那樣:
既然耦合太嚴(yán)重了唇牧,那我們就只能解耦了。首先要明確的時聚唐,我們只和必要的類通信奋构,即移除Tenant與Room的依賴。我們進(jìn)行如下修改:
public class Mediator {
List<Room> mRooms = new ArrayList<>();
public Mediator() {
for (int i = 0; i < 5; i++) {
mRooms.add(new Room(14 + i, (14 + i) * 1500));
}
}
public Room rentOut(float area, float price) {
for (Room room : mRooms) {
if (isSuitable(area, price, room)) {
return room;
}
}
return null;
}
private boolean isSuitable(float area, float price, Room room) {
return Math.abs(room.price - price) < Tenant.diffPrice
&& Math.abs(room.area - area) < Tenant.diffArea;
}
}
public class Tenant {
public float roomArea;
public float roomPrice;
public static final float diffPrice = 100.0001f;
public static final float diffArea = 0.00001f;
public void rentRoom(Mediator mediator) {
System.out.println("租到房間啦拱层!" + mediator.rentOut(roomArea, roomPrice));
}
}
重構(gòu)后的UML圖如下:
只是將對Room的判定操作移到了Mediator類中弥臼,這本應(yīng)該是Mediator的職責(zé),根據(jù)租戶設(shè)定的條件查找符合要求的房子根灯,并且將結(jié)果交給租戶就可以了径缅。租戶并不需要知道太多關(guān)于Room的細(xì)節(jié)。