Discussion 當Strategy Pattern碰到signature不一樣時,該怎麼設計

  • 2011年8月16日 下午 02:51
     
      包含代碼

    如題,在設計的時候,當原本程式碼存在著 利用if/else,來new不同的instance,執行抽象意義相同的方法時,通常我們會透過Strategy Pattern,改呼叫interface的方法。但這通常的前提是兩個instance的方法signature相同,才能直接透過一致的interface去呼叫。

    如果方法的signature不同時,該怎麼設計比較妥善呢?

    舉個例子,例如民眾去申辦某個作業,如果是代理人去,需要雙證件(身份證跟駕照)。如果本人去,只需要身份證。

    範例的程式碼如下:

    namespace StrategySample
    {
      class Program
      {
        /// <summary>
        /// 如果是代理人來申辦,需要有身份證跟駕照
        /// 如果是親自來申辦,只需要身份證
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
          var agent = new Agent();
          Person person = new Person() { IsAgent = true, EntrustAgent = agent };
    
          bool result;
          IdCard id = new IdCard(person.Id);
          if (person.IsAgent)
          {
            DrivingLicense drivingLicense = new DrivingLicense();
    
            result = person.EntrustAgent.ApplyDocument(id, drivingLicense);
          }
          else
          {
            result = person.ApplyDocument(id);
          }
        }
      }
    
      
    }
    public class Person
    {
      public bool IsAgent { get; set; }
    
      public string Id { get; set; }
    
      public Agent EntrustAgent { get; set; }
    
      internal bool ApplyDocument(IdCard id)
      {
        throw new NotImplementedException();
      }
    }
    
    public class Agent
    {
      internal bool ApplyDocument(IdCard id, DrivingLicense drivingLicense)
      {
        throw new NotImplementedException();
      }
    }
    
    public class IdCard
    {
      private string _id;
    
      public IdCard(string id)
      {
        // TODO: Complete member initialization
        this._id = id;
      }
    
    }
    
    public class DrivingLicense
    {
    }
    



    常用資源參考:
    小弟的blog: In 91,wiki: my wiki

