我們已經回顧了單例對象的使用,但您可能還沒有意識到這種策略是一種設計模式,而且是最常用的設計模式之一。

事實上,這些模式有很多,可以根據其特定用途進行分類。

花樣分類

圖案類型 應用
創意的 解決對象創建問題的類型
結構性的 讓我們在架構中構建正確且可擴展的類層次結構的模式
行為的 這組模式促進了程序中對象之間的安全和方便的交互。

通常,模式的特徵在於它解決的問題。讓我們看一下我們在使用 Java 時最常遇到的幾種模式:

圖案 目的
單例 我們已經熟悉這種模式——我們用它來創建和訪問一個不能有多個實例的類。
迭代器 這個我們也很熟悉。我們知道這種模式可以讓我們在不暴露其內部表示的情況下迭代一個集合對象。它與集合一起使用。
適配器 此模式連接不兼容的對象,以便它們可以協同工作。我認為適配器模式的名稱可以幫助您準確地想像它的作用。這是現實生活中的一個簡單示例:牆上插座的 USB 適配器。
模板法

一種解決集成問題並允許您更改算法步驟而不更改算法結構的行為編程模式。

想像一下,我們有一個裝配步驟序列形式的汽車裝配算法:

底盤 -> 車身 -> 發動機 -> 車廂內飾

如果我們放入一個加固的框架,一個更強大的引擎,或者一個帶有額外照明的內部空間,我們不必改變算法,抽象序列保持不變。

裝潢師 此模式為對象創建包裝器以賦予它們有用的功能。我們將把它視為本文的一部分。

在 Java.io 中,以下類實現了模式:

圖案 它在java.io中的使用位置
適配器
模板法
裝潢師

裝飾者模式

假設我們正在描述一個家居設計模型。

通常,該方法如下所示:

最初,我們可以選擇幾種類型的房屋。最低配置是一層有屋頂。然後我們使用各種裝飾器來改變額外的參數,這自然會影響房子的價格。

我們創建一個抽象的 House 類:


public abstract class House {
	String info;
 
	public String getInfo() {
    	return info;
	}
 
	public abstract int getPrice();
}
    

這裡我們有2種方法:

  • getInfo()返回有關我們房子的名稱和特徵的信息;
  • getPrice()返回當前房屋配置的價格。

我們還有標準的 House 實現——磚木結構:


public class BrickHouse extends House {
 
	public BrickHouse() {
    	info = "Brick House";
	}
 
	@Override
	public int getPrice() {
    	return 20_000;
	}
}
 
public class WoodenHouse extends House {
 
	public WoodenHouse() {
    	info = "Wooden House";
	}
 
	@Override
	public int getPrice() {
    	return 25_000;
	}
}
    

這兩個類都繼承了House類並覆蓋了它的價格方法,為標準房屋設置了自定義價格。我們在構造函數中設置名稱。

接下來,我們需要編寫裝飾器類。這些類也將繼承House類。為此,我們創建了一個抽象裝飾器類。

這就是我們將放置用於更改對象的附加邏輯的地方。最初,沒有額外的邏輯,抽像類是空的。


abstract class HouseDecorator extends House {
}
    

接下來,我們創建裝飾器實現。我們將創建幾個類,讓我們為房子添加額外的功能:


public class SecondFloor extends HouseDecorator {
	House house;
 
	public SecondFloor(House house) {
    	this.house = house;
	}
 
	@Override
	public int getPrice() {
    	return house.getPrice() + 20_000;
	}
 
	@Override
	public String getInfo() {
    	return house.getInfo() + " + second floor";
	}
}
    
為我們的房子增加二樓的裝飾師

裝飾器構造函數接受我們將“裝飾”的房子,即添加修改。我們重寫了getPrice()getInfo()方法,根據舊房子返回更新後的新房子的信息。


public class Garage extends HouseDecorator {
 
	House house;
	public Garage(House house) {
    	this.house = house;
	}
 
	@Override
	public int getPrice() {
    	return house.getPrice() + 5_000;
	}
 
	@Override
	public String getInfo() {
    	return house.getInfo() + " + garage";
	}
}
    
為我們的房子增加車庫的裝飾師

現在我們可以用裝飾器更新我們的房子。為此,我們需要創建一個房子:


House brickHouse = new BrickHouse();
    

接下來,我們設置我們的房子變量等於一個新的裝飾器,傳入我們的房子:


brickHouse = new SecondFloor(brickHouse); 
    

我們的房子變量現在是二樓的房子。

讓我們看看涉及裝飾器的用例:

示例代碼 輸出

House brickHouse = new BrickHouse(); 

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

磚房

20000


House brickHouse = new BrickHouse(); 

  brickHouse = new SecondFloor(brickHouse); 

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

磚房+二樓

40000


House brickHouse = new BrickHouse();
 

  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

磚房+二層+車庫

45000


House woodenHouse = new SecondFloor(new Garage(new WoodenHouse())); 

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());
                    

木屋+車庫+二樓

50000


House woodenHouse = new WoodenHouse(); 

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());
                    

木房子

25000

木屋+車庫

30000

此示例說明了使用裝飾器升級對象的好處。所以我們沒有改變木房子對象本身,而是在舊對象的基礎上創建了一個新對象。在這裡我們可以看出利與弊:我們每次都在內存中創建一個新對象,增加了內存消耗。

看看我們程序的 UML 圖:

裝飾器具有超級簡單的實現並動態更改對象,升級它們。裝飾器可以通過它們的構造函數來識別,構造函數將與當前類相同的抽像類型或接口的對像作為參數。在 Java 中,這種模式被廣泛用於 I/O 類中。

例如,正如我們已經註意到的,java.io.InputStreamOutputStreamReaderWriter的所有子類都有一個接受相同類對象的構造函數。