Android代碼設(shè)計(jì)及其應(yīng)用(1)-面向?qū)ο蟮牧笤瓌t

最近在學(xué)習(xí)設(shè)計(jì)模式, 搜索大量的資料發(fā)現(xiàn)很多資料都是只是說明這些設(shè)計(jì)模式是怎樣的, 而沒有說明實(shí)際用途, 大量的資料都是重疊重復(fù)的. 雖說入門, 但是給出例子之后就沒有再深入下去了. 學(xué)Android開發(fā)的, 很多時(shí)候看完用Java寫的設(shè)計(jì)模式代碼, 但是卻不知道怎么應(yīng)用到實(shí)際的項(xiàng)目開發(fā)中去. 所以打算根據(jù)自己的?一些拙見, 能把經(jīng)常使用的設(shè)計(jì)方法寫成文章互相交流.

從ImageLoader說起

如果需要寫一個(gè)ImageLoader,那么一般代碼就像下面那樣

public class ImageLoader {

    private static final int SHOW_IMAGE = 100;

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private LruCache<String, Bitmap> mCache;
    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            if (msg.what == SHOW_IMAGE) {
                ImageHolder holder = (ImageHolder) msg.obj;
                if (holder != null && holder.imageView != null && holder.bitmap != null && holder.url != null
                        && holder.url.equals(holder.imageView.getTag())) {
                    holder.imageView.setImageBitmap(holder.bitmap);
                }
            }
        };
    };

    /**
     * 私有化構(gòu)造函數(shù)并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    /**
     * 圖片顯示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 下載圖片
     * 
     * @param url
     * @return
     */
    private Bitmap downloadImage(String url) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(new URL(url).openStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    /**
     * 獲取實(shí)例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }

    /**
     * 顯示圖片消息數(shù)據(jù)傳輸對象
     * 
     * @author August
     *
     */
    static final class ImageHolder {
        ImageView imageView;
        Bitmap bitmap;
        String url;
    }
}

好, 至此一個(gè)ImageLoader就已經(jīng)寫完了.我們下面根據(jù)面向?qū)ο蟮牧笤瓌t對它進(jìn)行改造

單一職責(zé)原則

書面語就兩句重要的話:

  • 就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因

  • 一個(gè)類中應(yīng)該是一組相關(guān)性很高的函數(shù)和數(shù)據(jù)的封裝

上面我們把ImageLoader的各部分都卸載一個(gè)類中, 所以已經(jīng)違背了該原則, 我們應(yīng)該盡量地去簡化每個(gè)類的工作量. 把相關(guān)度不高的部分分離出去. 于是我們就有了下面的改造.

Downloader

/**
 * 下載網(wǎng)絡(luò)圖片
 * 
 * @author August
 *
 */
