Java與Android代碼的根基-面向?qū)ο罅蠡驹瓌t 定義+舉例說明,這是周會分享講滿3小時的文章

前言:有色無閹割版請參見 Android面向?qū)ο罅蠡驹瓌t-團隊技術(shù)分享

這是團隊技術(shù)分享前,編寫的文檔,一篇文章,講滿了技術(shù)分享的3個小時。
圍繞ImageLoader通俗易懂的講解了各原則的使用場景與優(yōu)缺點。幫助非常的大!不管是新的,還是老的程序員,都推薦來看一下。復(fù)習(xí)一下。相比理解了,記住了,更推薦朋友們能夠面向大家講出來,進步更大。

Android 面向?qū)ο罅蠡驹瓌t

本文幾個方面來介紹

1、面向?qū)ο罅蠡驹瓌t的定義+舉例說明
2、代碼中的使用
3、優(yōu)缺點,
4、小結(jié)
會圍繞一個圖片加載框架,一步步講解代碼優(yōu)化代碼,從而表明六大基本原則的應(yīng)用與效果。
因為這是設(shè)計模式的基礎(chǔ)與核心參照原則

第一章、走向靈活軟件之路-面向?qū)ο罅蠡驹瓌t

前言:面向?qū)ο罅蠡驹瓌t,是代碼編寫和代碼優(yōu)化的基礎(chǔ),也是核心,雖然是基礎(chǔ),但是非常重要,而且沒有深入了解過的話,一知半解,也不好。本次圍繞 圖片加載庫 舉例說明。深入淺出透徹的理解什么是六大基本原則,如何運用。以及優(yōu)缺點。

基本原則就好比 建造房租一樣,首先要打地基,明白建造房租的流程,哪些節(jié)點是可以優(yōu)化的,比如水電如何優(yōu)化、開關(guān)如何設(shè)計、接口如何預(yù)留等等。明白了基本原則后,做任何事,都會在正常安全的范圍內(nèi),不會導(dǎo)致大錯

這種例子 生活中其實比比皆是,比如相機、手機的設(shè)計和制造 都有屬于各自領(lǐng)域的基本原則,遵循原則可以讓一切變得有規(guī)律、順暢、符合邏輯。

如果某家單反生產(chǎn)商,搞特殊,相機鏡頭搞了個18:9的接口,那基本上就廢了,市面上所有的鏡頭接口都不適合他。無法更換鏡頭,也注定了,他永遠(yuǎn)都做不大。不管多牛逼,遵循基本原則,是首要前提。

比如一個攝影師,要拍人像,卻用廣角鏡頭,人當(dāng)然可以拍出來,但是效果就差太多了。或者說P圖的基本原則,磨皮、瘦臉、縮鼻翼,大眼、瘦腰、大長腿。遵循這些基本原則,不管是拍出來的照片、還是p出來的圖片,都要比無章法的自由發(fā)揮好很多。

1.1 優(yōu)化代碼的第一步-【單一職責(zé)原則(SRP)】

1.1.1 定義,什么是單一職責(zé)原則?

?????英文名稱是 SIngle Responsibility Principle(SRP),定義為:就一個類而言,應(yīng)該僅有一個引起他變化的原因。簡單來說,一個類中,應(yīng)該是一組相關(guān)性很高的函數(shù)、數(shù)據(jù)的封裝。
?????但是 單一職責(zé)的劃分界限并不算很清晰,很多時候都需要靠個人經(jīng)驗來界定,最大的問題就算對職責(zé)的定義,什么是類的職責(zé),以及怎么劃分類的職責(zé)。

?????比如:我經(jīng)常看到一些Android開發(fā)在Activity中寫B(tài)ean文件,網(wǎng)絡(luò)數(shù)據(jù)處理,如果有列表的話Adapter 也寫在Activity中,問他們?yōu)槭裁闯撕谜乙矝]啥理由了,把他們拆分到其他類豈不是更好找,如果Activity過于臃腫行數(shù)過多,顯然不是好事,如果我們要修改Bean文件,網(wǎng)絡(luò)處理和Adapter都需要上這個Activity來修改,就會導(dǎo)致引起這個Activity變化的原因太多,我們在版本維護時也會比較頭疼。也就嚴(yán)重違背了定義“就一個類而言,應(yīng)該僅有一個引起它變化的原因”。

