Amy(PO):
As a 建立訂單的秘書
I want 可以在同一介面上依據各供應商、產品和是否急件列印不同格式的訂單
So that 秘書可以不必因為上述之差異而到處KEY重複的資料
JB:
這個User Story我們分成兩個Task了,分別是"前端輸入"和"列印訂單";我們負責的是後者,至於訂單的來源都已經在資料庫了。
你有什麼看法? 或許可以用策略模式(Strategy)把不同列印的邏輯丟到主程式即可·
Lily:
的確是可行,不過依據需求,訂單除了各家不同還區分產品且還至少分成"一般"和"急件",
我想可以使用橋接模式(Bridge)來實做這個需求。
JB:
我看過Bridge的定義和說明,它和Strategy的差異在哪裡呢?
Lily:
Strategy屬於行為模式(Behavioral design patterns)。
Bridge屬於結構型模式(Structural design patterns),它將抽象和實做解耦合,使兩者可獨立的變化。
喔,我們還是來Pair Programming實際用Bridge來作為解決這個問題的結構,然後再加上Strategy處理列印的細節。
- 分離Abstraction及Implementor,使兩者可獨立變化。
- 在run time設定Abstraction裡的Implementor
- WIKI
以我們這個使用者案例來看,最後執行的結果是:列印訂單
。
所以依據以下條件對照Bridge:
抽象化的目標 | 對應Bridge的角色 |
---|---|
哪家供應商 | Abstraction |
如何列印 (一般還是急件,哪種產品) | Implementor |
我們先來寫列印(Implementor)的部分,並且先假設每家供應商相同的訂單格式是相同的,只有區分一般/急件。
後面我們再加上策略模式來調整 不同供應商商-相同產品-但格式不同的需求。
public interface IPrinter
{
void OrderA();
void OrderB();
}
public class PrinterUsual : IPrinter
{
public void OrderA()
{
System.Diagnostics.Trace.WriteLine("Order A (Take your time, bro)");
}
public void OrderB()
{
System.Diagnostics.Trace.WriteLine("Order B (Take your time, bro)");
}
}
public class PrinterEmergency : IPrinter
{
public void OrderA()
{
System.Diagnostics.Trace.WriteLine("Order A : Emergency!");
}
public void OrderB()
{
System.Diagnostics.Trace.WriteLine("Order B : Emergency!");
}
}
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def orderA(self):
pass
@abstractmethod
def orderB(self):
pass
class PrinterUsual(Printer):
def orderA(self):
print("Order A (Take your time, bro)")
def orderB(self):
print("Order B (Take your time, bro)")
class PrinterEmergency(Printer):
def orderA(self):
print("Order A : Emergency!")
def orderB(self):
print("Order B : Emergency!")
我們接下來會實做供應商處理訂單的 Abstraction。
注意為了區別兩家供應商,我們假設第二家供應商(Goople)並沒有提供產品B的服務,所以在要求列印產品B的時候會回覆錯誤訊息。
public interface IOrder
{
void PrintOrderA();
void PrintOrderB();
}
public class FatbookOrder : IOrder
{
private IPrinter _printer = null;
public FatbookOrder(IPrinter printer)
{
this._printer = printer;
}
public void PrintOrderA()
{
this._printer.OrderA();
}
public void PrintOrderB()
{
this._printer.OrderB();
}
}
public class GoopleOrder : IOrder
{
private IPrinter _printer = null;
public GoopleOrder(IPrinter printer)
{
this._printer = printer;
}
public void PrintOrderA()
{
this._printer.OrderA();
}
public void PrintOrderB()
{
string err = "Goople does't have product B!";
System.Diagnostics.Trace.WriteLine(err);
// throw new Exception(err);
}
}
from abc import ABC, abstractmethod
class Order(ABC):
@abstractmethod
def printOrderA(self):
pass
@abstractmethod
def printOrderB(self):
pass
class FatbookOrder(Order):
_printer = None
def __init__(self, printer=Printer):
if printer is None:
raise TypeError
else:
self._printer = printer
def printOrderA(self):
self._printer.orderA()
def printOrderB(self):
self._printer.orderB()
class GoopleOrder(Order):
_printer = None
def __init__(self, printer=Printer):
if printer is None:
raise TypeError
else:
self._printer = printer
def printOrderA(self):
self._printer.orderA()
def printOrderB(self):
err = "Goople does't have product B!"
print(err)
# raise ValueError(err)
主程式如下:
//列印第一家廠商:產品B的訂單
IOrder order1 = new FatbookOrder(new PrinterUsual());
order1.PrintOrderB();
//列印第二家廠商:產品A的急單
IOrder order2 = new GoopleOrder(new PrinterEmergency());
order2.PrintOrderA();
//列印第二家廠商:產品B的訂單=>但該廠商並無產品B
IOrder order3 = new GoopleOrder(new PrinterUsual());
order3.PrintOrderB();
# 列印第一家廠商:產品B的訂單
order1 = FatbookOrder(PrinterUsual())
order1.printOrderB()
# 列印第二家廠商:產品A的急單
order2 = GoopleOrder(PrinterEmergency())
order2.printOrderA()
# 列印第二家廠商:產品B的訂單=>但該廠商並無產品B
order3 = GoopleOrder(PrinterUsual())
order3.printOrderB()
結果為:
Order B (Take your time, bro)
Order A : Emergency!
Goople does't have product B!
由以上程式碼我們可以藉由抽換Abstraction以及Implementor來改變不同供應商不同列印的細節。
另外如果需要再達到相同產品但是各供應商訂單不同的需求,則可再加上Strategy的組合。
開始建立Strategy介面(或抽象類別)和實做類別。
public interface IPrintStg
{
void PrintA();
void PrintB();
}
public class FatbookPrintStg : IPrintStg
{
public void PrintA()
{
System.Diagnostics.Trace.WriteLine("Use FatbookPrintStg to Print A's oreder");
}
public void PrintB()
{
System.Diagnostics.Trace.WriteLine("Use FatbookPrintStg to Print B's oreder");
}
}
from abc import ABC, abstractmethod
class PrintStg(ABC):
@abstractmethod
def printA(self):
pass
@abstractmethod
def printB(self):
pass
class FatbookPrintStg(PrintStg):
def printA(self):
print("Use FatbookPrintStg to Print A's order")
def printB(self):
print("Use FatbookPrintStg to Print B's order")
更新實做Iprinter
的類別:PrinterUsual
和PrinterEmergency
,或者如下建立一個新的類別。
注意該類別裡面的邏輯已經抽換成IPrintStg
所定義的方法。
public class PrinterCostom : IPrinter
{
private IPrintStg _printStg = null;
public PrinterCostom(IPrintStg printStg)
{
this._printStg = printStg;
}
public void OrderA()
{
this._printStg.PrintA();
}
public void OrderB()
{
this._printStg.PrintB();
}
}
class PrinterCustom(Printer):
_printStg=None
def __init__(self, printStg=PrintStg):
if printStg is None:
raise TypeError
else:
self._printStg = printStg
def orderA(self):
self._printStg.printA()
def orderB(self):
self._printStg.printB()
來看一下主程式如何應用Bridge和Strategy的組合:
var stg = new FatbookPrintStg();
IOrder order = new FatbookOrder(new PrinterCostom(stg));
order.PrintOrderA();
order.PrintOrderB();
stg = FatbookPrintStg();
order = FatbookOrder(PrinterCustom(stg))
order.printOrderA()
order.printOrderB()
輸出結果為:
Use FatbookPrintStg to Print A's order
Use FatbookPrintStg to Print B's order
問一下,橋接模式可以算裝飾者模式+策略模式的組合餐嗎?
橋接模式(Bridge)是Strategy的延伸應用。
如以下的ConcreteClass
和Strategy
都利於抽換:
//Bridge
Interface A = new ConcreteClass(Strategy)
A.DoSomthing();
裝飾者(Decorator)則用於在不繼承原類別的情況下,加強原有類別(如下圖A):
//Decorator
Interface A = new ConcreteClassA();
Interface B = new ConcreteClassB(A);
Interface C = new ConcreteClassC(B);
C.DoSomthing();
Bridge和Decorator要解決的問題並不太一樣。 但事實上幾乎所有的Structural和Behavioral patterns都用到了Strategy的概念。 這是我的理解,請您參考看看 :)
Ok thanks
No problem :-)