[Adapter] 包裝資料與邏輯在一起的物件
原始碼:
1: public class DS {
2: public string Input { get; set; }
3: public string Output { get; set;}
4: public void Process(){
5: // ... input >> process >> output
6: }
7: }
8:
9:
10: public class MainClass {
11: public void Main(){
12: DS ds = new DS();
13: ds.Input = "A";
14: ds.Process();
15: Console.WriteLine(ds.Output);
16: }
17: }
從上方的原始碼中我們可以看到DS具有2種功能:
1. 資料儲存體 ( Input , Output )
2. 邏輯處理器 ( Process )
限制:
DS物件為第三方元件不可變更其內部邏輯.
挑戰:
DS物件為第三方元件且可能會換版.
目標:
將DS物件的功能做切割及再包裝
一、界面定義
1: public interface IInput {
2:
3: }
4:
5: public interface IOutput {
6:
7: }
8:
9: public interface IProcess {
10: IOutput Process(IInput input);
11: }
二、再包裝
1: public class InputImpl : IInput {
2: public string Input { get; set;}
3: }
1: public class OutputImpl : IOutput {
2: public string Output { get; set;}
3: }
1: public class ProcessImpl : IProcess {
2: private DS ds = new DS(); // has without is
3: public IOutput Process(IInput input) {
4: InputImpl inputImpl = (InputImpl) input;
5: ds.Input = input.Input;
6: ds.Process();
7: return new OutputImpl() { Output : ds.Output };
8: }
9: }
三、測試
現在主程式已經跟第三方的"DS"類別相依性脫離了.
1: InputImpl input = new InputImpl(){ Input: "A" };
2: ProcessImpl process = new ProcessImpl();
3: OutputImpl output = (IOutput) process.Process(input);
四、效益在那裡?
未來如果第三方的"DS"類別有了差異只要改寫"ProcessImpl"類別即可,
不需再到各處改Reference到DS類別的程式了 :D
五、再加強
雖然結構上已經有了脫離, 但因為IInput及IOutput並沒有實質內容,
導致主程式及Process都用了轉型 (PS.累贅),
若IInput 及IOutput 實作了實質內容,
又會讓資料儲存體的彈性降低 (PS.也許不是每一種Input物件都有一個Input:string的屬性),
思考的重點是如何建立物件來增加彈性, 且使用泛型來解決轉型的問題,
後者比較容易可以先實作.
六、概念
某個Process的實作就是處理固定的Input & Output ,
透過泛型來固定Input及Output, 現在主程式不需要轉型了.
1: public interface IProcess<TInput ,TOutput>
2: where TInput : IInput
3: where TOutput : IOuput {
4: TOutput Process(TInput input);
5: }
6:
7:
8: public class ProcessImpl : IProcess<InputImpl, OutputImpl> {
9: public OutputImpl Process(InputImpl input){
10: // ... logic
11: }
12: }
七、再加強
Process 有沒有 實作 Factory 的必要呢?
考慮點是未來會不會有可能雖然是某個固定的Input & Output但有不同的流程且經常性變動
1: public interface IProcessFactory {
2: public ProcessImpl Create();
3: }
4:
5: public class ProcessFactory : IProcessFactory {
6: public static ProcessFactory GetInstance(Type type){
7: switch(type){
8: case MAIN_TYPE:
9: return new SimpleProcessFactory();
10: }
11: throw new NotImplementedException(type.ToString());
12: }
13: }
14:
15: internal class SimpleProcessFactory : IProcessFactory {
16: public Process Create(){
17: return new ProcessImpl();
18: }
19: }
八、效益在那裡
現在Main 類別的內容如下, Main 與 Input 及 Output 仍然相依著, 但 ProcessImpl 也可以抽換了.
1: ProcessImpl process = ProcessFactory.GetInstance(this.GetType());
2: OutputImpl output = process.Process(new InputImpl(){ Input : "A" });
3: Console.WriteLine(output.Output);
結論:
1. Adaptee(DS in 3party.dll) 可抽換了, Main程式完全不認識他.
2. Adaptor(ProcessImpl) 也可抽換了, Main 程式可透過不同的工廠來取得.
3. 有沒有必要都實作? 要看需求, 如果沒有彈性的必要性, 就不需提早過度設計了.
討論題:
IInput , IOutput 沒有實質內容的實作, 還有意義嗎? 該重構它並移除嗎?還是很重要一定要留下來呢?
大家來討論吧 :D
加強練習題:
如果連Input & Output 都會換來換去的, 那該怎麼彈性它呢?