?????當(dāng)然如果想爭論的話,這個模式是可以引起很多爭論的,但請記住一點,你寫代碼不只是為了你也是為了其他人

?????舉個栗子:小到燈的開關(guān)、馬桶的沖水按鈕、大一點到房屋設(shè)計,廚房是廚房、衛(wèi)生間是衛(wèi)生間,電閘是單獨在一個固定區(qū)域的。從來沒見過衛(wèi)生間和廚房是在一起的吧?也沒見過,按一下馬桶沖水按鈕,既能沖水,又能充話費吧?

1.1.2 代碼舉例說明:

舉例就舉大家最熟悉的例子,以 圖片加載庫為例,可以分析下,這個類,在設(shè)計上有什么問題

public class ImageLoader{
    // 圖片緩存
    LruCache<String,Bitmap> mImageCache;
    // 線程池,線程數(shù)量為CPU的數(shù)量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    public ImageLoader(){
        initImageCache();
    }

    // 初始化
    private void initImageCache(){
        // 計算可使用的最大內(nèi)存
        final int maxMenory = (int)(Runtime.getRuntime().maxMenory()/1024);
        // 取1/4的可用內(nèi)存作為緩存
        final int cacheSize = maxMenory / 4;
        mImageCache = new LryCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key,Bitmap Bitmap){
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        }
    }

    public void displayImage(final String url,final ImageView ImageView){
        image.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);
            }
        });
    }

    public 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();
        }cache(Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }

}</pre>

問題解析:1、耦合嚴(yán)重 2、拓展性,靈活性差。

主要表現(xiàn)在:圖片緩存邏輯 與 展示邏輯,寫在同一個類,隨著業(yè)務(wù)增多,個性化需求增多,代碼會越來越復(fù)雜和無法維護

1.1.3 通過單一職責(zé)原則優(yōu)化

public class ImageLoader{

    // 圖片緩存-新建一個圖片緩存類
    ImageCache mImageCache = new ImageCache() ;
    // 線程池,線程數(shù)量為CPU的數(shù)量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    public void displayImage(final String url,final ImageView ImageView){
        image.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);
            }
        });
    }

    public 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();
        }cache(Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }

}</pre>

將圖片緩存邏輯拆分出來。

public class ImageCache{

    // 圖片LRU緩存
    LruCache<String,Bitmap> mImageCache;

    // 初始化
    private void initImageCache(){
        // 計算可使用的最大內(nèi)存
        final int maxMenory = (int)(Runtime.getRuntime().maxMenory()/1024);
        // 取1/4的可用內(nèi)存作為緩存
        final int cacheSize = maxMenory / 4;
        mImageCache = new LryCache<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);
    }

}

優(yōu)點:將ImageLoader一拆為二,ImageLoader只負(fù)責(zé)圖片加載的邏輯,ImageCache只負(fù)責(zé)處理圖片緩存的邏輯,這樣相互代碼量少了。職責(zé)也清晰了。 修改任一方邏輯,都相互不干預(yù)

小結(jié):一個函數(shù)的職責(zé),每個人都有自己的看法,這要根據(jù)經(jīng)驗而定,具體邏輯而定,但是也有基本指導(dǎo)原則:兩個完全不一樣的功能,就不應(yīng)該放在一個類中,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)、數(shù)據(jù)的封裝。這需要不斷的審視代碼,根據(jù)具體業(yè)務(wù)、功能進行拆分優(yōu)化;

1.2 讓程序更穩(wěn)定、更靈活-**【開閉原則(OCP)】重要

1.2.1 定義,什么是開閉原則

開閉原則的英文全稱是 Open Close Principle ,縮寫是OCP,定義為:軟件中的對象(類、模塊、函數(shù)等)應(yīng)該對于拓展是開放的,但是,對于修改是封閉的

