开发者

Dynamically selecting the type of a component given the type of its generic type parameter

开发者 https://www.devze.com 2023-01-10 18:02 出处:网络
I frequently seems to come up to a situation where I have an abstract type which needs to be processed differently depending on which concrete implementation it has.

I frequently seems to come up to a situation where I have an abstract type which needs to be processed differently depending on which concrete implementation it has.

As an example, an abstract class Payment could be subclassed as class CreditCard or class StoredCredit. To actually process the payment, we want to use an implementation of

interface IPaymentT开发者_高级运维aker {
  PaymentProcessingResult Process(Payment payment); }

i.e. either

class CreditCardPaymentTaker : IPaymentTaker { ... }

or

class StoredCreditPaymentTaker : IPaymentTaker { ... }

In the past I have injected an IDictionary into the parent component and then done

_paymentTakers[payment.GetType()].Process(payment);

The downside of this is that the IPaymentTaker implementations are not strongly typed enough, so the first bit of the Process method has to be:

Process(Payment payment)
{
  var creditCardPayment = payment as CreditCardPayment;
  if (creditCardPayment == null)
    throw new Exception("Payment must be of type CreditCard");
}

I'm sure there must be a name for the pattern I'm trying to implement but I don't know what it is!

Ideally I would

(a) be able to instantiate the PaymentProcessor based just on the type of the Payment, without creating the dictionary;

(b) be able to have strongly typed PaymentProcessors that only accept the subclass they can use.

Does anyone have a neat way of solving this problem?


You can solve this with a visitor:

interface IPaymentVisitor {
  void Visit(CreditCard payment);
  void Visit(StoredCredit payment);
}

abstract class Payment {
  public abstract void Accept(IPaymentVisitor visitor);
}
class CreditCard : Payment {
  public override void Accept(IPaymentVisitor visitor) {
    visitor.Visit(this);
  }
}
class StoredCredit : Payment {
  public override void Accept(IPaymentVisitor visitor) {
    visitor.Visit(this);
  }
}

class PaymentTaker : IPaymentVisitor, IPaymentTaker {
  public void Visit(CreditCard payment) {
    // ... 
  }

  public void Visit(StoredCredit payment) {
    // ... 
  }

  public PaymentProcessingResult Process(Payment payment) {
    payment.Accept(this);
    // ...
  }
}

If you still want to separate the different payment takers, or if your hierarchy jitters, you can use an acyclic visitor (pdf):

interface IPaymentVisitor {
}

interface IPaymentVisitor<TPayment> : IPaymentVisitor where TPayment : Payment {
  void Visit(TPayment payment);
}

abstract class Payment {
  public abstract void Accept(IPaymentVisitor visitor);
}
class CreditCard : Payment {
  public override void Accept(IPaymentVisitor visitor) {
    if (visitor is IPaymentVisitor<CreditCard>) {
      ((IPaymentVisitor<CreditCard>)visitor).Visit(this);
    }
  }
}
class StoredCredit : Payment {
  public override void Accept(IPaymentVisitor visitor) {
    if (visitor is IPaymentVisitor<StoredCredit>) {
      ((IPaymentVisitor<StoredCredit>)visitor).Visit(this);
    }
  }
}

class CreditCardPaymentTaker : IPaymentVisitor<CreditCard>, IPaymentTaker {
  public void Visit(CreditCard payment) {
    // ...
  }
  public PaymentProcessingResult Process(Payment payment) {
    payment.Accept(this);
    // ...
  }
}
class StoredCreditPaymentTaker : IPaymentVisitor<StoredCredit>, IPaymentTaker {
  public void Visit(StoredCredit payment) {
    // ...
  }
  public PaymentProcessingResult Process(Payment payment) {
    payment.Accept(this);
    // ...
  }
}


interface IPayment
{
 IPaymentTaker Taker {get;}
}

class CreditCardPayment : IPayment
{
  IPaymentTaker Taker{ get {return new CreditCardPaymentTaker();}}
}

payment.Taker.Process(payment); 


Even though James' method is ideal, using an IoC container could be difficult. Here's my Reflection or dynamics based approach. Doing the following will allow you to still use an IoC to setup the mapping between the PaymentTaker and Payment.

public class Payment
{

}

public class CreditCardPayment : Payment
{

}

public class StoreCreditPayment : Payment
{

}

public interface IPaymentTaker
{

}

public interface IPaymentTaker<T> : IPaymentTaker
{
    void Process(T payment);
}

public static class PaymentTaker
{
    public static void Process(Payment payment)
    {
        var paymentType = payment.GetType();

        // You would have these already setup and loaded via your IOC container...
        var paymentTakers = new Dictionary<Type, IPaymentTaker>();
        paymentTakers.Add(typeof(CreditCardPayment), new CreditCardPaymentTaker());
        paymentTakers.Add(typeof(StoreCreditPayment), new StoreCreditPaymentTaker());

        // Get the payment taker for the specific payment type.
        var paymentTaker = paymentTakers[paymentType];

        // Execute the 'Process' method.
        paymentTaker.GetType().GetMethod("Process").Invoke(paymentTaker, new object[]{ payment });
        // If .NET 4.0 - dynamics can be used.
        // dynamic paymentTaker = paymentTakers[paymentType];
        // paymentTaker.Process((dynamic)payment);

    }
}

public class CreditCardPaymentTaker : IPaymentTaker<CreditCardPayment>
{
    public void Process(CreditCardPayment payment)
    {
        Console.WriteLine("Process Credit Card Payment...");
    }
}

public class StoreCreditPaymentTaker : IPaymentTaker<StoreCreditPayment>
{
    public void Process(StoreCreditPayment payment)
    {
        Console.WriteLine("Process Credit Card Payment...");
    }
}

And then you can use it like this:

var cc = new CreditCardPayment();
PaymentTaker.Process(cc);


If you can ensure the names of the Payment and PaymentTaker match you can use something like this:

Process(Payment payment)
{
    String typeName = "YourPathToPaymentTakers." + payment.GetType().Name + "Taker";
    Type type = typeof(IPaymentTaker).Assembly.GetType(typeName);                       
    IPaymentTaker taker = (IPaymentTaker)Activator.CreateInstance(type);;
}

I have used this approach in the past, but if you do not have 100% control of the names of the classes this could be a problem.

0

精彩评论

暂无评论...
验证码 换一张
取 消