0. 序言
- 這篇博客從實(shí)踐栗子出發(fā),通俗易懂的講解單一職責(zé)原則適用的場(chǎng)景,以及單一職責(zé)原則所應(yīng)該把握的"適度".希望閱讀完本文可以更好的對(duì)代碼的可讀性、可維護(hù)性姥卢、復(fù)雜度、變革風(fēng)險(xiǎn)有進(jìn)一步的認(rèn)識(shí)渣聚。
- 博客目錄如下:
- 單一職責(zé)原則的定義
- 單一職責(zé)原則的優(yōu)缺點(diǎn)
- 用正反栗子講解如何在接口独榴、類(lèi)和方法中保持單一職責(zé)原則。
- 用正反栗子講解如何保持單一職責(zé)原則的“適度”
1. 定義
- 就一個(gè)類(lèi)而言饵逐,應(yīng)該僅有一個(gè)引起它變化的原因括眠。
2. 優(yōu)點(diǎn)
- 類(lèi)的復(fù)雜性降低:實(shí)現(xiàn)什么職責(zé)都有清晰明確的定義。
- 可讀性提高:復(fù)雜性降低倍权,那當(dāng)然可讀性提高了掷豺。
- 可維護(hù)性提高:可讀性提高捞烟,那當(dāng)然更容易維護(hù)。
- 變革引起的風(fēng)險(xiǎn)降低:變更是必不可少的当船,如果接口的單一職責(zé)做得好题画,一個(gè)接口修改只對(duì)相應(yīng)的實(shí)現(xiàn)類(lèi)有影響,對(duì)其他的接口無(wú)影響德频,這對(duì)系統(tǒng)的擴(kuò)展性和維護(hù)性都有非常大的幫助苍息。
3. 缺點(diǎn)
- 單一職責(zé)原則提出了一個(gè)編寫(xiě)程序的標(biāo)準(zhǔn),用“職責(zé)”或“變化原因”來(lái)衡量接口或者類(lèi)設(shè)計(jì)的是否優(yōu)良壹置,但是“職責(zé)”和“變化原因”都是不可度量的竞思,因項(xiàng)目而異,因環(huán)境而異钞护,因而有時(shí)候飽受爭(zhēng)議盖喷。
4. 栗子
單一職責(zé)原則適用于接口、類(lèi)和方法难咕。
- 接口:
反面教材:
public interface IUserInfo {
void setUserID(String userID);
String getUserID();
void setPassword(String password);
String getPassword();
void setUserName(String userName);
String getUserName();
boolean changePassword(String oldPassword);
boolean deleteUser();
void mapUser();
boolean addOrg(int orgID);
boolean addRole(int roleID);
}
問(wèn)題所在:用戶的屬性和用戶的行為沒(méi)有分開(kāi)课梳。
正面栗子:
//BO(Business Object) 業(yè)務(wù)對(duì)象
public interface IUserBO {
void setUserID(String userID);
String getUserID();
void setPassword(String password);
String getPassword();
void setUserName(String userName);
String getUserName();
}
//Biz(Business Logic) 業(yè)務(wù)邏輯
public interface IUserBiz {
boolean changePassword(String oldPassword);
boolean deleteUser();
void mapUser();
boolean addOrg(int orgID);
boolean addRole(int roleID);
}
修正:兩個(gè)接口有各自不同的實(shí)現(xiàn)類(lèi),職責(zé)單一余佃,分工明確暮刃。
- 類(lèi):
反面教材:
public class ImageLoader {
//圖片緩存
LruCache<String, Bitmap> mImageCache;
//線程池,線程數(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);
//取四分之一的可用內(nèi)存作為緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
private 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);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
問(wèn)題:創(chuàng)建線程池爆土、創(chuàng)建LruCache椭懊、下載圖片、設(shè)置圖片都放在了一個(gè)類(lèi)步势,耦合性太強(qiáng)灾搏,觸一發(fā)動(dòng)全身。
正面栗子:
/**
* Created by FuKaiqiang on 2017-10-10.
* 圖片緩存類(lèi)
*/
public class ImageCache {
//圖片Lru緩存
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
private void initImageCache() {
//計(jì)算可使用的最大內(nèi)存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取四分之一的可用內(nèi)存作為緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
}
/**
* Created by FuKaiqiang on 2017-10-10.
* 圖片加載類(lèi)
*/
public class ImageLoader {
//圖片緩存
ImageCache mImageCache = new ImageCache();
//線程池立润,線程數(shù)量為CPU的數(shù)量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//加載圖片
private 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);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
修正:
① ImageLoader負(fù)責(zé)圖片加載,ImageCache負(fù)責(zé)圖片緩存媳板,ImageLoader代碼量變少桑腮,職責(zé)清晰。
② 當(dāng)緩存相關(guān)的邏輯變動(dòng)時(shí)蛉幸,不需要修改ImageLoader類(lèi)破讨,當(dāng)圖片加載相關(guān)的邏輯變動(dòng)時(shí),不需要修改緩存處理邏輯奕纫。
- 方法
反面教材:
public interface IUserManager {
void changeUser(String newUserName, String newHomeAddress, String telNumber);
}
問(wèn)題:方法職責(zé)不清晰提陶,不單一。
正面栗子:
public interface IUserManager {
void changeUserName(String newsUserName);
void changeHomeAddress(String newHomeAddress);
void changeOfficeTel(String telNumber);
}
修正:職責(zé)單一匹层,分工明確隙笆,你我他都好锌蓄。
5. 進(jìn)階
- 我們首先看一個(gè)接口和它的實(shí)現(xiàn)類(lèi):
public interface IPhone {
//撥通電話
public void dial(String phoneNumber);
//通話
public void chat(Object o);
//通話完畢,掛電話
public void hangup();
}
問(wèn)題:它包含兩個(gè)職責(zé):一個(gè)是協(xié)議管理撑柔,一個(gè)是數(shù)據(jù)傳輸瘸爽。
public class Phone implements IPhone {
@Override
public void dial(String phoneNumber) {
}
@Override
public void chat(Object o) {
}
@Override
public void hangup() {
}
}
- 我們對(duì)之進(jìn)行改進(jìn):
public interface IConnectionManager {
//撥通電話
public void dial(String phoneNumber);
//通話完畢,掛電話
public void hangup();
}
public interface IDataTransfer {
//通話
public void chat(Object o);
}
public class ConnectionManager implements IConnectionManager {
@Override
public void dial(String phoneNumber) {
}
@Override
public void hangup() {
}
}
public class DataTransfer implements IDataTransfer {
@Override
public void chat(Object o) {
}
}
改進(jìn):
① 兩個(gè)接口兩個(gè)實(shí)現(xiàn)類(lèi)----導(dǎo)致了Phone類(lèi)要把兩個(gè)實(shí)現(xiàn)類(lèi)組合在一起才可以使用铅忿。
② 然而組成是一種強(qiáng)耦合關(guān)系剪决,有共同的生命周期,這樣的耦合關(guān)系還不如使用接口實(shí)現(xiàn)的方式檀训,而且還正價(jià)了類(lèi)的復(fù)雜性柑潦,多了兩個(gè)類(lèi)。
- 最終改進(jìn)如下:
public interface IConnectionManager {
//撥通電話
public void dial(String phoneNumber);
//通話完畢峻凫,掛電話
public void hangup();
}
public interface IDataTransfer {
//通話
public void chat(Object o);
}
public class Phone implements IConnectionManager, IDataTransfer {
@Override
public void dial(String phoneNumber) {
}
@Override
public void chat(Object o) {
}
@Override
public void hangup() {
}
}
進(jìn)一步改進(jìn):
- 這樣的設(shè)計(jì)才是完美的渗鬼,一個(gè)類(lèi)實(shí)現(xiàn)了兩個(gè)接口,把兩個(gè)職責(zé)融合在一個(gè)類(lèi)中蔚晨。
- 你肯定會(huì)問(wèn)這個(gè)Phone有兩個(gè)原因引起變化了呀乍钻,是的,但是別忘記了我們是面向接口編程铭腕,我們對(duì)外公布的是接口而不是實(shí)現(xiàn)類(lèi)银择。
- 如果真要實(shí)現(xiàn)類(lèi)的單一職責(zé),我們就必須使用上面的組合模式了累舷,這會(huì)引起類(lèi)間耦合過(guò)重浩考、類(lèi)的數(shù)量增加等問(wèn)題,人為增加了設(shè)計(jì)的復(fù)雜性被盈。
6. 后續(xù)
如果大家喜歡這篇文章析孽,麻煩點(diǎn)贊。
如果想看更多 設(shè)計(jì)模式 方面的技術(shù)只怎,歡迎關(guān)注袜瞬!