實際應(yīng)用舉例說明:在軟件生命周期內(nèi),因為變化、升級和維護等原因,需要對軟件原有代碼進行修改時,可能會錯誤引入原本已經(jīng)經(jīng)過測試的舊代碼中,破壞原有系統(tǒng),本來是正常的,一修改 全掛了。 。 。

因此,當(dāng)軟件需要變化時,我們應(yīng)該盡量通過拓展的方式來實現(xiàn)變化,而不是通過修改已有代碼來實現(xiàn)。但是,實際開發(fā)中,拓展代碼、在原有基礎(chǔ)上修改,是經(jīng)常同時存在的

舉個栗子:生活中常見的,電源插座,當(dāng)位置不夠的時候,怎么操作?沒有人 把插座拿去改造吧?都是再找個插頭,續(xù)上。這其實就是開閉原則,我可以支持任意拓展,但是你不能來隨意修改我。

再比如,房屋的設(shè)計,電表是有個電箱單獨存放的,是開放的。沒見過,把電表砌在衛(wèi)生間墻里面的吧?萬一晚上電表爆了,難道要抹黑去一錘子80去敲墻 找電表再替換?這顯然是不可能的。電表都單獨存在,并且易替換、易維護。修改、替換電表,對我房子沒有任何影響。

1.2.2 代碼舉例說明

以上面的ImageLoader展示與緩存為例,通過內(nèi)存緩存解決了每次從網(wǎng)絡(luò)加載圖片的問題,但是,Android應(yīng)用內(nèi)存有限,并且具有易失性,應(yīng)用重啟后,原有加載過的圖片將丟失

因此,考慮加入SD卡緩存功能來解決這一問題,說干就干,初始代碼如下:

新建一個SD卡緩存實現(xiàn)類:

public class DiskCache {

    static String cacheDir = "sdcard/cache/"

    // 從緩存中獲取圖片
    public Bitmap get(String url){
        return BitmapFactory.decodeFile(cacheDir +url);
    }

    // 將圖片緩存SD卡
    public void put(String url,Bitmap bitmap){
        FileOutPutStream fileOutPutStream = null ; 
        try{
            fileOutPutStream = new FileOutPutStream(cacheDir + url);
            bitmap.compress(CompressFormat.PNG,100,fileOutPutStream);

        }cache(Exception e){
            e.printStackTrace();
        }finally{
            if(fileOutPutStream!=null){
                try{
                    fileOutPutStream.close();
                }cache(Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

因為,要將圖片緩存到SD卡中,對應(yīng)ImageLoader代碼修改如下

public class ImageLoader{

    // 內(nèi)存緩存
    ImageCache mImageCache = new ImageCache() ;
    // SD 卡緩存
    DiskCache mDiskCache = new DiskCache();
    // 是否
    boolean isUseDiskCache = false;
    // 線程池,線程數(shù)量為CPU的數(shù)量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    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 ;
        }
        // 如果沒有緩存,交給線程池下載...
    }

    public void useeDiskCache(boolean useDiskCache){
        isUseDiskCache = useDiskCache;
    }

}</pre>

用戶可以自由選擇,使用內(nèi)存緩存 或者 SD卡緩存

問題:使用內(nèi)存緩存時,不能同時SD卡緩存,使用SD卡緩存,不能同時使用內(nèi)存緩存

正常的策略:優(yōu)先從內(nèi)存緩存,如果內(nèi)存緩存沒有則使用SD卡緩存,如果SD卡也沒有,才從網(wǎng)絡(luò)下載圖片,這才是好的緩存策略

于是按照這個思路,又優(yōu)化代碼。。。新增一個DoubleCache類:

/*
* 雙緩存,獲取圖片時,從內(nèi)存中獲取,如果內(nèi)存中沒有緩存圖片,再從SD卡中獲取。
* 緩存圖片在內(nèi)存和SD卡中都緩存一份
*/
public class DoubleCache{
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();

    // 優(yōu)先從內(nèi)存緩存中獲取圖片,如果沒有,再從SD卡中獲取圖片
    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);
    }

}</pre>

再看看最新的ImageLoader類:

public class ImageLoader{