public class Downloader {
    public Bitmap downloadImage(String url) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(new URL(url).openStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

ImageCache

/**
 * 圖片緩存類
 * @author August
 *
 */
public class ImageCache {
    private LruCache<String, Bitmap> mCache;

    public ImageCache() {
        mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    public Bitmap get(String url) {
        return mCache.get(url);
    }

    public void put(String url, Bitmap bitmap) {
        mCache.put(url, bitmap);
    }
}

ImageHolder

/**
 * 顯示圖片消息的數(shù)據(jù)傳輸對象
 * 
 * @author August
 *
 */
public class ImageHolder {
    public ImageView imageView;
    public Bitmap bitmap;
    public String url;

    public boolean isVerify() {
        return imageView != null && bitmap != null && url != null && url.equals(imageView.getTag());
    }

    public void showImage() {
        if (isVerify()) {
            imageView.setImageBitmap(bitmap);
        }
    }

}

ImageHandler

/**
 * 消息處理類
 * @author August
 *
 */
public class ImageHandler extends Handler {
    public static final int SHOW_IMAGE = 100;

    public void handleMessage(android.os.Message msg) {
        if (msg.what == SHOW_IMAGE) {
            ImageHolder holder = (ImageHolder) msg.obj;
            if (holder != null) {
                holder.showImage();
            }
        }
    };
}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private ImageCache mCache = new ImageCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化構(gòu)造函數(shù)并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 圖片顯示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 獲取實(shí)例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

可以看到現(xiàn)在我們的ImageLoader已經(jīng)是有模有樣地分開了幾個(gè)類.

開閉原則

也是兩句話作總結(jié)

  • 軟件中的對象(類 模塊 函數(shù)等)應(yīng)該對于擴(kuò)展是開放的, 但是對于修改是封閉的.

  • 程序一旦開發(fā)完成, 程序中的一個(gè)類的實(shí)現(xiàn)只應(yīng)該因錯(cuò)誤而被修改, 新的或者改變的特性應(yīng)該通過新建不同的類實(shí)現(xiàn), 新建的類可以通過集成的方式來重用原來的代碼.

上面的ImageLoader只在內(nèi)存中緩存, 后來發(fā)現(xiàn)一級的緩存是行不通的. 因?yàn)锽itmap占用的內(nèi)存太, 很容易被回收. 所以我們需要使用磁盤緩存. 然后我們增加DiskCache類并且修改ImageLoader.

DiskCache

public class DiskCache {

    private File mDir;

    public DiskCache() {
        mDir = new File(Environment.getExternalStorageDirectory(), "cache");
        if (!mDir.exists()) {
            mDir.mkdir();
        }
    }

    public void put(String url, Bitmap bitmap) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        bitmap.compress(CompressFormat.JPEG, 100, os);
    }

    public Bitmap get(String url) {

        File file = new File(mDir, getDiskName(url));
        Bitmap bitmap = null;
        if (file.exists()) {
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        }
        return bitmap;
    }

    private String getDiskName(String url) {
        return Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
    }

}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private DiskCache mCache = new DiskCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化構(gòu)造函數(shù)并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 圖片顯示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 獲取實(shí)例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

什么??? 只有磁盤緩存又不夠快??? 要做二級緩存???? 代碼又要改...也不怎么難, 我們添加一個(gè)DoubleCache的類

DoubleCache

/**
 * 二級緩存類
 * 
 * @author August
 *
 */
public class DoubleCache {
    private ImageCache mImageCache = new ImageCache();
    private DiskCache mDiskCache = new DiskCache();

    public Bitmap get(String url) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
        mDiskCache.put(url, bitmap);
    }
}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private DoubleCache mCache = new DoubleCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化構(gòu)造函數(shù)并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 圖片顯示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 獲取實(shí)例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

雖然說是完成了需求, 但是我們做了一個(gè)非常蠢的事情,就是去修改了ImageLoader的代碼...這明顯是違背了開閉原則的.下面介紹一下里氏替換原則和依賴倒置原則.

里氏替換原則

還是兩句話

  • 所有引用基類的地方必須能透明地使用其子類的對象

  • 里氏替換原則的核心原理是抽象, 抽象又依賴于繼承這個(gè)特性, 通過建立抽象, 通過抽象建立規(guī)范, 具體的實(shí)現(xiàn)在運(yùn)行時(shí)替換掉抽象, 保證系統(tǒng)的擴(kuò)展性和靈活性.

優(yōu)點(diǎn)

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

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

  • 提高代碼的可擴(kuò)展性

依賴倒置原則

依賴倒置原則只帶一種特定的解耦形式, 使得高層次的模塊不依賴于低層次的模塊.

關(guān)鍵點(diǎn)

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

  • 抽象不應(yīng)該依賴細(xì)節(jié)

  • 細(xì)節(jié)應(yīng)該依賴抽象

在Java中, 抽象就是借口或者抽象類, 細(xì)節(jié)就是實(shí)現(xiàn)類

回到ImageLoader中, 上面的圖片緩存就是直接依賴于緩存的具體實(shí)現(xiàn). 修改后我們可以依賴起父類或者接口. 但是內(nèi)存緩存和磁盤緩存的復(fù)用代碼幾乎沒有, 所以我們選擇依賴接口. 那么我們就應(yīng)該設(shè)計(jì)成下面那樣.

IImageCache

/**
 * 抽象的緩存接口
 * 
 * @author August
 *
 */
public interface IImageCache {
    public Bitmap get(String url);

    public void put(String url, Bitmap bitmap);
}

MemoryCache

/**
 * 內(nèi)存圖片緩存類
 * 
 * @author August
 *
 */
public class MemoryCache implements IImageCache {
    private LruCache<String, Bitmap> mCache;

