理解面對物件的六大原則

weixin_33860722發表於2018-04-25

以下內容來自:《Android原始碼設計模式解析與實戰》

說起面對物件的六大原則,可能大部分人都能說出一二來。但是如何應用到自己的程式碼中卻是一個不小的難題。這篇文章會用一個實際的例子,並用六大原則改造,在改造的過程中體會。

我們來看一個Android中常見的功能模組——圖片載入。在不使用任何現有框架的前提下,可能會這樣寫:

public class ImageLoader {
    //圖片快取
    LruCache<String, Bitmap> mImageCache;
    //固定執行緒數的執行緒池,執行緒數為CPU的數量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public ImageLoader() {
        initImageCache();
    }

    private void initImageCache() {
        //計算可使用的最大記憶體
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //使用1/4作為圖片快取
        int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() * 1024;
            }
        };
    }

    public 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);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

這樣可以完成圖片載入的功能,但是這個ImageLoader嚴重耦合,毫無設計可言,更不用說擴充套件性、靈活性了。所有的功能都寫在一個類裡,隨著功能的增加,ImageLoader類會越來越大,程式碼也越來越複雜,圖片載入系統就越來越脆弱。接下來我們嘗試用單一職責原則改造以下這個ImageLoader。

單一職責原則

**Single Responsibility Principle **

定義:就一個類而言,應該僅有一個引起它變化的原因。簡單來說,一個類中應該是一組相關性很高的函式、資料的封裝。

雖然如何劃分一個類,一個函式的職責,每個人都有自己的看法,這需要根據個人經驗、具體的業務邏輯而定。但完全兩個不一樣的功能就不應該放在一個類中。因此從單一職責來看ImageLoader,明顯它可以分為兩個,一個是圖片載入;另一個是圖片快取。因此我們這樣改造:

public class ImageLoader {
    //圖片快取
    ImageCache mImageCache = new ImageCache();
    //固定執行緒數的執行緒池,執行緒數為CPU的數量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public 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);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

抽出ImageCache用於處理圖片快取:

public class ImageCache {
    LruCache<String, Bitmap> mImageCache;

    public ImageCache() {
        initImageCache();
    }

    private void initImageCache() {
        //計算可使用的最大記憶體
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //使用1/4作為圖片快取
        int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() * 1024;
            }
        };
    }

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

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

改造後的ImageLoader只負責圖片載入的邏輯,而ImageCache只負責處理圖片快取的邏輯,這樣ImageLoader的程式碼量變少了,職責也清晰了;當快取相關的邏輯需要改變時,不需要修改ImageLoader類,而圖片的載入邏輯需要修改時也不會影響到快取處理邏輯。

開閉原則

Open Close Principe

定義:軟體中的物件(類、模組、函式等)應該對於擴充套件是開放的,但是對於修改是封閉的。在軟體的生命週期內,因為變化、升級和維護等原因需要對軟體原有程式碼進行修改時,可能會將錯誤引入原本已經經過測試的舊程式碼中,破壞原有系統。因此,當軟體需要變化時,我們應該儘量通過擴充套件的方式來實現變化麼人不是通過修改已有的程式碼來實現。當然,在現實開發中,只通過繼承的方式來升級、維護原有系統只是一個理想化的願景,因此,在實際的開發過程中,修改原有程式碼、擴充套件程式碼往往是同時存在的。

我們再來看看ImageLoader,雖然通過記憶體快取解決了每次從網路載入圖片的問題,但是,Android應用的記憶體很有限,且具有易失性,即當應用重新啟動之後,原來已經載入過的圖片將會失去,這樣重啟之後就需要重新下載!這又會導致載入緩慢、耗費使用者流量的問題。引入SD卡快取可以解決這一個問題,我們在來新增一個SD卡快取類:

public class DiskCache {
    static String cacheDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";

    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }

    public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

然後相應的,改動ImageLoader的程式碼,新增SD卡快取:

public class ImageLoader {
    //記憶體快取
    ImageCache mImageCache = new ImageCache();
    //SD卡快取
    DiskCache mDiskCache = new DiskCache();
    //是否使用SD卡快取
    boolean isUseDiskCache = false;
    //固定執行緒數的執行緒池,執行緒數為CPU的數量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    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;
        }
        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);
                }
                if (isUseDiskCache) {
                    mDiskCache.put(url, bitmap);
                } else {
                    mImageCache.put(url, bitmap);
                }
            }
        });
    }

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

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