    // 內(nèi)存緩存
    ImageCache mImageCache = new ImageCache() ;
    // SD 卡緩存
    DiskCache mDiskCache = new DiskCache();
    // 雙緩存
    DoubleCache mDoubleCache = new DoubleCache();
    // 是否使用SD卡緩存
    boolean isUseDiskCache = false;
    // 是否使用雙緩存
    boolea isUseDoubleCache = false;
    // 線程池,線程數(shù)量為CPU的數(shù)量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    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 = mMemoryCache.get(url);
        }
        if(bitmap!=null){
            imageView.setImageBitmap(bitmap);
        }
        // 如果沒有緩存,交給線程池下載...
    }

    public void useeDiskCache(boolean useDiskCache){
        isUseDiskCache = useDiskCache;
    }

    public void useDoubleCache(boolean useDoubleCache){
        isUseDoubleCache = useDoubleCache;
    }

}

問題:1、每次修改緩存邏輯,都需要修改ImageLoader類,然后通過一個布爾變量來選擇使用哪種緩存,這樣可能引入bug,并且使得代碼越來越臃腫,容易出錯

** 2、用戶不能自己實現(xiàn)緩存策略注入到ImageLoader中,可拓展性差,可拓展性是框架最重要的特性之一**

回到開閉原則的定義,(類、模塊、函數(shù)等)對于拓展應(yīng)該是開放的,但是對于修改是封閉的。上面的代碼明顯不復(fù)合開閉原則。軟件優(yōu)化時,盡量通過拓展的方式來實現(xiàn)變化,而不是通過直接修改已有代碼來實現(xiàn)

1.2.3 通過開閉原則重新設(shè)計與優(yōu)化

public class ImageLoader{

    // 內(nèi)存緩存-修改為抽象接口
    ImageCache mImageCache = new ImageCache() ;
    // 線程池,線程數(shù)量為CPU的數(shù)量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    // 注入緩存實現(xiàn)
    public void setImageCache(ImageCache cache){
        mImageCache = cache;
    }

    public void displayImage(String url,ImageView imageView){
         Bitmap bitmap = mImageCache.get(url);
         if(bitmap != null){
            imageView.setImageBitmap(bitmap);
            return;
         }
         // 圖片沒有緩存,提交到線程池中下載圖片
         submitLoadRequest(imageUrl,imageView);
    }

    private void submitLoadRequest(final String imageUrl,final ImageView imageView){
        imageView.setTag(imageUrl);
        mExecutorService.submit(new Runnable(){
            @Override
            public void run(){
                Bitmap bitmap = downloadImage(imageUrl);
                if(bitmap==null){
                    return;
                }
                if(imageView.getTag().equals(imageUrl)){
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(imageUrl,bitmap);
            }
        });
    }

    public 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();
        }cache(Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }

}

這里的ImageCache并不是原有的ImageCache,而是提取成了一個圖片緩存接口,用來抽象圖片緩存功能,具體聲明如下:

// 圖片緩存接口
public interface ImageCache{

    public Bitmap get(String url);

    public void put(String url,Bitmap bitmap);

}</pre>

ImageCache定義了獲取、緩存兩個函數(shù),緩存的key是圖片的url,值是圖片本身,內(nèi)存緩存,SD卡緩存,雙緩存都實現(xiàn)了該接口,看看幾個緩存的實現(xiàn):

public class MemoryCache implements ImageCache{

     private LruCache<Stirng,Bitmap> mMemeryCache;

     public MemoryCache(){
        // 初始化LRU緩存
     }

     @Override
     public Bitmap get(String url){
        return mMemeryCache.get(url);
     }

     @Override
     public void put(String url,Bitmap bitmap){
        mMemeryCache.put(url,bitmap);
     }
}

public class DiskCache implements ImageCache{
     @Override
     public Bitmap get(String url){
        // 從本地文件獲取圖片
     }

     @Override
     public void put(String url,Bitmap bitmap){
        // 將Bitmap寫入文件中
     }

}

