views:

88

answers:

4

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 IPaymentTaker {
  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?

+2  A: 
interface IPayment
{
 IPaymentTaker Taker {get;}
}

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

payment.Taker.Process(payment); 
James Curran
That's a pretty good idea and would work fine in most situations, but I would usually expect the PaymentTakers to be loaded from an IoC container.The more general problem is that this binds the Taker to the payment, which does not allow different Takers for different contexts.
Gaz
+1  A: 

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);
TheCloudlessSky
Yep that one had occurred to me as well. Probably neater to use dynamic; i.e. dynamic paymentTaker = paymentTakers[paymentType]; paymentTaker.Process(paymentType).
Gaz
Hm, yeah never even though about that if you're using .NET 4.0. Good point!
TheCloudlessSky
So I tried testing `dynamic paymentTaker = paymentTakers[paymentType]; paymentTaker.Process(payment)` and it was throwing an exception so instead you'd need to write `paymentTaker.Process((dynamic)payment);`
TheCloudlessSky
I don't think the problem calls for reflection.
Steven Sudit
@Steven - Never said it did, but I just thought I'd write it for fun...
TheCloudlessSky
Just tested it myself without the cast of payment and it worked... what was the exception?
Gaz
@TheCloud: I appreciate your candor.
Steven Sudit
@Gax - `RuntimeBinderException - The best overloaded method match for 'CreditCardPaymentTaker.Process(CreditCardPayment)' has some invalid arguments`.
TheCloudlessSky
Hmmm... if you're passing in payment and the object in payment is of type CreditCardPayment it should work. You're not passing in paymentType by mistake are you?
Gaz
@Gaz - Nope. I works when I add `(dynamic)payment` instead of `payment`... I thought it was weird that I had to cast it to `dynamic` as well...
TheCloudlessSky
A: 

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.

Shaun Bowe
+2  A: 

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);
    // ...
  }
}
Jordão
That's perfect! Thanks Jordao!
Gaz