所有回覆

  • 2011年8月16日 下午 02:58
     
     

    有想到的幾種作法:

    1. 針對signature不一樣的部分,一樣保留if/else。signature一樣的部分,則透過interface。
    2. 將參數與回傳值,定義成abstract class/super class/interface,讓signature一樣,再透過interface去呼叫ApplyDocument
    3. 將Person與Agent的ApplyDocument方法所需要的參數,改封裝到property。
      也就是呼叫person.ApplyDocument()或agent.ApplyDocument()。
      在person.ApplyDocument()的方法中,再調用person自己的property來做ApplyDocument的邏輯。
      在agent.ApplyDocument()的方法中,再調用agent自己的property來做ApplyDocument的邏輯。

    不曉得大家有沒更好的作法或是建議呢?


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
  • 2011年8月16日 下午 03:36
     
      包含代碼

    >>如果方法的signature不同時,該怎麼設計比較妥善呢?

    是說呼叫方法不同,還是方法同名但傳入引數不同?(小弟對這專有名詞不是很瞭解)

    以下小弟的做法,是去執行person.ApplyDocument(id,drivingLicense);
    person可能為代理人,也有可能為本人,但兩者都是執行ApplyDocument(id,drivingLicense);此方法

    您可能會疑問,要是本人沒駕照的情況呢?

    =>駕照的傳數傳null就好

    以下是小弟的淺見,和版主討論討論

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Xml;
    
    namespace StrategySample
    {
      class Program
      {
        /// <summary>
        /// 如果是代理人來申辦,需要有身份證跟駕照
        /// 如果是親自來申辦,只需要身份證
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
          DrivingLicense drivingLicense = new DrivingLicense();//駕照物件
          //這是代理人
          //Person person = new Agent("AAAA",drivingLicense);//不管是代理人還是本人都有自己的id身份證
          //這是本人
          Person person = new myself("mmmm", null);//本人沒有駕照
    
    
    
          person.ApplyDocument();
          Console.ReadKey();
        }
      }
    
    
    }
       public interface Person
      {
       //民眾去申辦某個作業
         void ApplyDocument();
       
      }
        //代理人
        public class Agent:Person
        {
          string id;
          DrivingLicense drivingLicense;
    
          //身份證、駕照
          public Agent(string id, DrivingLicense drivingLicense)
          { 
           this.id = id;
           this.drivingLicense = drivingLicense;
          
          }
          public void ApplyDocument()
          {
            Console.WriteLine("代理人要帶身份證和駕照");
          }
        }
    
        public class myself : Person
        {
          string id;
          DrivingLicense drivingLicense;
    
          //身份證、駕照
          public myself(string id, DrivingLicense drivingLicense)
          {
            this.id = id;
            this.drivingLicense = drivingLicense;
          
          
          }
          public void ApplyDocument()
          {
            Console.WriteLine("本人只要身份證就好");
          }
        }
    
    
        public class IdCard
        {
         private string _id;
    
         public IdCard(string id)
         {
         
           this._id = id;
         }
    
        }
    
      //駕照
      public class DrivingLicense
      {
    
    
    
      }
    
    
    
    
    

     


    Shadowと愉快なコード達
  • 2011年8月16日 下午 03:37
     
      包含代碼
      public class Person
      {
        public IdCard IdCard { get; set; }
    
        internal virtual bool ApplyDocument()
        {
          // Check IdCard
          //...
    
          throw new NotImplementedException();
        }
      }
    
      public class Agent : Person
      {
        public DrivingLicense DrivingLicense { get; set; }
    
        internal override bool ApplyDocument()
        {
          // Base 
          // ...
    
          // Check DrivingLicense
          // ...
    
          throw new NotImplementedException();
        }
      }
    

     


    學無止境
  • 2011年8月16日 下午 03:46
     
      包含代碼
     public class Person
     {
     public IdCard IdCard { get; set; }
    
     internal virtual bool ApplyDocument()
     {
      // Check IdCard
      //...
    
      throw new NotImplementedException();
     }
     }
    
     public class Agent : Person
     {
     public DrivingLicense DrivingLicense { get; set; }
    
     internal override bool ApplyDocument()
     {
      // Base 
      // ...
    
      // Check DrivingLicense
      // ...
    
      throw new NotImplementedException();
     }
     }
    

     


    學無止境

    恩,謝謝Clark兄,我這例子舉的情況,看起來剛好可以用繼承來省掉一份code。

    如果不是這例子的情況,Agent與Person『不能用繼承的情況』呢?

    例如Agent的話,需要帶『委託書』與『切結書』。本人的話則是帶『身份證』。


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
  • 2011年8月16日 下午 03:49
     
      包含代碼

    >>如果方法的signature不同時,該怎麼設計比較妥善呢?

    是說呼叫方法不同,還是方法同名但傳入引數不同?(小弟對這專有名詞不是很瞭解)

    以下小弟的做法,是去執行person.ApplyDocument(id,drivingLicense);
    person可能為代理人,也有可能為本人,但兩者都是執行ApplyDocument(id,drivingLicense);此方法

    您可能會疑問,要是本人沒駕照的情況呢?

    =>駕照的傳數傳null就好

    以下是小弟的淺見,和版主討論討論

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Xml;
    
    namespace StrategySample
    {
     class Program
     {
      /// <summary>
      /// 如果是代理人來申辦,需要有身份證跟駕照
      /// 如果是親自來申辦,只需要身份證
      /// </summary>
      /// <param name="args"></param>
      static void Main(string[] args)
      {
       DrivingLicense drivingLicense = new DrivingLicense();//駕照物件
       //這是代理人
       //Person person = new Agent("AAAA",drivingLicense);//不管是代理人還是本人都有自己的id身份證
       //這是本人
       Person person = new myself("mmmm", null);//本人沒有駕照
    
    
    
       person.ApplyDocument();
       Console.ReadKey();
      }
     }
    
    
    }
      public interface Person
     {
      //民眾去申辦某個作業
       void ApplyDocument();
      
     }
      //代理人
      public class Agent:Person
      {
       string id;
       DrivingLicense drivingLicense;
    
       //身份證、駕照
       public Agent(string id, DrivingLicense drivingLicense)
       { 
        this.id = id;
        this.drivingLicense = drivingLicense;
       
       }
       public void ApplyDocument()
       {
        Console.WriteLine("代理人要帶身份證和駕照");
       }
      }
    
      public class myself : Person
      {
       string id;
       DrivingLicense drivingLicense;
    
       //身份證、駕照
       public myself(string id, DrivingLicense drivingLicense)
       {
        this.id = id;
        this.drivingLicense = drivingLicense;
       
       
       }
       public void ApplyDocument()
       {
        Console.WriteLine("本人只要身份證就好");
       }
      }
    
    
      public class IdCard
      {
       private string _id;
    
       public IdCard(string id)
       {
       
        this._id = id;
       }
    
      }
    
     //駕照
     public class DrivingLicense
     {
    
    
    
     }
    
    
    
    
    

     


    Shadowと愉快なコード達

    謝謝Shadow兄的回應,若是new Myself()或new Agent()的部分,是透過Factory Pattern去取回執行的instance,那建構式的部分該怎麼處理呢?

    另外,您的例子裡面,如果是myself,建構式的參數,應該就不需要傳駕照進去了。


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
  • 2011年8月16日 下午 03:54
     
     

    發現我原本的舉例實在太鳥了,

    再舉另外一種情況,假設是兩間銀行要申辦信用卡,需要帶的文件不同,還會根據客戶的年收入不同來決定。

    這樣應該比較不會有繼承的情況發生,就算要繼承,應該也會有個共同的祖先。:P


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
  • 2011年8月16日 下午 04:00
     
      包含代碼
      public class Person
      {
        public IdCard IdCard { get; set; }
    
        public bool ApplyDocument()
        {
          // Check IdCard
          //...
    
          throw new NotImplementedException();
        }
      }
    
      public class Agent
      {
        public Passport Passport { get; set; }
    
        public DrivingLicense DrivingLicense { get; set; }
    
        public bool ApplyDocument()
        {
          // Check Passport
          // ...
    
          // Check DrivingLicense
          // ...
    
          throw new NotImplementedException();
        }
      }
      
      public interface IUser
      {
        bool ApplyDocument();
      }
    
      public class PersonUserAdapter : IUser
      {
        private readonly Person _adaptee;
        
        public bool ApplyDocument()
        {
          return _adaptee.ApplyDocument();
        }
      }
    
      public class AgentUserAdapter : IUser
      {
        private readonly Agent _adaptee;
    
        public bool ApplyDocument()
        {
          return _adaptee.ApplyDocument();
        }
      }
    
    把雜七雜八的都抽出去,只留下bool ApplyDocument()就好 ^^
    


    學無止境
  • 2011年8月16日 下午 04:00
     
      包含代碼

    我的想法是這樣的,提供一個介面IPerson,他具有身分證和駕照

    實作後分為本人和代理人,代理人可向本人取得身分證和駕照進行申請,但兩者皆繼承IPerson

     

     public interface IPerson
     {
      IdCard Card { get; set; }
    
      DrivingLicense License { get; set; }
     }
    
     public class Person : IPerson
     {
      public IdCard Card { get; set; }
    
      public DrivingLicense License { get; set; }
     }
    
     public class PersonAgent : IPerson
     {
      public PersonAgent(IPerson person)
      {
       this.Card = person.Card;
       this.License = person.License;
      }
    
      public IdCard Card { get; set; }
    
      public DrivingLicense License { get; set; }
     }
    

     

    根據本人或代理人,提供不同的驗證方式,但入口皆為IPerson介面

     

     public interface IVerify
     {
      void Verify(IPerson person);
     }
    
     public class PersonVerifier : IVerify
     {
      public void Verify(IPerson person)
      {
       Console.WriteLine("Self Apply, ID: {0}", person.Card.ID);
      }
     }
    
     public class AgentVerifier : IVerify
     {
      public void Verify(IPerson person)
      {
       Console.WriteLine("Agent Apply, ID: {0}, License: {1}", person.Card.ID,person.License.Name);
      }
     }
    

     


    而將選擇驗證方式的策略,封裝至工廠之中

     

      public class VerifierFactory
     {
      public IVerify GetVerifier(IPerson person)
      {
       if (person is Person)
       {
        return new PersonVerifier();
       }
       else if (person is PersonAgent)
       {
        return new AgentVerifier();
       }
       else
       {
        return null;
       }
      }
     }
    

     

    提供一個Business Logic層,作為申請的進入點,允許各種IPerson提出申請,

    並根據不同的IPerson類別,提供相對應的驗證方式

     

     public class VerifyService
     {
      public VerifyService()
      {
       this.VerifierFactory = new VerifierFactory();
      }
    
      public VerifierFactory VerifierFactory { get; set; }
    
      public void VerifyApplyment(IPerson person)
      {
       IVerify verifier = this.VerifierFactory.GetVerifier(person);
    
       if (verifier!=null)
       {
        verifier.Verify(person); 
       }
       else
       {
        throw new Exception("Verifier not found!");
       }
      }
     }
    

     

    實際執行

     

     class Program
     {
      static void Main(string[] args)
      {
       VerifyService flow = new VerifyService();
    
       // 1. 親自申請
       IPerson person = new Person() { Card = new IdCard() { ID = 1 }, License = new DrivingLicense() { Name = "Kirk" } };   
    
       flow.VerifyApplyment(person);
    
       // 2. 代理人申請
       IPerson agent = new PersonAgent(person);
    
       flow.VerifyApplyment(agent);   
      }
     }
    

     


    如此一來,當擴充Person時,只需要修改VerifierFactory及增加Verifier,主要商業邏輯不須更動

    更甚者可將工廠改為抽象工廠,那只需修改設定檔即可完成擴充,

    這是我的一點點小想法,提供出來和大家討論看看