铸造和接口困境

时间:2014-01-31 07:12:07

标签: c# oop

所以这些天我更多地使用接口。但是这次我碰到了一堵砖墙。

仅仅是为了上下文,让我告诉你这里的RESTful WCF合约,我打算向您展示我如何使用IPaymentRequest:

[ServiceContract]
public interface IPaymentService
{
    [WebInvoke(Method = "POST", UriTemplate = "/PreAuthorization")]
    PreAuthorizeResponse SendTransaction(PreAuthorizeRequest request);

    [WebInvoke(Method = "POST", UriTemplate = "/Capture")]
    CaptureResponse SendTransaction(CaptureRequest captureRequest);

    ... and so on
}

例如,服务合同的实施有一些方法如下:

public PreAuthorizeResponse SendTransaction(PreAuthorizeRequest request)
{

   .....
   Processor.SetSettings(request);

 }

(关于清洁代码主体的注意/免责声明。我有更好的名字,比如名字SetSettings(),但是为了隐私我已经为这个Stack帖子命名了更简单的东西,比如“SetSettings”和“Process”。我的类名中有什么样的处理器,所以只有FYI)。

第二次,让我告诉您,我有一个处理器类,它基本上类似于实用程序类来执行某些操作,例如将请求字段发送到外部REST API。在这个类中我设置的另一个方法的一个示例是一个SetSettings方法,我将基于请求的类型设置一些东西。大多数情况下,我将从其Transaction获取我需要的东西属性。

public class Processor
{
    private void SetSettings(IPaymentRequest request)
    {
         var someValue = request.Transaction.SomeProperty1;
         ...
    }
}

现在这是IPaymentRequest的样子:

public interface IPaymentRequest
{
    string Token { get; set; }
    int ClientID { get; set; }
}

现在,这里有几个我的域模型示例(我的服务合同期望从客户端请求发送的模型)实现IPaymentRequest:

public class PreAuthorizeRequest : IPaymentRequest
{
    public string Token { get; set; }
    public int ClientID { get; set; }
    public int Amount { get; set; }
    public PreAuthTransaction Transaction { get; set; }
}

public class CaptureRequest : IPaymentRequest
{
    public string Token { get; set; }
    public int ClientID { get; set; }
    public int BankID { get; set; }
    public CaptureTransaction Transaction { get; set; }
}

我在我的WCF服务中使用IPaymentRequest(它是预期将发送到我的支付服务的方法合同中的类型),并使用我服务中其他地方的这些接口来重复使用这些请求可以流经的方法。作为SendRequest(IPaymentRequest请求),等等。

这是我遇到的困境/问题

在我想为任何类型的请求重用逻辑的方法中,我最终必须检查它在我的处理器类中的方法有时的类型。所以我不得不创建一堆凌乱的if语句来确定和转换传入的ITransaction,以便在我的实用程序mehtods中开始使用它。

所以让我们继续下去,这样我就可以解释一下我的第一个方法SetSettings()

请注意,我需要请求中的事务对象的值,并且能够使用该TYPE请求中的属性。

现在让我们看一下CaptureTransaction对象,例如CaptureRequest

public class CaptureTransaction : ITransaction
{
    public string Reference { get; set; }
    public decimal Amount { get; set; }
    public string CurrencyCode { get; set; }
    public CreditCard CustomerCreditCard { get; set; }
}

正如您所看到的,对于每个请求类型,我都有一个相关的具体事务类型,它实现了ITransaction并保存了事务需要发送到外部API的信息。

注意:所有请求都会有一个事务(ITransaction),所以我认为将ITransaction放在我的IPaymentRequest中是个好主意,所以这样:

public interface IPaymentRequest
{
    string Token { get; set; }
    int ClientID { get; set; }
    ITransaction Transaction {get; set; }
}

这是ITransaction。我们服务中的每个请求都需要现在和将来的货币,因此该字段是使用接口的良好候选/理由:

public interface ITransaction
{
    string CurrencyCode { get; set; }
}

因此,将此添加到我的IPaymentRequest现在要求我将自定义类型中的属性名称更改为“事务”,例如:

public class CaptureRequest : IPaymentRequest
{
    public string Token { get; set; }
    public int ClientID { get; set; }
    public int BankID { get; set; }
    public ITransaction Transaction { get; set; }
}

我觉得还不错。

但是现在如果我尝试在我的实用程序方法中使用Transactions,因为它是一个Interface变量,它不知道它是什么类型的Transaction。所以我最终不得不在我使用它之前进行投射:

private void SetSettings(IPaymentRequest request)
{
     ITransaction transaction;

     if (request is CaptureRequest)
         transaction = request.Transaction as CaptureTransaction;
     if (request is PreAuthorizeRequest)
         transaction = request.Transaction as PreAuthorizeTransaction;

... etc.

     var someValue = request.Transaction.Some1;
     ...carry on and use SomeProperty1elsewhere in this method for whatever reason
}

IMO只是感觉像巨大的代码味道。所以很明显我没有做正确的事情,或者我还不知道我应该知道的接口...这使我能够在这里更好地使用它们,或者没有那么多的铸造。并且过多的铸造IMO是糟糕的,性能方面的。

在我想要创建的方法中使用泛型而不是接口参数以便在不同类型的Concrete Request类型(Capture,PreAuth,Void yada yada)中重用时,这可能是个好例子吗?

这里的重点是我希望能够在某些方法中指定接口参数来使它们干(不要重复自己)/可重用...然后使用通过多态性进入的具体类型并使用请求实例。

4 个答案:

答案 0 :(得分:0)

