SOLID+迪米特原則
在應(yīng)用開發(fā)的過程中,最難的往往不是開發(fā)的工作,而是在后續(xù)的升級維護過程中讓應(yīng)用系統(tǒng)能夠靈活地變化!也就是我們經(jīng)常強調(diào)的代碼健壯性和穩(wěn)定性铅檩!在滿足用戶需求的前提下,不破壞系統(tǒng)穩(wěn)定性的前提下保持高度可擴展性莽鸿,高內(nèi)聚低耦合昧旨,在經(jīng)歷各個版本后依舊保持清晰靈活穩(wěn)定的系統(tǒng)架構(gòu),那么編程的時候應(yīng)當(dāng)盡量根據(jù)面向?qū)ο笞兂傻牧笤瓌t來做祥得。
<h3>S 單一職責(zé)原則(Single Responsibility Principle)</h3>
解釋:
就一個類而言兔沃,應(yīng)該僅有一個引起他變化的原因,簡單來說级及,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)乒疏、數(shù)據(jù)的封裝!
<pre>
我們來寫一個很簡單的類吧饮焦!寫一個緩存圖片的類
public class ImageLoader{
LurCache<String,Bitmap> caches;
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public ImageLoader(){
initImageCache();
}
//顯示圖片
public void display(String url,ImageView imageview){
Bitmap bm = caches.get(url);
if(bm!=null){
imageview.setImageBitmap(bm);
return;
}
imageview.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bm = downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bm);
}
cache.putBitmapToCache(url, bitmap);
}
});
}
//初始化緩存參數(shù)
public void initImageCache(){
long maxMemory = Runtime.getRuntime().maxMemory()/1024;
int cacheSize = (int) maxMemory/4;
mImageCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;
}
};
}
//獲取圖片
public Bitmap get(String url){
return caches.get(url);
}
//存儲圖片
public void put(String url,Bitmap bm){
if(cache!=null){
cache.put(url,bm);
}
}
//下載圖片
public Bitmap downloadImage(String imageUrl) {
Bitmap bm = null;
try {
URL url = new URL(imageUrl);
URLConnection connection = url.openConnection();
bm = BitmapFactory.decodeStream(connection.getInputStream());
return bm;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bm;
}
}
</pre>
上面這個類怕吴,有三個功能,獲取網(wǎng)絡(luò)圖片县踢,顯示圖片转绷,緩存處理,單一職責(zé)就是要一個類實現(xiàn)一個功能硼啤,那么需要把兩個功能給分開暇咆。那就變成了
<pre>
public class ImageDownloader{
//下載圖片
public static Bitmap downloadImage(String imageUrl) {
Bitmap bm = null;
try {
URL url = new URL(imageUrl);
URLConnection connection = url.openConnection();
bm = BitmapFactory.decodeStream(connection.getInputStream());
return bm;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bm;
}
}
</pre>
<pre>
public class ImageCache{
LrcCache<String,Bitmap> caches;
public ImageCache(){initImageCache();
public void initImageCache(){
long maxMemory = Runtime.getRuntime().maxMemory()/1024;
int cacheSize = (int) maxMemory/4;
mImageCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;
}
};
}
//獲取圖片
public Bitmap get(String url){
return caches.get(url);
}
//存儲圖片
public void put(String url,Bitmap bm){
if(cache!=null){
cache.put(url,bm);
}
}
}
</pre>
<pre>
public class ImageLoader{
ImageCache cache = new ImageCache();
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//加載圖片
public void display(final String url, final ImageView imageView) {
final Bitmap bitmap = cache.getBitmapFromCache(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bm = ImageDownloader.downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bm);
}
cache.putBitmapToCache(url, bitmap);
}
});
}
}
</pre>
<h3>O 開閉原則(Open Close Principle)</h3>
解釋:
軟件中的對象(類,模塊丙曙,函數(shù)等)對擴展來說是開放的,對修改是封閉的其骄。
在軟件的生命周期內(nèi)亏镰,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時拯爽,可能會將錯誤引入原本已經(jīng)經(jīng)過測試的就代碼中索抓,破壞原有系統(tǒng)。
當(dāng)軟件需要變化時毯炮,我們應(yīng)該盡量通過擴展的方式來實現(xiàn)變化逼肯,而不是通過修改該已有的代碼來實現(xiàn)。
<pre>
還是用回上一個例子擴展代碼桃煎,而不修改代碼篮幢,要做到這樣應(yīng)該怎么改之前的代碼呢,例如加上一個本地圖片的功能還有設(shè)置允許緩存为迈,那我們需要新建一個DiskCache類和一個DoubleCache類三椿,Cache類都需要get和put房方法缺菌,所以我們只要將ImageCache作為接口讓所有緩存的類都繼承這個類就可以了
//接口實現(xiàn)類
public class DiskCache extends ImageCache {
String cachePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath();
@Override
public void put(String fileUrl, Bitmap bm) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(cachePath + File.separator + fileUrl);
bm.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cachePath + File.separator + url);
}
}
//接口類
public abstract class ImageCache {
public abstract void put(String url, Bitmap bm);
public abstract Bitmap get(String url);
}
//接口實現(xiàn)類
public class DoubleCache extends ImageCache {
MemoryCache mImageCache = new MemoryCache();
DiskCache dImageCache = new DiskCache();
@Override
public void put(String url, Bitmap bm) {
mImageCache.put(url, bm);
mImageCache.put(url, bm);
}
@Override
public Bitmap get(String url) {
Bitmap bm = mImageCache.get(url);
if (bm == null) {
return dImageCache.get(url);
}
return bm;
}
}
最后在ImageLoader里面添加一個依賴注入方法
public void setCache(ImageCache cache) {this.cache = cache;}
就實現(xiàn)了
</pre>
<h3>L 里氏替換原則(Liskov Substitution Principle)</h3>
解釋:
所有引用基類的地方必須能透明地使用其子類的對象。
只要父類能出現(xiàn)的地方子類就可以出現(xiàn)搜锰,而且替換成子類也不會產(chǎn)生任何錯誤或異常
里氏替換原則的核心原理是抽象伴郁,抽象又依賴于繼承這個特性,在OOP當(dāng)中蛋叼,繼承的優(yōu)缺點都相當(dāng)明顯:
優(yōu)點:
- 代碼重用焊傅,減少創(chuàng)建類的成本,每個子類都擁有父類的方法和屬性
- 子類與父類基本想死狈涮,但又與父類有所區(qū)別
- 提高代碼的可擴展性
缺點:
- 繼承是侵入性的狐胎,只要繼承就必須擁有父類所有屬性和方法**
- 可能造成子類代碼冗余,靈活度降低**
<pre>
那么父類和子類在上面的代碼哪里顯示了薯嗤?就是一下這句依賴注入
public void setCache(ImageCache cache) { this.cache = cache;}
作為ImageCache的子類顽爹,DiskCache和DoubleCache都可以帶入,而不會影響到原本的結(jié)果
</pre>
<h3>I 接口隔離原則 (Interface Segregation Principle)</h3>
解釋:
客戶端不應(yīng)該依賴他不需要的接口
類的依賴關(guān)系應(yīng)該建立在最小的接口上
讓客戶端依賴的接口盡可能小
<pre>
接口隔離原則骆姐,其實相比來說是更簡單镜粤,當(dāng)前類如果功能上無相關(guān),則可以新建一個類專門處理該接口玻褪。
例如上面代碼的這部分:
try {
fos = new FileOutputStream(cachePath + File.separator + fileUrl);
bm.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
</pre>
FileOutputStream的close方式所導(dǎo)致的IOException肉渴,在這里顯得多余,所以把它隔離開新增個類來封裝好
<pre>public class CloseUtils {
public static void closeQuietly(Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}}}
變成以下代碼
try {
fos = new FileOutputStream(cachePath + File.separator + fileUrl);
bm.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtils.closeQuietly(fos);//接口隔離带射,在另一個實現(xiàn)類里面負責(zé)
}
</pre>
<h3>D 依賴倒置原則(Dependence Inversion Principle)</h3>
解釋:
高層模塊不應(yīng)該依賴低層模塊同规,兩者之間都應(yīng)該依賴抽象
抽象不應(yīng)該依賴細節(jié)(實現(xiàn)類),細節(jié)應(yīng)該依賴抽象
模塊間的依賴關(guān)系通過抽象發(fā)生窟社,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系券勺,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的。
for ex:
如果是直接實現(xiàn)類與實現(xiàn)類之間發(fā)生聯(lián)系灿里,那么MemoryCache和ImageLoader关炼,就會有沖突,所以需要一個setCache方法來設(shè)置依賴,以后如果需要修改的話匣吊,寫類直接繼承ImageCache或其子類儒拂,就可以了
<pre>
實體類與實體類之間,如果發(fā)生改變色鸳,通過依賴注入的方式更改
ImageLoader.setCache(ImageCache cache);
</pre>
<h3>最少知識原則 (迪米特原則)(Dependence Inversion Principle)</h3>
解釋:
一個對象應(yīng)該對其它對象有最小的了解社痛。或者一個類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少命雀,類的內(nèi)部如何實現(xiàn)與調(diào)用者或者依賴者關(guān)系都沒關(guān)系蒜哀,調(diào)用者只需要它需要的方法就可以了
迪米特原則,是我覺得最為容易理解的咏雌!一個類凡怎,負責(zé)一個功能校焦,那么盡量地它必須要和其它的類之間的關(guān)系少一點!只和直接接觸的類進行聯(lián)系统倒,例如寨典,找房子,租戶的條件需要篩選價格地區(qū)房匆,租戶從中介中找到房源耸成,然后查看哪家好,下結(jié)論浴鸿,這怎么可能井氢!我們都找中介了,那么要把自己的條件和中介說岳链,說好了再看花竞,那么我們的直接關(guān)系對象就是中介。所以租戶其實是沒必要和房間數(shù)據(jù)做交流掸哑,不過現(xiàn)實我們還是要去看房子约急。
第二個例子,上面寫的ImageCache及其子類苗分,ImageLoader.setCache(ImageCache cache),用戶根本不需要知道ImageCache里面具體的實現(xiàn)是啥厌蔽,我們只需要知道ImageCache是怎么使用的,就是里面的put和get摔癣!因為ImageCache才是和ImageLoader直接打交道的類奴饮,DiskCache和DoubleCache是ImageCache具體的實現(xiàn)類,But who care择浊,用戶又不關(guān)心這個戴卜。
<h3>總結(jié)</h3>
面向?qū)ο笳Z言的三大特點:繼承、封裝琢岩,多態(tài)叉瘩,而程序擴展性取決于代碼的耦合度,保持一個程序高類聚低耦合是我們寫代碼需要注意的事情粘捎。當(dāng)然,這只是個理想的狀態(tài)危彩。沒有百分百攒磨,不過,靈活使用接口汤徽,盡量做到這幾個原則娩缰,未免以后太多Bug的出現(xiàn)。我在寫這篇文章的時候谒府,在想面向?qū)ο缶幊毯孟窈芏喽寂c接口相關(guān)拼坎,利用接口作為父類(里氏替換浮毯,開閉原則),添加依賴注入(依賴倒置)泰鸡,然后代碼功能分離(單一職責(zé)债蓝,接口隔離),數(shù)據(jù)邏輯和頁面分離(迪米特原則)盛龄,好像也可以這樣解釋饰迹,當(dāng)然,畢竟是設(shè)計思想余舶,看自己怎么去理解