什麼是裝飾者模式?
裝飾者模式屬於結構型設計模式之一,主要目的是透過包裝物件而不是繼承來擴充套件功能。這種模式允許使用者動態地為物件新增新的行為而無需修改其原始碼。這與繼承相比提供了一種更為靈活的方式來擴充套件功能。
裝飾者模式的關鍵組成部分:
抽象元件(Component):定義核心業務介面。
具體元件(ConcreteComponent):核心業務實現類,實現了抽象元件介面。
裝飾器(Decorator):持有抽象元件的引用,實現抽象元件介面,用來擴充套件功能。
適用場景
想增強現有類的功能,但又不想改變這個類的原有程式碼。
想無需修改原有程式碼的情況下即可使用物件。
透過繼承來擴充套件物件行為的方案難以實現或者根本不可行。
實踐
下面是一個基礎的點餐功能。現在點餐的基礎功能上新增,折扣、飲料兩個功能。
基礎服務
介面
public interface OrderService {
double getPrice(); // 獲取價格
void placeOrder(); // 下單
}
實現類:
@Service
public class OrderServiceImpl implements OrderService {
private final double price = 50.0; // 固定價格
@Override
public double getPrice() {
return price;
}
@Override
public void placeOrder() {
System.out.println("單價: " + getPrice());
}
}
擴充套件功能
我們將實現兩個新功能:折扣和飲料。這兩個功能將分別使用裝飾者模式進行實現。
折扣裝飾者
我們將建立一個折扣裝飾者,允許在訂單上應用折扣
public class DiscountDecorator implements OrderService {
private final OrderService orderService; // 被裝飾的物件
private final double discount; // 折扣金額
public DiscountDecorator(OrderService orderService, double discount) {
this.orderService = orderService;
this.discount = discount;
}
@Override
public double getPrice() {
return orderService.getPrice() - discount; // 返回折扣後的價格
}
@Override
public void placeOrder() {
orderService.placeOrder();
System.out.println("折扣: " + discount);
}
}
飲料服務介面和實現
新增一個飲料介面和其實現類,以支援飲料功能。
介面
public interface DrinkService {
double getPrice(); // 獲取飲料價格
}
實現類
@Service
public class DrinkServiceImpl implements DrinkService {
private final double price = 0; // 初始化飲料價格
@Override
public double getPrice() {
return price;
}
}
飲料裝飾者
public abstract class DrinkServiceDecorator implements DrinkService {
protected DrinkService drinkService;
public DrinkServiceDecorator(DrinkService drinkService) {
this.drinkService = drinkService;
}
@Override
public double getPrice() {
return drinkService.getPrice();
}
}
ColaDecorator.java 可樂類
public class ColaDecorator extends DrinkServiceDecorator {
private final double additionalPrice = 5.0; // 可樂附加價格
public ColaDecorator(DrinkService drinkService) {
super(drinkService);
}
@Override
public double getPrice() {
return super.getPrice() + additionalPrice; // 新增可樂價格
}
}
OrangeJuiceDecorator.java 橙汁類
public class OrangeJuiceDecorator extends DrinkServiceDecorator {
private final double additionalPrice = 6.0; // 橙汁附加價格
public OrangeJuiceDecorator(DrinkService drinkService) {
super(drinkService);
}
@Override
public double getPrice() {
return super.getPrice() + additionalPrice; // 新增橙汁價格
}
}
修改控制器以支援新功能
@RestController
public class OrderController {
private OrderService orderService; //訂單服務
private DrinkService drinkService; // 飲料服務
public OrderController(OrderService orderService, DrinkService drinkService) {
this.orderService = orderService;
this.drinkService = drinkService;
}
@GetMapping("/placeOrder")
public String placeOrder(@RequestParam(value = "drinkType", required = false) String drinkType,
@RequestParam(value = "discount", required = false) Double discount) {
double totalPrice = orderService.getPrice();
// 處理飲料邏輯
if ("cola".equalsIgnoreCase(drinkType)) {
drinkService = new ColaDecorator(drinkService);
} else if ("orange".equalsIgnoreCase(drinkType)) {
drinkService = new OrangeJuiceDecorator(drinkService);
}
// 處理折扣邏輯
if (discount != null) {
orderService = new DiscountDecorator(orderService, discount);
}
totalPrice = drinkService.getPrice() + orderService.getPrice(); // 計算折扣後的訂單價格
orderService.placeOrder();
System.out.println("新增的飲料價格: " + drinkService.getPrice());
return "總金額: + " + totalPrice;
}
}
測試
基礎功能
保證以前的功能不受影響:直接下單符合預期50
下單並同時新增可樂和折扣:
單價50,可樂5,折扣20,符合預期35
總結
裝飾者模式比繼承較為靈活,不改變原有物件的情況下,動態的給一個物件擴充套件功能,即插即用。
繼承:在編譯時定義類的行為,任何新功能的新增都需要建立新的子類,一旦編譯,類的行為就已經固定,無法在執行時更改。
參考
https://refactoringguru.cn/design-patterns/decorator