如果每个请求都有一个事务,那么这是正确的方法:

interface IPaymentRequest
{
    string Token { get; set; }
    int ClientID { get; set; }
    ITransaction Transaction { get; set; }
}

显然,要处理自定义请求,您需要一个自定义处理器:

class Processor
{
    protected virtual void OnSetSettings(IPaymentRequest request)
    {
    }

    private void SetSettings(IPaymentRequest request)
    {
        // do the common stuff
        // ...
        // set custom settings
        OnSetSettings(request);
    }
}

class PreAuthorizeRequestProcessor : Processor
{
    protected override void OnSetSettings(IPaymentRequest request)
    {
        base.OnSetSettings(request);

        // set custom settings here
        var customRequest = (PreAuthorizeRequest)request;
    }
}

如您所见,这需要一点点型铸造。 Yo可以避免使用泛型,但这会带来类型声明的复杂性:

interface IPaymentRequest<TTransaction>
    where TTransaction : ITransaction
{
    string Token { get; set; }
    int ClientID { get; set; }
    TTransaction Transaction { get; set; }
}

class Processor<TRequest, TTransaction>
    where TRequest : IPaymentRequest<TTransaction>
    where TTransaction : ITransaction
{
    protected virtual void OnSetSettings(TRequest request)
    {
    }

    private void SetSettings(TRequest request)
    {
        // do the common stuff
        // ...
        // set custom settings
        OnSetSettings(request);
    }
}

class PreAuthorizeRequestProcessor : Processor<PreAuthorizeRequest, PreAuthTransaction>
{
    protected override void OnSetSettings(PreAuthorizeRequest request)
    {
        base.OnSetSettings(request);

        // set custom settings here
    }
}

答案 1 :(得分:0)

对我的评论的解释(在这种情况下如何使用访客模式):

interface IPaymentRequest
{
    void Process(IPaymentRequestProcessor processor);
}

class CaptureRequest  : IPaymentRequest
{
    public void Process(IPaymentRequestProcessor processor)
    {
        processor.Process(this);
    }
}

class PreAuthorizeRequest : IPaymentRequest
{
    public void Process(IPaymentRequestProcessor processor)
    {
        processor.Process(this);
    }
}


interface IPaymentRequestProcessor
{
    void Process(CaptureRequest request);
    void Process(PreAuthorizeRequest request);
}

其中:

private void SetSettings(IPaymentRequest request)
{
    IPaymentRequestProcessor processor = new PaymentRequestProcessor();
    request.Process(processor);
}

答案 2 :(得分:0)

访问者模式是一个显而易见的解决方案 - 它允许您绕过这样的事实:C#无法解析您在运行时使用的ITransaction子类型,以便通过使用技巧选择方法重载叫双重调度。访问者模式的结果是将特定于类型的处理代码从条件(可以错过案例)移动到类型定义,编译器可以强制执行完整性。然而,成本是通过虚拟方法反弹的代码,当你试图从头开始理解时,可能会有点复杂。

这是它的工作原理。

ITransaction获得方法Accept(ITransactionVisitor visitor)ITransactionVisitor是一个接口,它具有Visit方法,并为您要处理的每个ITransaction子类覆盖:

interface ITransactionVisitor {
    void Visit(PreAuthTransaction t);
    void Visit(VoidTransaction t);
    // etc.
}

当然,您需要实现这些方法。 Accept很简单,但确实需要为ITransaction的每个实现实施。为什么?不仅因为它是一个接口方法,而且因为在该方法体内,编译器将具体知道在编译时的事务类型,因此它可以在ITransactionVisitor中选择正确的重载。 / p>

public void Accept(ITransactionVisitor visitor) {
    visitor.Visit(this);
}

然后您需要做的就是实施适当的ITransactionVisitor。这种模式的一个优点是你可以用完全不同的行为实现任意多个,ITransaction不需要进一步的知识或修改(这就是访问者用接口或抽象类指定的原因) )。

public class TransactionProcessorVisitor:ITransactionVisitor {     public TransactionProcessorVisitor(/ *构造函数中的一些合适的上下文,因此它可以完成它的工作* /){...}     public void访问(PreAuthTransaction t){       // 做东西     }     public void访问(VoidTransaction t){       //做其他的事     }   }

所以,是的,访问者类必须知道所有类型的事务,但

  1. 这不是一个巨大的if声明
  2. 它不是ITransaction实施
  3. 的一部分
  4. 它是编译时检查的 - 如果你添加一个新的ITransaction类型并尝试通过处理器提供它,编译器将能够找出它没有Visit方法并抛出错误,而不是等到运行时,这是if版本中最好的。
  5. 这不一定是最好的答案,但它是 答案。

答案 3 :(得分:0)

首先,我告诉你,你的SetSettings方法是错误的。 var不起作用。从那里,你的整个推理线程是错误的。事实上,你正在使用某种“实用方法”,并且你有很糟糕的架构配方。

首先,我会将这些实用程序方法更改为某种具有某种界面的全功能类。我相信您可以创建IProcessor界面并拥有PreAuthorizeProcessorCaptureProcessor。从那里开始,您IProcessor GetProcessor()上有IPaymentRequest方法,然后强制每个请求都能拥有自己的处理器。或者您可以使用工厂通过IProcessor CreateProcessor(IPaymentRequest)为给定请求创建特定处理器。在这里,您可以硬编码预处理器或使用某种订阅机制。

此外,只要正确封装,使用类型检查和强制转换就没有错,就像在工厂内部一样。使用访问者模式与进行手动类型检查没有太大区别。你们仍然会从两者中获得同样的优点和缺点。