這裡增加了一個DiskCache類,使用者可以通過useDiskCache方法來使用哪種快取進行設定,例如:

ImageLoader imageLoader = new ImageLoader();
//使用SD卡快取
imageLoader.useDiskCache(true);
//使用記憶體快取
imageLoader.useDiskCache(false);

但是這樣又存在不能同時使用記憶體快取和SD卡快取。應該優先使用記憶體快取,如果記憶體快取中沒有圖片再使用SD卡快取,如果SD卡中也沒有圖片最後才去網路上獲取,這才是最好的快取策略。我們在新增一種雙快取的類DoubleCache:

public class DoubleCache {
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();

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

並且同樣應用到ImageLoader中:

public class ImageLoader {
    //記憶體快取
    ImageCache mImageCache = new ImageCache();
    //SD卡快取
    DiskCache mDiskCache = new DiskCache();
    //雙快取
    DoubleCache mDoubleCache = new DoubleCache();
    //使用SD卡快取
    boolean isUseDiskCache = false;
    //使用雙快取
    boolean isUseDoubleCache = false;
    //固定執行緒數的執行緒池,執行緒數為CPU的數量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    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 = mImageCache.get(url);
        }
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        //沒有快取,則提交給執行緒池進行非同步下載
    }

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

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

現在即可以使用記憶體快取,也可以使用SD卡快取,或者兩者同時使用,但還是存在一些問題。我們來分析一下現在的程式碼,ImageLoader通過boolean變數來讓使用者選擇使用哪種快取,因此存在各種if-else判斷語句,通過這些判斷來確定使用哪種快取。隨著這些邏輯的引入,程式碼變得越來越複雜、脆弱,如果一不小心寫錯了某個if條件,那就需要更多的時間來排除,整個ImageLoader類也會變得越來越臃腫。還有一點,使用者不能自己實現快取注入到ImageLoader中,可擴充套件性差。

這樣的程式碼明顯不滿足開閉原則,為了滿足上面說到的需求,我們可以採用策略模式繼續改造,先來看下UML圖:

4657803-24fb74b9e7d3fee1
UML圖

接著,我們把程式碼按照UML圖改造一下,ImageCache變成了介面,並且有3個實現類:MemoryCache、DiskCache、DoubleCache。

public interface ImageCache {
    void put(String url, Bitmap bitmap);

    Bitmap get(String url);
}

public class MemoryCache implements ImageCache {
    ...
}

public class DiskCache implements ImageCache {
    ...
}

public class DoubleCache implements ImageCache {
    ...
}

而ImageLoader變成這樣:

public class ImageLoader {
    //快取
    ImageCache mImageCache = new MemoryCache();
    //固定執行緒數的執行緒池,執行緒數為CPU的數量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void setImageCache(ImageCache imageCache) {
        mImageCache = imageCache;
    }

    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        //圖片沒有快取,提交到執行緒池中下載
        submitLoadRequest(url, imageView);
    }

    private void submitLoadRequest(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);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

這裡的增加了一個setImageCache(ImageCache imageCache) 函式,使用者可以通過該函式設定快取實現,也就是通常說的依賴注入:

//預設使用記憶體快取
ImageLoader imageLoader = new ImageLoader();
//使用SD卡快取
imageLoader.setImageCache(new DiskCache());
//使用雙快取
imageLoader.setImageCache(new DoubleCache());
//使用自定義快取
imageLoader.setImageCache(new ImageCache(){

    @Ovrride
    public void put(String url, Bitmap bitmap){
        //快取圖片
    }

    @Ovrride
    public Bitmap get(String url){
        retrun /*從快取獲取圖片*/;
    }
});

現在我們再來看看,無論之後新增加什麼快取,都可以實現ImageCache介面,然後通過setImageCache方法注入,在這個過程中我們都不用修改ImageLoader類的程式碼,這就是開閉原則:對擴充套件是開放的,對修改是封閉的。而設計模式是前人總結的能更好地遵守原則的方式。

里氏替換原則

Liskov Substitution Principle

定義:如果對每一個型別為S的物件O1,都有型別為T的獨享O2,使得以T定義的所有程式P在所有的物件O1都代替成O2時,程式P的行為沒有發生變化,那麼型別S是型別T的子型別。

看定義的話不是很好理解,換一種描述方式就是:所有引用基類的地方必須能透明地使用其子類的物件。

我們來看看Android中Window與View的關係:

4657803-8eb713d7569defd9
Window與View的關係

Window依賴於View,而View定義了一個檢視抽象,measure是各個子類共享的方法,子類通過複寫View的draw方法實現具有各自特色的功能,即繪製自身內容。任何繼承自View類的子類都可以設定給show方法,就是所說的裡式替換。通過裡式替換,就可以自定義各式各樣、千變萬化的View,然後傳遞給Window,Window負責組織View,並且將View顯示到螢幕上。

裡式替換的核心原理是抽象,抽象又依賴於繼承這個特性,在OOP當中,繼承的優缺點都相當明顯:優點有以下幾點:

  1. 程式碼重用,減少建立類的成本,每個子類都擁有父類的方法和屬性;
  2. 子類與父類基本相似,但又與父類有所區別;
  3. 提高程式碼的可擴充套件性。

繼承的缺點:

  1. 繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法;
  2. 可能造成子類程式碼冗餘、靈活性降低,因為子類必須擁有父類的屬性和方法。

事物總是具有兩面性,如何權衡利弊都是需要根據具體情況來做出選擇並加以處理。

前面ImageLoader的例子也體現了裡式替換原則,即MemoryCache、DiskCache、DoubleCache都可以替換ImageCache的工作,並且能夠保證行為的正確性。ImageCache建立獲取快取圖片、儲存快取圖片的介面規範,MemoryCache等根據介面規範實現了相應的功能,使用者只需要在使用時指定具體的快取物件就可以動態地替換ImageLoader中的快取策略。這就使得ImageLoader的快取系統具有無限的可能性,也就保證了可擴充套件性。

開閉原則和裡式替換原則往往是生死相依,不離不棄的,通過裡式替換來達到對擴充套件開放,對修改關閉的效果。然而,這兩個原則都同時強調了一個OOP的重要特性——抽象,因此,在開發過程中運用抽象是走向程式碼優化的重要一步。

依賴倒置原則

Dependence Inversion Principe

定義:是一種特殊的解耦形式,使得高層次的模組不依賴於低層次的模組的實現細節的目的,依賴模組被顛倒了。
這到底是什麼意思呢?
依賴倒置原則有以下幾個關鍵點:

  • 高層模組不應該依賴低層模組,兩者都應該依賴其抽象;
  • 抽象不應該依賴細節;
  • 細節應該依賴抽象。

在Java中,抽象就是指介面或抽象類,兩者都是不能直接例項化的;細節就是實現類,實現介面或繼承抽象類而產生的類就是細節,其特點就是,可以直接被例項化。高層模組就是呼叫端,低層模組就是具體實現類。依賴倒置原則在Java語言中的表現就是:模組間的依賴通過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是通過介面或抽象類產生的。通俗點說就是面對介面(抽象)程式設計。

如果類與類直接依賴於細節,那麼他們之間就有直接的耦合,當具體實現需要變化時,意味著要同時修改該依賴者的程式碼,這限制了系統的可擴充套件性。ImageLoader的例子一開始直接依賴MemoryCache,是一個具體實現,而不是一個抽象類或者介面。這導致了我們後面修改其他快取實現就需要修改ImageLoader類的程式碼。

最後版本的ImageLoader就很好的體現了依賴抽象,我們抽出的ImageCache的介面,並且定義了兩個方法。而ImageLoader依賴的是抽象(ImageCache介面),而不是具體的某個實現類。當需求發生變化時,我們可以使用其他的實現替換原有的實現。

介面隔離原則

InterfaceSegregation Principe

定義:客戶端不應該依賴它不需要的介面。另一種定義:類間的依賴關係應該建議在最小的介面上。介面隔離原則將非常龐大、臃腫的介面拆分成更小的和更具體的介面,這樣客戶將會只需要知道他們感興趣的方法。介面隔離原則的目的是系統解開耦合,從而容易重構、更改和重新部署。

介面隔離原則說白了就是讓客戶端依賴的介面儘可能的小,這樣說可能還是有點抽象,我們還是以一個例子來說明:

public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

這段程式碼的可讀性非常差,各種try-catch巢狀都是些簡單的程式碼,但是會嚴重影響程式碼的可讀性,並且多層級的大括號很容易將程式碼寫到錯誤的層級中。我們來看看如何解決這類問題。
我們可能知道Java中有一個Closeable介面,該介面標識了一個可關閉的物件,它只有一個close方法。FileOutputStream就實現這個介面。我們可以嘗試寫一個工具類,專門用於關閉Closeable介面的實現。