public class DoubleCache implements ImageCache{
    ImageCache mMemeryCache = new MemoryCache();
    ImageCache mDiskCache = new DiskCache();

    // 先從內(nèi)存緩存中獲取圖片,如果沒有,再從SD卡中獲取圖片
    public Bitmap get(String url){
        Bitmap bitmap = mMemeryCache.get(url);
        if(bitmap==null){
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    // 將圖片寫入到內(nèi)存和SD卡中
    public void put(String url,Bitmap bitmap){
        mMemeryCache.put(url,bitmap);
        mDiskCache.put(url,bitmap);
    }
}

細(xì)心的人可能發(fā)現(xiàn)了。ImageLoader類中增加了一個setImageCache(ImageCache cache)函數(shù),用戶可以童工該函數(shù)設(shè)置緩存實現(xiàn),也就是通常說的依賴注入,設(shè)置方法如下:

ImageLoader imageLoader = new ImageLoader();
// 使用內(nèi)存緩存
imageLoader.setImageCache(new MemoryCache());
// 使用SD卡緩存
imageLoader.setImageCache(new DiskCache());
// 使用雙緩存
imageLoader.setImageCache(new DoubleCache());
// 用戶自定義緩存
imageLoader.setImageCache(new Imagecache(){
    @Override
    public void put(Strign url,Bitmap bitmap){
        // 自定義將圖片緩存策略
    }
    @Override
    public Bitmap get(String url){
        // 自定義從緩存中獲取圖片
    }
})

小結(jié):在上述代碼中,通過setImageCache(ImageCache cache)方法注入不同的緩存實現(xiàn),不僅能夠使ImageLoader更簡單,健壯,也使得ImageLoader的可拓展性,靈活性更高。通過ImageCache接口,注入到ImageLoader中,可以實現(xiàn)千變?nèi)f化的緩存策略,并且拓展這些緩存策略不會導(dǎo)致ImageLoader修改。這就是標(biāo)準(zhǔn)的開閉原則。

PS:當(dāng)軟件需要優(yōu)化時,應(yīng)該盡量通過拓展的方式來實現(xiàn)變化,而不是通過修改已有代碼來實現(xiàn) “應(yīng)該盡量”說明并非絕對不可以修改原始類。如果代碼惡心到吐,還是應(yīng)該盡早的重構(gòu),具體要根據(jù)開發(fā)時間以及配套資源來判定。

1.3 構(gòu)建拓展新更好的系統(tǒng)-【里氏替換原則(LSP)】

1.3.1 定義,什么里氏替換原則?

里氏替換原則英文全稱是(LisKov SubStitution Principle),縮寫是LSP,定義為:所有引用基類的地方,都能透明的使用其子類對象;

面向?qū)ο笕筇攸c,繼承、封裝、多態(tài),里氏替換就是依賴了繼承、多態(tài)兩大特性;通俗點講,只要父類出現(xiàn)的地方,子類就可以出現(xiàn),而且替換為子類也不會產(chǎn)生任何錯誤或異常,使用者根本就不需要知道父類還是子類,但是反過來就不行了,有子類的地方,父類未必能適應(yīng)。。。

總結(jié)其實就兩個字:抽象

舉個栗子:耳機,老的有3.5mm接口的二級,新的有type-c接口的耳機或者藍(lán)牙耳機。

手機只有一個,但是耳機我有十個,是什么讓我可以任意的、方便的替換耳機,而手機不需要任何更改。核心就在于耳機接口的設(shè)計。 只要耳機是3.5mm的,我就可以支持。不管你是森海塞爾的、鐵三角的,還是華為、小米的。隨便你換。

還有就是單反相機,單反相機的鏡頭都是可替換的,廣角、微距、人像定焦、一鏡走天下等等。我既可以使用原廠的鏡頭,也可以使用第三方鏡頭替換,為什么可以做到這樣?因為單反設(shè)計了鏡頭的接口,只要是符合單反接口的鏡頭。都可以支持。

試想一下,如果不設(shè)計這種接口,那會怎么樣?那只能是小作坊。永遠(yuǎn)做不大了。

****1.3.2 代碼舉例說明**

