或許有的掘友們發現了,在閱讀 Android 系統底層源碼或者開源框架源碼時,發現內部大量的設計模式,若是你對設計模式不懂的話,那麼閱讀源碼真的是步履維艱。那麼這篇文章咱們先來學習面向對象的六大原則,設計模式大概 23 種,後面咱們一步一步來學習它。html
單一職責原則的英文名稱是 Single Responsibility Principle ,縮寫是 SRP 。 SRP 的定義是:就一個類而言,應該僅有一個引發變化的緣由。簡單的來講,就是一個類中應該是一組相關性很高的函數、數據的封裝。單一職責的劃分界限也並非那麼的清晰,不少時候都是靠我的經驗來給定界限,固然,最大的的問題就是對職責的定義,什麼是類的職責,以及怎麼劃分類的職責。java
下面咱們就以 圖片加載庫 的例子代碼來對類的職責簡單說明下,在設計一個圖片加載庫以前,咱們須要先大概畫下 UML 類圖,有了 UML 圖以後寫代碼就能更加的清晰。android
從上面 UML 類圖能夠看出 ImageLoader 只負責加載圖片,MemoryCache 實現 IImageCache 負責往內存中存/取緩存,到這裏也許有的同窗對單一職責有了必定概念了,相信看完下面的代碼,你已經對單一職責掌握的差很少了,直接上代碼git
public class ImageLoader {
/** * 內存緩存 */
private IImageCache mMemoryCache;
/** * 圖片下載 */
private IDownloader mImageDownloader;
/** * 線程池 */
private ExecutorService mExecutorService;
/** * 主線程管理 */
private Handler mHandler = new Handler(Looper.getMainLooper());
private static ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null)
instance = new ImageLoader();
return instance;
}
public ImageLoader() {
//圖片緩存
this. mMemoryCache = new MemoryCache();
//圖片下載
this.mImageDownloader = new HttpURLConnectionDownloaderImp();
//線程池,線程數據量爲 CPU 的數量
this.mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/** * 加載圖片 */
public void loadImage(final String url, final ImageView imageView) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
//若是內存緩存中沒有圖片,就開啓網絡請求去下載
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap imager = mImageDownloader.downLoader(url);
if (imager == null) return;
if (imageView.getTag().equals(url)) {
displayImage(imager, imageView);
}
mMemoryCache.put(url,imager);
}
});
}
/** * 顯示圖片 * * @param downBitmap * @param imageView */
private void displayImage(final Bitmap downBitmap, final ImageView imageView) {
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(downBitmap);
}
});
}
}
複製代碼
public class MemoryCache implements IImageCache {
/** * 初始化內存緩存 */
private LruCache<String, Bitmap> mMemoryLru;
public MemoryCache() {
init();
}
private void init() {
int currentMaxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//內存緩存的大小
int cacheSize = currentMaxMemory / 4;
mMemoryLru = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
@Override
public void put(String url, Bitmap bitmap) {
mMemoryLru.put(url,bitmap);
}
@Override
public Bitmap get(String url) {
return mMemoryLru.get(url);
}
}
複製代碼
經過上面代碼能夠看出 ImageLoader 負責圖片加載的邏輯,而 MemoryCache 負責緩存,這 2 個類職責分明,就像公司裏面不一樣部門幹不一樣的活同樣。可是,若是這 2 類寫在一塊兒的話,缺點一下就出來了,不只功能職責不分明,並且代碼也比較臃腫,耦合過重。 如今雖然代碼結構變得清晰,職責也分明瞭,可是可擴展性還須要進一步優化,下面咱們就來慢慢優化吧。github
開閉原則英文全稱是 Open Close Principle,縮寫 OCP ,它是 Java 世界裏最基礎的設計原則,它指導咱們如何創建一個穩定的、靈活的系統。編程
開閉原則的定義是:軟件中的對象 (類、模塊、函數等) 應該對於擴展是開放的,可是,對於修改是封閉的 這就是開放-關閉原則。設計模式
上一小節的 ImageLoader 職責單一,結構清晰,應該算是一個不錯的開始了,可是 Android 中應用內存是有限制的,當應用從新啓動,那麼原有的緩存就不在了。如今咱們加上本地磁盤緩存,爲了聽從開閉原則的思想,我又對 ImageLoader 從新設計了。緩存
public class ImageLoader {
private String TAG = getClass().getSimpleName();
/** * 默認內存緩存 */
private IImageCache mMemoryCache;
/** * 線程池 */
private ExecutorService mExecutorService;
/** * 主線程管理 */
private Handler mHandler = new Handler(Looper.getMainLooper());
private static ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null)
instance = new ImageLoader();
return instance;
}
public ImageLoader() {
mMemoryCache = new MemoryCache();
//線程池,線程數據量爲 CPU 的數量
mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/** * 用戶配置緩存策略 * * @param imageCache */
public void setImageCache(IImageCache imageCache) {
this.mMemoryCache = imageCache;
}
/** * 加載圖片 */
public void loadImage(final String url, final ImageView imageView) {
.....
}
/** * 顯示圖片 * * @param downBitmap * @param imageView */
private void displayImage(final Bitmap downBitmap, final ImageView imageView) {
.....
}
}
複製代碼
//磁盤緩存
public class DiskCache implements IImageCache {
private DiskLruCache mDiskLruCache;
private static final int MAX_SIZE = 10 * 1024 * 1024;//10MB
//IO緩存流大小
private static final int IO_BUFFER_SIZE = 8 * 1024;
//緩存個數
private static final int DISK_CACHE_INDEX = 0;
public DiskCache(Context context) {
try {
File cacheDir = CacheUtils.getDiskCacheDir(context, "bitmapCache");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, ImageLoaderUtils.getAppVersion(context), 1, MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void put(String url, Bitmap bitmap) {
OutputStream outputStream = null;
DiskLruCache.Snapshot snapshot = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
String key = ImageLoaderUtils.hashKeyForDisk(url);
try {
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
InputStream inputStream = ImageLoaderUtils.bitmap2InputStream(bitmap, 50);
in = new BufferedInputStream(inputStream, IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
editor.commit();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (snapshot != null) {
snapshot.close();
}
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public Bitmap get(String url) {
//經過key值在緩存中找到對應的Bitmap
Bitmap bitmap = null;
String key = ImageLoaderUtils.hashKeyForDisk(url);
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot == null) return null;
//獲得文件輸入流
InputStream fileInputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (fileInputStream != null)
bitmap = BitmapFactory.decodeStream(fileInputStream);
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
複製代碼
public class DoubleCache implements IImageCache {
private String TAG = getClass().getSimpleName();
/**
* 內存緩存
*/
private IImageCache mMemoryCache;
/**
* 磁盤緩存
*/
private IImageCache mDiskCache;
public DoubleCache(Context context) {
this.mMemoryCache = new MemoryCache();
this.mDiskCache = new DiskCache(context);
}
@Override
public void put(String key, Bitmap bitmap) {
mMemoryCache.put(key, bitmap);
mDiskCache.put(key, bitmap);
}
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap != null) {
Log.i(TAG,"使用內存緩存");
return bitmap;
}
Log.i(TAG,"使用磁盤緩存");
return mDiskCache.get(url);
}
}
複製代碼
public interface IImageCache {
/** * 存圖片 */
void put(String url, Bitmap bitmap);
/** * 獲取圖片 */
Bitmap get(String url);
}
複製代碼
IImageCache 接口簡單定義了 存儲/獲取 兩個函數,緩存的 url 就是圖片網絡地址,值就是緩存的圖片,通過此次重構咱們擴展了內存/磁盤緩存,細心的同窗可能注意到了, ImageLoader 類中增長了一個 setImageCache (IImageCache cache) 函數,用戶能夠經過該函數來設置緩存,也就是一般說的依賴注入。下面看看怎麼配置:bash
public void config() {
//使用雙緩存
ImageLoader.getInstance().setImageCache(new DoubleCache(getApplicationContext()));
//用戶自定義
ImageLoader.getInstance().setImageCache(new IImageCache() {
@Override
public void put(String url, Bitmap bitmap) {
}
@Override
public Bitmap get(String url) {
return null;
}
});
}
複製代碼
在上述代碼中,經過 setImageCache() 方法注入不一樣的緩存實現,這樣不只可以使 ImageLoader 更簡單,健壯,也使得 ImageLoader 的可擴展性,靈活性能高,MemoryCache 、DiskCache 、DoubleCache 緩存圖片的具體實現徹底同樣,可是,他們的一個特色是都實現了 ImageCache 接口,而且經過 setImageCache() 注入到 IImageCache 中,這樣就實現了變幻無窮的緩存策略,且擴展不會致使內部的修改,哈哈,這就是咱們以前所說的開閉原則。網絡
里氏替換原則英文全稱是 Liskov Substitution Principle , 縮寫是 LSP。LSP 的第一種定義是:若是對每個類型爲 S 的對象 O1, 都有類型爲 T 的對象 O2, 使得以 T 定義的全部程序 P 在全部的對象 O1都替換成 O2 時,程序 P 的行爲沒有發生變化,那麼類型 S 是類型 T 的子類型。上面這種描述確實有點很差理解,咱們再來看第二種里氏替換原則定義:全部引用基類的地方必須能透明地使用其子類的對象。
咱們知道,面嚮對象語言的三大特色是 繼承,封裝,多態,里氏替換原則就是依賴於 繼承,多態這兩大特性。里氏替換原則通俗來講的話就是,只要父類能出現的地方子類就能夠出現,並且替換爲子類也不會產生任何錯誤或異常,使用者可能根本不用知道是父類仍是子類,可是反過來就不行了,有子類出現的地方,父類就不必定能適應,說了這麼多,其實最終總結就兩個字:抽象。
爲了咱們可以深刻理解直接看下面代碼示例吧:
//框口類
public class Window{
public void show(View view){
view.draw();
}
}
//創建視圖對象,測量視圖的寬高爲公用代碼,繪製實現交給具體的子類
pubic abstract class View{
public abstract void draw();
public vid measure(int width,int height){
//測量視圖大小
}
}
public class ImageView extends View{
draw{
//繪製圖片
}
}
... extends View{
...
}
複製代碼
上述示例代碼中, Window 依賴於 View , 而 View 定義了一個視圖抽象, measure 是各個子類共享的方法,子類經過重寫 View 的draw 方法實現具備各自特點的功能,在這裏,這個功能就是繪製自身的內容,在任何繼承 View 類的子類均可以傳遞給 show 函數,這就是所說的里氏替換。
里氏替換原則的核心原理是抽象,抽象又依賴於繼承這個特性,在 OOP 當中,繼承的優缺點都至關明顯,優勢:
繼承的缺點:
事務都是都利和弊,須合理利用。
繼續拿上面的 ImageLoader 緩存策略來講明裏氏替換原則,用戶只須要指定具體的緩存對象就能夠經過 ImageCache 的 setImageCache() 函數就能夠替換 ImageLoader 的緩存策略,這就使得 ImageLoader 的緩存系統有了無限的可能性,也保證了可擴展性。
開閉和里氏每每是生世相依,不離不棄,經過里氏替換來達到程序的擴展,對修改的關閉效果。然而,這兩個原則都同時強調了一個 OOP 的重要性 - 抽象,所以,在開發過程當中,運用抽象是走向代碼優化的重要一步。
依賴倒置原則英文全稱是 Dependence Inversion Principle, 簡寫 DIP 。依賴倒置原則指代了一種特定的解耦形式,使得高層次的模塊不依賴於底層次模塊的實現細節的目的,依賴模塊被顛倒了。這個概念有點很差理解,這究竟是什麼意思勒?
依賴倒置有幾個關鍵點:
在 Java 語言中,抽象就是接口或抽象類,二者都是不能直接被實例化的;細節就是實現類,其特色就是能夠直接實例化,也就是能夠加上一個 new 關鍵字產生一個對象。高層模塊就是調用端,底層模塊就是具體實現類。依賴倒置原則在 Java 語言中的表現就是: 模塊間的依賴經過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是經過接口或抽象類產生的 ,這又是一個將理論抽象化的實例,其實一句話能夠歸納:面向接口編程,或者說是面向抽象編程,面向接口編程是面向對象精髓之一,也就是上面兩節強調的抽象。
這裏咱們仍是以 ImageLoader 來講明,先看下面代碼:
public class ImageLoader {
private String TAG = getClass().getSimpleName();
/** * 默認內存緩存(直接依賴於細節,而不是抽象) */
private MemoryCache mMemoryCache;
/** * 線程池 */
private ExecutorService mExecutorService;
/** * 主線程管理 */
private Handler mHandler = new Handler(Looper.getMainLooper());
private static ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null)
instance = new ImageLoader();
return instance;
}
public ImageLoader() {
mMemoryCache = new MemoryCache();
//線程池,線程數據量爲 CPU 的數量
mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/** * 用戶配置緩存策略 * * @param imageCache */
public void setImageCache(MemoryCache imageCache) {
this.mMemoryCache = imageCache;
}
...
}
複製代碼
上面代碼 ImageLoader 直接依賴於細節 MemoryCache ,若是框架升級需有多級緩存也就是內存 + SD 卡緩存策略,那麼就又須要改 ImageLoader 中的代碼,以下:
public class ImageLoader {
private String TAG = getClass().getSimpleName();
/** * 默認內存緩存(直接依賴於細節,而不是抽象) */
private DoubleCache mMemoryCache;
/** * 線程池 */
private ExecutorService mExecutorService;
/** * 主線程管理 */
private Handler mHandler = new Handler(Looper.getMainLooper());
private static ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null)
instance = new ImageLoader();
return instance;
}
public ImageLoader() {
mMemoryCache = new DoubleCache();
//線程池,線程數據量爲 CPU 的數量
mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/** * 用戶配置緩存策略 * * @param imageCache */
public void setImageCache(DoubleCache imageCache) {
this.mMemoryCache = imageCache;
}
...
}
複製代碼
在 ImageLoader 中咱們把默認內存緩存改爲了雙緩存,這樣不只違背了沒有開閉原則,也沒有依賴於抽象,因此下面的代碼纔是正確的:
public class ImageLoader {
private String TAG = getClass().getSimpleName();
/** * 默認內存緩存 默認依賴於抽象 */
private IImageCache mMemoryCache;
private static ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null)
instance = new ImageLoader();
return instance;
}
public ImageLoader() {
...
}
/** * 用戶配置緩存策略 注入抽象類 * * @param imageCache */
public void setImageCache(IImageCache imageCache) {
this.mMemoryCache = imageCache;
}
}
複製代碼
在這裏實現類沒有發生直接的依賴,而是經過抽象發生的依賴。知足了依賴倒置基本原則,想要讓程序更爲靈活,那麼抽象就是邁出靈活的第一步。
接口隔離原則英文全稱是
InterfaceSegregation Principles, 縮寫 ISP 。接口隔離原則的目的是系統解耦,從而容易重構、更改和從新部署。說白了就是讓客服端依賴的接口儘量地小,這樣說可能還有點抽象,仍是以一個示例說明一下
未優化的接口
public class DiskCache implements IImageCache {
private DiskLruCache mDiskLruCache;
private static final int MAX_SIZE = 10 * 1024 * 1024;//10MB
//IO緩存流大小
private static final int IO_BUFFER_SIZE = 8 * 1024;
//緩存個數
private static final int DISK_CACHE_INDEX = 0;
@Override
public void put(String url, Bitmap bitmap) {
.....
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (snapshot != null) {
snapshot.close();
}
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
複製代碼
能夠看見上面一段代碼雖然功能達到了要求,可是各類 try...catch 嵌套,不經影響代碼美觀,並且可讀性差。咱們能夠看 Cloaseable 這個類的實現差很少 160 多個實現類,若是每一個類都 close 那不的瘋了,咱們直接抽取一個 CloseUtils 以下:
public class CloaseUtils {
public static void close(Closeable... closeable) {
if (closeable != null) {
try {
if (closeable.length == 1){
closeable[0].close();
return;
}
for (int i = 0; i < closeable.length; i++) {
closeable[i].close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
複製代碼
支持同時關閉一個,或多個實現類的 close。
改造以後的代碼:
public class DiskCache implements IImageCache {
private DiskLruCache mDiskLruCache;
private static final int MAX_SIZE = 10 * 1024 * 1024;//10MB
//IO緩存流大小
private static final int IO_BUFFER_SIZE = 8 * 1024;
//緩存個數
private static final int DISK_CACHE_INDEX = 0;
@Override
public void put(String url, Bitmap bitmap) {
.....
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
CloaseUtils.close(snapshot,out,in);
}
}
}
複製代碼
是否是清爽多了,一行代碼解決了剛剛差很少 10 行代碼的邏輯。並且這裏基本原理就是依賴於 Closeable 抽象,而不是具體實現類(這不就是咱們剛剛纔說了的依賴倒置原則嘛),而且創建在最小化依賴原則的基礎上,它只須要知道這個對象是否關閉,其它一律不關心,也就是這裏的接口隔離原則。
迪米特原則英文的全稱爲 Law of Demeter , 縮寫是 LOD , 也稱爲最少知識原則。雖然名字不一樣,但描述的是同一個原則:一個對象應該對其餘對象有最少的的瞭解。通俗的將,一個類應該對本身須要耦合或調用的類知道的最少,類的內部如何實現與調用者或者依賴者沒有關係,調用者或者依賴着只須要知道它須要的方法便可,其餘的可一律不用管。類與類之間關係密切,耦合度就越大,當一個類發生改變時,對另外一個類的影響也越大。
下面以一個租房例子說明:
/**房子*/
public class Room {
//面積
public float area;
//價錢
public float price;
public Room(float area,float price){
this.area = area;
this.price = price;
}
}
複製代碼
/**中介*/
public class Mediator{
List <Room> mRooms = new ArrayList<Room>();
public Mediator(){
for(i = 0; i < 5 ; i ++){
mRoom.add(new Room(14 + i,(14 + i) * 150));
}
}
public List<Room> getAllRooms(){
return mRooms;
}
}
複製代碼
/**租客**/
public class Tenant {
public void rentRoom(float roomArea,float roomPrice,Mediator mediator){
List<Room> rooms = mediator.getAllRooms();
for(Room room : rooms){
if(isSuitable(roomArea,roomPrice,room)){
Log.i(TAG,"租到房子了");
bread;
}
}
}
//租金要小於等於指定的值,面積要大於等於指定的值
public boolean isSuitable(float roomArea,float roomPrice,Room room){
return room.price <= roomPrice && room.area >= roomArea;
}
}
複製代碼
上面的代碼中能夠看到,Tenant 不只依賴了 Mediator 類,還須要頻繁得於 Room 類打交道。租客只是找一個房子而已,若是把這些功能都放在 Tenant 類裏面,那中介都沒有存在感了吧?耦合過重了,咱們只須要跟中介通訊就好了,繼續重構代碼;
//中介
public class Mediator{
List<Room> mRooms = new ArrayList<Room>();
/**構造房子**/
public Mediator(){
for(i = 0; i < 5 ; i ++){
mRoom.add(new Room(14 + i,(14 + i) * 150));
}
}
public Room rentOut(float area,float price){
for(Room room : mRooms){
if(isSuitable(area,price,room)){
return room;
}
}
return null;
}
public boolean isSuitable(float area,float price ,Room room){
return room.price <= price && room.area >= area
}
}
複製代碼
//租客
public class Tenant{
/**是否租到房子了*/
public Room rentRoom(float roomArea,float roomPrice,Mediator mediator){
return mediator.rentOut(roomArea,roomPrice);
}
}
複製代碼
根據上面的重構優化,咱們得出結構,租客只須要跟中介通訊,主要關心中介那裏有沒有我須要的房子,而中介勒就去他的資源庫裏面去找,有沒有租客須要的房子,每一個對象作的事兒明確。「只與直接有關係的聯繫」 這簡單的幾個字就可以將咱們從複雜的關係網中抽離出來,使程序耦合度更低,穩定性更好。
從六大原則中咱們得出了重要的結論,就是必定要有抽象的思惟,面向抽象或面向接口編程。在應用開發過程當中,最難的不是完成開發工做,而是後續的維護和迭代工做是否擁有可變性,擴展性,在不破壞系統的穩定性前提下依然保持 二高一低原則(高擴展,高內聚,低耦合) 在經歷多個版本的迭代項目依然保持清晰,靈活,穩定的系統架構。固然這是咱們一個比較理想的狀況,可是咱們須要往這個方向去實現努力,就至關於接口(想法)出來了,咱們要去實現(接口實現類)它,遵循面向對象六大原則就是咱們走向靈活軟件之路所邁出的第一步,加油!