Flyweight Pattern (享元模式) 定義 透過共享物件來減少系統中創建重複物件的內存使用量,特別適合用於大規模物件的場景中。該模式的核心在於將物件分解為內部狀態(intrinsic state)和外部狀態(extrinsic state),並共享內部狀態以達到節省資源的效果。
sequenceDiagram
participant Client
participant FlyweightFactory
participant Flyweight
Note over Flyweight: Intrinsic State<br/>(內部狀態,共享)
Client->>FlyweightFactory: getFlyweight(key)
alt Flyweight not found
FlyweightFactory->>FlyweightFactory: create new Flyweight
end
FlyweightFactory-->>Client: return Flyweight
Client->>Flyweight: pass Extrinsic State<br/>(外部狀態)
Flyweight-->>Client: execute behavior<br/>using both states
核心概念 1. 內部狀態(Intrinsic State)
固定且可以被多個物件共享的狀態。
通常由享元物件自身管理。
2. 外部狀態(Extrinsic State)
會隨著物件的使用情況而變化的狀態。
通常由客戶端管理。
3. 共享池(Flyweight Factory)
範例 重構前 在下列程式中,HotelRoom 類別同時持有「房型資訊」與「房間編號」等所有資料。當系統中有大量相同房型(例如:Deluxe Double)的房間時,會造成重複資訊的儲存,浪費記憶體。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class HotelRoom { private String roomType; private String bedType; private int capacity; private double price; private int roomNumber; private boolean isOccupied; public HotelRoom (String roomType, String bedType, int capacity, double price, int roomNumber, boolean isOccupied) { this .roomType = roomType; this .bedType = bedType; this .capacity = capacity; this .price = price; this .roomNumber = roomNumber; this .isOccupied = isOccupied; } public void displayRoomInfo () { System.out.println("Room Number: " + roomNumber + ", Type: " + roomType + ", Bed: " + bedType + ", Capacity: " + capacity + ", Price: " + price + (isOccupied ? " (Occupied)" : " (Available)" )); } }
假設有一個 Hotel 類別管理所有房間,每新增一個房間,都要重複建立相同房型的資訊:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.util.ArrayList;import java.util.List;public class Hotel { private List<HotelRoom> rooms; public Hotel () { rooms = new ArrayList <>(); rooms.add(new HotelRoom ("Deluxe" , "Double" , 2 , 1200 , 101 , false )); rooms.add(new HotelRoom ("Deluxe" , "Double" , 2 , 1200 , 102 , true )); rooms.add(new HotelRoom ("Suite" , "King" , 4 , 2500 , 201 , false )); rooms.add(new HotelRoom ("Suite" , "King" , 4 , 2500 , 202 , false )); } public void showAllRooms () { for (HotelRoom room : rooms) { room.displayRoomInfo(); } } }
問題
重複儲存
:roomType, bedType, capacity, price 在相同房型的每個物件都重複存在。
浪費記憶體
:若系統中有成百上千的房間,重複資料會佔用大量資源。
不易維護
:若要修改某個共用房型資訊,可能會需要對多個物件進行調整。
重構後
Intrinsic State (內部狀態) :可被多個物件共享、不會因使用情境而改變的資料。 可以抽取「房型、床型、可容納人數、基本價格」等作為內部狀態。
Extrinsic State (外部狀態) :依賴運行情境、需要額外傳入或分別儲存的資料。 「房間編號、是否被佔用」屬於外部狀態。
RoomType 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class RoomType { private String roomType; private String bedType; private int capacity; private double basePrice; public RoomType (String roomType, String bedType, int capacity, double basePrice) { this .roomType = roomType; this .bedType = bedType; this .capacity = capacity; this .basePrice = basePrice; } public void showRoomTypeInfo () { System.out.printf("Type: %s, Bed: %s, Capacity: %d, Base Price: %.2f%n" , roomType, bedType, capacity, basePrice); } }
Flyweight Factory 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.HashMap;import java.util.Map;public class RoomTypeFactory { private static final Map<String, RoomType> ROOM_TYPES = new HashMap <>(); public static RoomType getRoomType (String roomType, String bedType, int capacity, double basePrice) { String key = roomType + "-" + bedType + "-" + capacity + "-" + basePrice; if (!ROOM_TYPES.containsKey(key)) { RoomType newType = new RoomType (roomType, bedType, capacity, basePrice); ROOM_TYPES.put(key, newType); } return ROOM_TYPES.get(key); } }
修正 HotelRoom 類別只保存外部狀態 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class HotelRoom { private final RoomType roomType; private final int roomNumber; private boolean isOccupied; public HotelRoom (RoomType roomType, int roomNumber, boolean isOccupied) { this .roomType = roomType; this .roomNumber = roomNumber; this .isOccupied = isOccupied; } public void displayRoomInfo () { System.out.printf("Room Number: %d, %s" , roomNumber, (isOccupied ? "Occupied" : "Available" ) + " -> " ); roomType.showRoomTypeInfo(); } }
Hotel 類別改為透過工廠取得房型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.util.ArrayList;import java.util.List;public class Hotel { private List<HotelRoom> rooms; public Hotel () { rooms = new ArrayList <>(); RoomType deluxeDouble = RoomTypeFactory.getRoomType("Deluxe" , "Double" , 2 , 120.0 ); rooms.add(new HotelRoom (deluxeDouble, 101 , false )); rooms.add(new HotelRoom (deluxeDouble, 102 , true )); RoomType suiteKing = RoomTypeFactory.getRoomType("Suite" , "King" , 4 , 250.0 ); rooms.add(new HotelRoom (suiteKing, 201 , false )); rooms.add(new HotelRoom (suiteKing, 202 , true )); } public void showAllRooms () { for (HotelRoom room : rooms) { room.displayRoomInfo(); } } }
優點
減少記憶體消耗
在重構前,每個 HotelRoom 物件都重複保存了相同的「房型資訊」。若飯店中有上千個房間,且重複的房型很多,就會造成大量重複儲存。 重構後,所有相同房型的 HotelRoom 都指向同一個 RoomType 物件 ,顯著減少記憶體使用量。
資料一致性高
若需要修改某個房型的共用資訊(例如:Deluxe Double 的價格),只要在 RoomType 中統一調整即可,所有引用該房型的房間都能同步更新 。
維護性更佳
分離「內部狀態」(可共享的房型資訊) 與「外部狀態」(房號、佔用情況) 後,可明確區分出哪些資料需要共享、哪些資料需要獨立保存,更容易擴充與維護程式。