[圖片上傳失敗...(image-b23d7a-1554990639319)]

**1.3.3 通過里氏替換原則優(yōu)化代碼****

可以看上面講過的 圖片加載示例,其實已經(jīng)很好的體現(xiàn)了里氏替換原則,MemoryCache、DiskCache、DoubleCache 都可以替換ImageCache的工作,并且保證正常運行。

小結(jié):里氏替換原則與開閉原則生死相依、不離不棄 ,通過里氏替換原則達到對拓展的開放,對修改的關(guān)閉效果,倆原則同時強調(diào)了一個OOP的重要特性-抽象,因此,在開發(fā)過程中,運用抽象是走向代碼優(yōu)化的重要一步。

里氏替換原則核心原理是抽象,抽象又依賴于繼承這個特性,在OOP中,繼承的優(yōu)缺點都特別明顯,

優(yōu)點有以下幾個方面:

(1)代碼重用,減少創(chuàng)建類的成本,每個子類都擁有父類的方法和屬性

(2)子類與父類基本相似,但又與父類有所區(qū)別

(3)提高代碼的可拓展性

繼承的缺點:

(1)集成是侵入性的,只要繼承就必須擁有父類的所有屬性和方法

(2)可能造成子類代碼冗余,靈活性降低,因為子類必須擁有父類的屬性和方法;

1.4 讓項目擁有變化的能力-【依賴倒置原則(DIP)】****

1.4.1 定義 什么是依賴倒置原則

依賴倒置英文全稱(Dependence Inversion Principle) 縮小是DIP

定義:高層模塊不應(yīng)該依賴低層模塊,兩個都應(yīng)該依賴于抽象。抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。

依賴倒置原則有以下幾個關(guān)鍵點:

(1)高層模塊不應(yīng)該依賴底層模塊,兩者應(yīng)該都依賴其抽象

(2)抽象接口不應(yīng)該依賴具體實現(xiàn)

(3)實現(xiàn)類應(yīng)該依賴抽象接口

解釋說明:在Java中,抽象就是指接口或者抽象類,兩者都是不能直接被實例化的;細(xì)節(jié)就是實現(xiàn)類,實現(xiàn)接口或者繼承抽象類而產(chǎn)生的就是細(xì)節(jié),也就是可以加上一個關(guān)鍵字new產(chǎn)生的對象。高層模塊就是調(diào)用端,低層模塊就是具體實現(xiàn)類。
依賴倒置原則在Java中的表現(xiàn)就是:模塊間通過抽象發(fā)生,實現(xiàn)類之間不發(fā)生直接依賴關(guān)系,其依賴關(guān)系是通過接口或者抽象類產(chǎn)生的。如果類與類直接依賴細(xì)節(jié),那么就會直接耦合,那么當(dāng)修改時,就會同時修改依賴者代碼,這樣限制了可擴展性。

舉個栗子:單反相機不應(yīng)該直接依賴某個鏡頭型號,即使這個鏡頭跟單反可以匹配。而是依賴4:3鏡頭連接接口,相機支持4:3接口的所有鏡頭。而鏡頭本身也是遵循4:3接口。那么兩者就可以關(guān)聯(lián)到一起。****相機就是高層模塊,鏡頭就是具體實現(xiàn),而抽象接口 就是4:3的鏡頭連接接口。

類似的有 插座、手機耳機、usb、type-c、hdmi等

1.4.2 代碼舉例

public class ImageLoader{

    //內(nèi)存緩存(直接依賴細(xì)節(jié))
    MemoeryCache mMenoryCache =  new MenoryCache();

    // 加載圖片到ImageLoader中
    public void displayImage(String url,ImageView imageView){
        Bitmap bitmap = mMenoryCache.get(url);
        if(bitmap==null){
            downloadImage(url,imageView);
        }else if(bitmap != null){
            imageView.setImageBitmap(bitmap);
        }
    }

    public void setImageCache(MenoryCache cache){
        mMemeryCache = cache;
    }
    // 下載圖片
    ... 
}