public final class CloseUtils {
    private CloseUtils() {
    }

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

我們把這個工具類引用到上述的例子中看看效果:

public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            CloseUtils.closeQuietly(fileOutputStream);
        }
    }

是不是簡潔多了!而且這個closeQuietly方法可以運用到各類可關閉的物件中,保證了程式碼的重用性。我們可以想想,為什麼close方法定義在FileOutputStream(或者它的父類)中,而是單獨用一個介面承載這個方法呢?從使用者的角度來看,也就是這個CloseUtils工具類,其實只關心close方法,而不關心FileOutputStream的其他方法,如果沒有這樣一個Closeable介面,closeQuietly(Closeable closeable) 方法的形參就得定義成FileOutputStream,會將沒有必要的其他方法暴露出來,並且其他同樣擁有close方法的類無法使用closeQuietly來關閉。

想一想Android中的OnClickListener以及OnLongClickListener,雖然都是點選,但是並沒有定義在一個介面中,而是分為兩個,因為很多時候,使用者只關心其中的一種。

迪米特原則

Law of Demeter 或者也叫做最少知識原則(Least Konwledge Principe)

定義:一個物件應該對其他物件有最少的瞭解。通俗點說,一個類應該對自己需要耦合或呼叫的類知道得最少,類的內部如何實現與呼叫者或者依賴者沒有關係。類與類之間的關係越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。
下面我們用租房為例來講講迪米特原則的應用。
房間:

public class Room {
    public float area;
    public float price;

    public Room(float area, float price) {
        this.area = area;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Room [area=" + area + ",price=" + price + "]";
    }
}

房間:

public class Mediator {
    List<Room> mRooms = new ArrayList<>();

    public Mediator() {
        for (int i = 0; i < 5; i++) {
            mRooms.add(new Room(14 + i, (14 + i) * 1500));
        }
    }

    public List<Room> getAllRooms() {
        return mRooms;
    }
}

租客:

public class Tenant {
    public float roomArea;
    public float roomPrice;
    public static final float diffPrice = 100.0001f;
    public static final float diffArea = 0.00001f;

    public void rentRoom(Mediator mediator) {
        List<Room> allRooms = mediator.getAllRooms();
        for (Room room : allRooms) {
            if (isSuitable(room)) {
                System.out.println("租到房間啦!" + room);
            }
        }
    }

    private boolean isSuitable(Room room) {
        return Math.abs(room.price - roomPrice) < diffPrice
                && Math.abs(room.area - roomArea) < diffArea;
    }
}

從程式碼中可以看到,Tenant不僅依賴了Mediator類,還需要頻繁地與Room類打交道。如果把檢測條件都放在Tenant類中,那麼中介類的功能就被弱化了,導致Tenant與Room的耦合,因為Tenant必須知道許多關於Room的細節,當Room變化時Tenant也必須跟著變化。就像下面UML描述的那樣:

4657803-0a06ed3d3872e419
image

既然耦合太嚴重了,那我們就只能解耦了。首先要明確的時,我們只和必要的類通訊,即移除Tenant與Room的依賴。我們進行如下修改:

public class Mediator {
    List<Room> mRooms = new ArrayList<>();

    public Mediator() {
        for (int i = 0; i < 5; i++) {
            mRooms.add(new Room(14 + i, (14 + i) * 1500));
        }
    }

    public Room rentOut(float area, float price) {
        for (Room room : mRooms) {
            if (isSuitable(area, price, room)) {
                return room;
            }
        }
        return null;
    }

    private boolean isSuitable(float area, float price, Room room) {
        return Math.abs(room.price - price) < Tenant.diffPrice
                && Math.abs(room.area - area) < Tenant.diffArea;
    }
}
public class Tenant {
    public float roomArea;
    public float roomPrice;
    public static final float diffPrice = 100.0001f;
    public static final float diffArea = 0.00001f;

    public void rentRoom(Mediator mediator) {
        System.out.println("租到房間啦!" + mediator.rentOut(roomArea, roomPrice));
    }
}

重構後的UML圖如下:

4657803-de77973a71ad0bce
image

只是將對Room的判定操作移到了Mediator類中,這本應該是Mediator的職責,根據租戶設定的條件查詢符合要求的房子,並且將結果交給租戶就可以了。租戶並不需要知道太多關於Room的細節。

相關文章