Динамический выбор типа компонента с учетом типа его параметра общего типа - PullRequest
2 голосов
/ 06 августа 2010

Мне часто кажется, что я сталкиваюсь с ситуацией, когда у меня есть абстрактный тип, который должен обрабатываться по-разному, в зависимости от конкретной реализации.

Например, abstract class Payment может быть разделено на подклассы как class CreditCard или class StoredCredit.Для фактической обработки платежа мы хотим использовать реализацию

interface IPaymentTaker {
  PaymentProcessingResult Process(Payment payment); }

, то есть либо

class CreditCardPaymentTaker : IPaymentTaker { ... }

, либо

class StoredCreditPaymentTaker : IPaymentTaker { ... }

В прошлом я вводилIDictionary в родительский компонент и затем выполняется

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

Недостатком этого является то, что реализации IPaymentTaker недостаточно строго типизированы, поэтому первый бит метода Process должен быть:

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

Я уверен, что должно быть имя для шаблона, который я пытаюсь реализовать, но я не знаю, что это такое!

В идеале я бы

(а) мог бы создать экземпляр ProcessProcessor только на основе типа Платежа, не создавая словарь;

(b) мог быстрого типизировал PaymentProcessors, которые принимают только тот подкласс, который они могут использовать.

У кого-нибудь есть изящный способ решения этой проблемы?

Ответы [ 4 ]

2 голосов
/ 07 августа 2010

Вы можете решить эту проблему с посетителем :

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);
    // ...
  }
}

Если вы все еще хотите разделить разных получателей платежей или если ваша иерархия дрожит, вы можете использовать ациклический посетитель (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);
    // ...
  }
}
2 голосов
/ 06 августа 2010
interface IPayment
{
 IPaymentTaker Taker {get;}
}

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

payment.Taker.Process(payment); 
1 голос
/ 06 августа 2010

Несмотря на то, что метод Джеймса идеален, использование контейнера IoC может быть затруднено. Вот мой подход, основанный на отражении или динамике. Выполнение следующих действий позволит вам все еще использовать IoC для настройки отображения между PaymentTaker и 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...");
    }
}

И тогда вы можете использовать его так:

var cc = new CreditCardPayment();
PaymentTaker.Process(cc);
0 голосов
/ 06 августа 2010

Если вы можете гарантировать совпадение имен Payment и PaymentTaker, вы можете использовать что-то вроде этого:

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

Я использовал этот подход в прошлом, но если у вас нет 100% контроляИмена классов это может быть проблемой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...