問題分析:可以看到,ImageLoader 直接依賴了MemoryCache,MemoeryCache是具體實現(xiàn),不是一個抽象類或者接口,當(dāng)MemoryCache不能滿足ImageLoader惡如需要被其他緩存實現(xiàn)替換時,此時就必須要修改ImageLoader的代碼。

而且,抽象接口的定義要具體化,不要定義不需要的接口。讓類的依賴關(guān)系也建立在最小的接口上。而不是提供一大堆接口,這樣很臃腫,也會存在耦合不易修改。

小結(jié):這與前文的開閉原則,其實也是對應(yīng)的。里氏替換原則能夠很好的保證代碼的可拓展性,能隨時替換具體實現(xiàn)并做到不影響調(diào)用端使用者,有擁抱變化的能力。讓代碼更靈活。其核心就是:抽象

1.4.3 通過依賴倒置原則優(yōu)化代碼

見1.2 開閉原則即可

1.5 系統(tǒng)擁有更高的靈活性-【接口隔離原則(ISP)】

1.5.1 定義 什么是接口隔離原則?

接口隔離英文全稱(InterfaceSegregation Principles),縮寫是ISP

定義:一個類對另一個類的依賴應(yīng)該建立在最小的接口上。臃腫的接口,拆分細(xì)化成更小和更具體的接口,這樣客戶端將會只需要知道他們感興趣的方法,接口隔離的原則與目的是系統(tǒng)解開耦合,從而容易被重構(gòu)、更改和重新部署

換種說法:建立單一接口,不要建立龐大臃腫的接口,盡量細(xì)化接口,接口中的方法盡量少。也就是說,我們要為各個類建立專用的接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調(diào)用。

采用接口隔離原則對接口進行約束時,要注意以下幾點:

(1)接口盡量小,但是要有限度。對接口進行細(xì)化可以提高程序設(shè)計靈活性,但是如果過小,則會造成接口數(shù)量過多,使設(shè)計復(fù)雜化。所以一定要適度。

(2)為依賴接口的類定制服務(wù),只暴露給調(diào)用的類它需要的方法,它不需要的方法則隱藏起來。只有專注地為一個模塊提供定制服務(wù),才能建立最小的依賴關(guān)系。

(3)提高內(nèi)聚,減少對外交互。使接口用最少的方法去完成最多的事情。

舉個栗子:燈的****開關(guān)、馬桶的沖水按鈕等等。燈的開關(guān)就是專門來控制開關(guān)的,沖水按鈕就是專門沖水的。一看就知道干嘛的。而不是,我想關(guān)燈,要從無數(shù)按鈕中查找。對我來說使用成本太大。而且無法量產(chǎn),這是不科學(xué)的。

1.5.2 代碼舉例說明

// 將圖片韓村到內(nèi)存中
public void put(String url,Bitmap bmp){

    FileOutPutStream fileOutPutStream = null ; 
    try{
        fileOutPutStream = new FileOutPutStream(cacheDir+url);
        bmp.compress(CompressFormat.PNG,100,fileOutPutStream);
    }cache(Exception e){
        e.printStackTrace();
    }finally{
            // 注意這一段代碼
            if(fileOutPutStream!=null){
                try{
                    fileOutPutStream.close();
                }cache(Exception e){

                }
            }// end if
    }//end if finally

}</pre>

代碼分析:代碼可讀性非常的差,各種try...cache嵌套的都是些簡單的代碼,但是會嚴(yán)重影響代碼的可讀性,寫代碼的時候,也容易發(fā)生錯誤。

追溯fileOutPutStream.close()方法,發(fā)現(xiàn),fileOutPutStream的clsoe方法,實現(xiàn)了Closeable接口,如果我的項目中,而系統(tǒng)中,有100多個類都實現(xiàn)了Closeable接口。這意味著,在使用這些類結(jié)束,最后關(guān)閉這100多個對象的時候,都需要寫上面finally里類似的代碼。

這是無法容忍的。

既然都實現(xiàn)了Closeable接口,干脆把這段代碼抽離出來,新建一個方法統(tǒng)一關(guān)閉這些對象。以后只要是實現(xiàn)了Closeable接口的類,都可以使用。

