Android代碼設計及其應用(1)-面向對象的六大原則

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

從ImageLoader說起

如果需要寫一個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);
                }
            }
        };
    };

    /**
     * 私有化構造函數(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;
    }

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

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

好, 至此一個ImageLoader就已經(jīng)寫完了.我們下面根據(jù)面向對象的六大原則對它進行改造

單一職責原則

書面語就兩句重要的話:

  • 就一個類而言,應該僅有一個引起它變化的原因

  • 一個類中應該是一組相關性很高的函數(shù)和數(shù)據(jù)的封裝

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

Downloader

/**
 * 下載網(wǎng)絡圖片
 * 
 * @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();

    /**
     * 私有化構造函數(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);
            }
        });

    }

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

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

開閉原則

也是兩句話作總結

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

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

上面的ImageLoader只在內存中緩存, 后來發(fā)現(xiàn)一級的緩存是行不通的. 因為Bitmap占用的內存太, 很容易被回收. 所以我們需要使用磁盤緩存. 然后我們增加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();

    /**
     * 私有化構造函數(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);
            }
        });

    }

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

什么??? 只有磁盤緩存又不夠快??? 要做二級緩存???? 代碼又要改...也不怎么難, 我們添加一個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();

    /**
     * 私有化構造函數(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);
            }
        });

    }

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

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

里氏替換原則

還是兩句話

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

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

優(yōu)點

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

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

  • 提高代碼的可擴展性

依賴倒置原則

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

關鍵點

  • 高層模塊不應該依賴底層模塊, 兩者都應該依賴抽象

  • 抽象不應該依賴細節(jié)

  • 細節(jié)應該依賴抽象

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

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

IImageCache

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

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

MemoryCache

/**
 * 內存圖片緩存類
 * 
 * @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();

    /**
     * 私有化構造函數(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);
            }
        });

    }

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

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

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

接口隔離原則

  • 客戶端不應該依賴它不需要的接口

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

例如上面的

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();
                }
            }
        }

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

CloseUtil

/**
 * 關閉流的工具類
 * 
 * @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也要有這樣的方法啊, 那么我們是不是又要重載一個方法, 參數(shù)為InputStream..
.

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

/**
 * 關閉流的工具類
 * 
 * @author August
 *
 */
public class CloseUtil {

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

迪米特原則

  • 一個類應該對其他對象有最少的了解

什么意思?

想想, 一個類對其他對象有最少的了解, 說明了彼此間的依賴關系不是太強, 那么對于類與類之間的耦合性就減少了. 當一個類修改的時候, 對另一個類的影響就少了. 這就是各種設計和模式的目的.

總結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末辜羊,一起剝皮案震驚了整個濱河市液南,隨后出現(xiàn)的幾起案子秕噪,更是在濱河造成了極大的恐慌离熏,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事甜,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機狡逢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拼卵,“玉大人奢浑,你說我怎么就攤上這事∫溉” “怎么了雀彼?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長即寡。 經(jīng)常有香客問我徊哑,道長,這世上最難降的妖魔是什么聪富? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任莺丑,我火速辦了婚禮,結果婚禮上墩蔓,老公的妹妹穿的比我還像新娘梢莽。我一直安慰自己,他們只是感情好奸披,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布昏名。 她就那樣靜靜地躺著,像睡著了一般阵面。 火紅的嫁衣襯著肌膚如雪轻局。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天样刷,我揣著相機與錄音仑扑,去河邊找鬼。 笑死颂斜,一個胖子當著我的面吹牛夫壁,可吹牛的內容都是我干的。 我是一名探鬼主播沃疮,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼盒让,長吁一口氣:“原來是場噩夢啊……” “哼梅肤!你這毒婦竟也來了?” 一聲冷哼從身側響起邑茄,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤姨蝴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肺缕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體左医,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年同木,在試婚紗的時候發(fā)現(xiàn)自己被綠了浮梢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡彤路,死狀恐怖秕硝,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情洲尊,我是刑警寧澤远豺,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站坞嘀,受9級特大地震影響躯护,放射性物質發(fā)生泄漏。R本人自食惡果不足惜丽涩,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一棺滞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧内狸,春花似錦检眯、人聲如沸厘擂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刽严。三九已至昂灵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舞萄,已是汗流浹背眨补。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倒脓,地道東北人撑螺。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像崎弃,于是被迫代替她去往敵國和親甘晤。 傳聞我的和親對象是個殘疾皇子含潘,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內容