2010-08-06 59 views
2

我经常似乎来了这样一种情况,我有一个抽象类,其需要取决于它有哪些具体的实现以不同的方式处理其泛型类型参数的类型组件的类型。动态选择给

作为示例,abstract class Payment可以被分类为class CreditCardclass StoredCredit。要真正处理付款,我们要使用的实现

interface IPaymentTaker { 
    PaymentProcessingResult Process(Payment payment); } 

即要么

class CreditCardPaymentTaker : IPaymentTaker { ... } 

class StoredCreditPaymentTaker : IPaymentTaker { ... } 

在过去,我已经注入一个IDictionary到父组件和然后做

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

这种方法的缺点是,IPaymentTaker实现并不强类型的不够,所以这个过程方法的第一位必须是:

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

我敢肯定,必须有我的格局名试图实现,但我不知道它是什么!

理想我想

(一)能够实例仅仅基于支付的类型PaymentProcessor,而无需创建字典;

(b)能强类型PaymentProcessors只接受他们可以使用的子类。

没有人有解决这个问题的一种巧妙的方法?

回答

2

您可以用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); 
    // ... 
    } 
} 

如果你还是想分开不同的付款接受者,或者如果你的层次抖动,您可以使用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); 
    // ... 
    } 
} 
+0

这很完美!谢谢Jordao! – Gaz 2010-08-08 11:58:44

2
interface IPayment 
{ 
IPaymentTaker Taker {get;} 
} 

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

payment.Taker.Process(payment); 
+0

这是一个很不错的主意,在大多数情况下都能正常工作,但我通常会期望从IoC容器中加载PaymentTakers。 更常见的问题是,这会将Taker绑定到付款上,不允许不同的上下文对应不同的上下文。 – Gaz 2010-08-06 13:47:16

1

尽管James的方法是理想的,但使用IoC容器可能很困难。这是我的基于反射或动力学的方法。执行以下操作将允许你仍然使用一个IoC来设置PaymentTakerPayment之间的映射。

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); 
+1

对我来说也是这样。可能更适合使用动态;即动态paymentTaker = paymentTakers [paymentType]; paymentTaker.Process(paymentType)。 – Gaz 2010-08-06 13:58:13

+0

嗯,是的,即使你使用.NET 4.0,也不会这样做。好点子! – TheCloudlessSky 2010-08-06 13:59:17

+0

所以我尝试测试'动态paymentTaker = paymentTakers [paymentType]; paymentTaker.Process(付款)',它是抛出一个异常,所以相反,你需要写'paymentTaker.Process((动态)支付);' – TheCloudlessSky 2010-08-06 14:03:20

0

如果你能保证支付的名称和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%的控制权,这可能是一个问题。