1.5.3 通過接口隔離原則優(yōu)化代碼

public final class CloseUtils{

    private CloseUtils(){ }

    /**
    * 關(guān)閉Closeable對象
    */
    public static void closeQuitely(Closeable closeable){
        if(closeable != null){
            try{
                closeable.close();
            }cache(Exception e){
                e.printStackTrace();
            }
        }
    }
}

再看看,把這段代碼運用到剛才put方法中的效果:

public void put(String url,Bitmap bmp){

    FileOutPutStream fileOutPutStream = null ; 

    try{
        fileOutPutStream = new FileOutPutStream(cacheDir+url);
        bmp.compress(CompressFormat.PNG,100,fileOutPutStream);
    }cache(Exception e){
        e.printStackTrace();
    }finally{
        CloseUtils.closeQuitely(fileOutPutStream);
    }

}

代碼明顯簡潔了很多,并且,這個類可以應(yīng)用到所有實現(xiàn)了Closeable的對象中,保證了代碼的重用性;

小結(jié):CloseUtils的closeQuality方法的基本原理,就在于依賴了Closeable抽象而不是具體實現(xiàn)(其實就是1.4中的依賴倒置原則),建立在最小化依賴原則的基礎(chǔ)上,它只需要知道這個對象是可關(guān)閉的,其它的一概不關(guān)心。這就是接口隔離。

思考:設(shè)想,如果關(guān)閉一個對象時,它卻暴露了其它的接口函數(shù),比如OutPutStream的write方法,這樣使得更多的細(xì)節(jié)暴露在客戶端代碼面前,不僅沒有很好的隱藏實現(xiàn),還增加了接口的使用難度。還有上文說到的ImageLoader中的ImageCache,調(diào)用者ImageLoader,只需要知道該緩存對象有存、取圖片的接口即可,其它的根本不關(guān)心。將龐大的接口,拆分到更細(xì)顆粒度的接口當(dāng)中,這樣代碼就會有更低的耦合性,更高靈活性。

Bob大叔(Robert C Martin)在21世紀(jì)早期,將 單一職責(zé)、開閉原則、里氏替換、接口隔離 以及 依賴倒置5個原則定義了SOLID原則,作為面向?qū)ο笞兂傻?個基本原則。當(dāng)這些原則被啟用時,系統(tǒng)軟件變得更加清晰、簡單、最大程度的擁抱變化。

1.1-1.5的總結(jié)和概括,其實可以為:抽象、單一職責(zé)、最小化。

1.6 更好的拓展性-【迪米特原則(LOD)】

1.6.1 定義,什么是迪米特原則?

定義:迪米特原則英文全稱 (LAW of Demeter) ,縮寫是LOD,也被稱為最少了解原則(Least Knowledage Principle),一個對象應(yīng)該對其它對象有最少的了解**

通俗的講,一個類應(yīng)該對自己需要耦合或者調(diào)用類知道的最少,類的內(nèi)部,如何實現(xiàn)與調(diào)用者或者依賴者沒有關(guān)系、調(diào)用者或者依賴者,只需要知道它需要的方法即可,其具體實現(xiàn),內(nèi)部如何騷造作,一概不管。

類與類之間的關(guān)系越密切,耦合程度就越大,當(dāng)一個類需要改變時,對另一個類的影響也越大。

舉個栗子:圖片加載類緩存類,ImageCache,MemoryCache、DiskCache,對于調(diào)用者ImageLoader,我只需要知道,存圖、取圖 就可以了。你也只需要開放這兩個接口給我就好了。我根本不需要知道你是怎么設(shè)置參數(shù)的、怎么設(shè)置緩存的。我根本不關(guān)心,也不應(yīng)該將這些業(yè)務(wù)邏輯暴露給調(diào)用者,

這樣會增加我使用的成本也會導(dǎo)致不必要的問題發(fā)生。比如調(diào)用者調(diào)用約定方法后,又同時調(diào)用了你提供的其它方法。這就有可能導(dǎo)致不必要的錯誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容