    public MemoryCache() {
        mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

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

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

DiskCache

/**
 * 磁盤緩存
 * @author August
 *
 */
public class DiskCache implements IImageCache {

    private File mDir;

    public DiskCache() {
        mDir = new File(Environment.getExternalStorageDirectory(), "cache");
        if (!mDir.exists()) {
            mDir.mkdir();
        }
    }

    @Override
    public void put(String url, Bitmap bitmap) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        bitmap.compress(CompressFormat.JPEG, 100, os);
    }

    @Override
    public Bitmap get(String url) {
        File file = new File(mDir, getDiskName(url));
        Bitmap bitmap = null;
        if (file.exists()) {
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        }
        return bitmap;
    }

    private String getDiskName(String url) {
        return Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
    }

}

ImageLoader

public class ImageLoader {

    private static final ImageLoader mInstance = new ImageLoader();
    private ExecutorService mExecutors;
    private IImageCache mCache = new MemoryCache();
    private Downloader mDownloader = new Downloader();
    private Handler mHandler = new ImageHandler();

    /**
     * 私有化構(gòu)造函數(shù)并初始化
     */
    private ImageLoader() {
        mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    /**
     * 圖片顯示
     * 
     * @param imageView
     * @param url
     */
    public void displayImage(final ImageView imageView, final String url) {
        imageView.setTag(url);
        Bitmap cacheBitmap = mCache.get(url);
        if (cacheBitmap != null) {
            imageView.setImageBitmap(cacheBitmap);
            return;
        }
        final ImageHolder holder = new ImageHolder();
        holder.imageView = imageView;
        holder.url = url;
        mExecutors.submit(new Runnable() {

            @Override
            public void run() {
                Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
                mCache.put(holder.url, remoteBitmap);
                holder.bitmap = remoteBitmap;
                Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
                message.obj = holder;
                mHandler.sendMessage(message);
            }
        });

    }

    /**
     * 設(shè)置緩存類型
     * 
     * @param cache
     */
    public void setImageCache(IImageCache cache) {
        mCache = cache;
    }

    /**
     * 獲取實(shí)例
     * 
     * @return
     */
    public static ImageLoader getInstance() {
        return mInstance;
    }
}

可以看到上面的ImageLoader直接依賴于Cache的抽象, 即使后面擴(kuò)展的時(shí)候需要加入其它類型的緩存, 開發(fā)者只需要關(guān)注IImageCache這個(gè)接口, 而對ImageLoader不需要有任何研究. 類似的還有Downloader的實(shí)現(xiàn), 可以修改其打開的方式.

接口隔離原則

  • 客戶端不應(yīng)該依賴它不需要的接口

  • 類間的依賴關(guān)系應(yīng)該建立在最小的接口上, 接口隔離原則就是將非常龐大, 臃腫的類拆分成更小的和更具體的接口.

例如上面的

OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

其中很多代碼是沒用的, 但是異常我們必須要去捕獲, 這樣我們是不是就可以寫一個(gè)工具類去關(guān)閉文件呢? 于是有了下面的CloseUtilClose

CloseUtil

/**
 * 關(guān)閉流的工具類
 * 
 * @author August
 *
 */
public class CloseUtil {
    public static void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mDir, getDiskName(url)));
            bitmap.compress(CompressFormat.JPEG, 100, os);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            CloseUtil.close(os);
        }

代碼立刻精簡了不少, 但是問題來了. InputStream也要有這樣的方法啊, 那么我們是不是又要重載一個(gè)方法, 參數(shù)為InputStream..
.

這時(shí)候根據(jù)接口隔離原則, 我們應(yīng)該把這種依賴關(guān)系建立在最小的接口上. 對于上面的情況, 拋出異常的是Closeable的close方法. 所以我們只要處理這種參數(shù)的就可以了, 下面的就通用了.

/**
 * 關(guān)閉流的工具類
 * 
 * @author August
 *
 */
public class CloseUtil {

    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

迪米特原則

  • 一個(gè)類應(yīng)該對其他對象有最少的了解

什么意思?

想想, 一個(gè)類對其他對象有最少的了解, 說明了彼此間的依賴關(guān)系不是太強(qiáng), 那么對于類與類之間的耦合性就減少了. 當(dāng)一個(gè)類修改的時(shí)候, 對另一個(gè)類的影響就少了. 這就是各種設(shè)計(jì)和模式的目的.

總結(jié)

之前看到過一句話架構(gòu)是為了妥協(xié)客觀的不足, 而設(shè)計(jì)模式是為了妥協(xié)主觀上的不足. 后面文章提到的設(shè)計(jì)模式, 都是為了規(guī)范開發(fā)人員的合作. 也是圍繞著上面的六大原則進(jìn)行的.

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

相關(guān)閱讀更多精彩內(nèi)容

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