【问题标题】:How can i unify classes that have same purpose but different input and output to one interface如何将具有相同目的但输入和输出不同的类统一到一个接口
【发布时间】:2019-06-03 17:52:40
【问题描述】:

我需要统一几个具有相同目的但使用相同方法但参数和结果不同的类。

你可能听起来很奇怪,所以让我解释一下。

我有多个类连接到多个 rest/web 服务以从中获取数据。对于这个例子,假设所有这些都是天气预报服务。每个人都做同样的事情。一些地区的回报预测,但他们都在按照自己的方式做。对于每个服务,我都有实现从中收集数据并映射到我的对象的类:

public class AForecast
{
    public AForecastResult GetForecast (AForecastRequest request)
    {
         // Grab Forecast
    }
}

public class BForecast
{
    public BForecastResult GetForecast (BForecastRequest request)
    {
         // Grab Forecast
    }
}

我正在调用这些类,获取预测数据,然后将其映射到我自己使用的对象中,这很好。我的问题是,现在我有 13 个预测服务。他们中的许多人正在使用类似的方法将预测结果映射到我自己的对象中。下一个问题也是,现在可能只有我知道如何将新预测添加到系统中。我想统一一下,以便能够在 Forecast 服务实现中添加一些接口,以及一些基本预测映射器。

我创建了接口,但由于我不知道预测结果和请求看起来如何,它看起来非常通用:

public interface IForecast<out TResult, in TRequest>
{
    TResult GetForecast(TReqiest request)
}

对于每个预测,我创建了单独的接口来实现IForecast

public interface IAForecast : IForecast<AForecastResult, AForecastRequest>
{
}

我的AForecast 实现开始看起来像这样:

public class AForecast : IAForecast 
{
    public AForecastResult GetForecast (AForecastRequest request)
    {
         // Grab Forecast
    }
}

多亏了我拥有自己的接口和通用基本接口的预测服务。

问题是当我想在基类中使用它时,它将能够调用每个预测服务和地图对象:

public abstract ForecastBase
{
    private readonly ?ForecastService _service;
    protected ForecastBase(?ForecastService service)
    {
        _service = service;
    }

    public MapedObject GetForecast(DateTime date, string zip)
    {
        var request = GetRequest(date,zip);
        var forecastServiceResponse = _service.GetForecast(request); 
        return Map(forecastServiceResponse);
    } 

    protected abstract MapedObject Map(?Response response);
    protected abstract ?Request GetRequest(DateTime date, string zip);
}

哦,这很长。现在最后一个问题如何实现ForeCast 基类?根据我的架构,我怎么知道?ForecastService、?Request 和?Response 的类型。我希望我可以制作这样的映射器:

public class AMap : ForecastBase 

并且知道在这种情况下?ForecastService 将是IAForecast,?Request 将是AForecastRequest 和?Response 将是AForecastResponse?

如果您需要更多解释,请随时询问。

【问题讨论】:

  • 您能发布您的ForecastResult 和ForecastRequest 类/接口吗?
  • 我不能,这取决于预测服务。可以是包含XML的字符串,也可以是包含JSON的字符串,也可以是wsdl文件的类形式。这取决于服务。他们中很少有人返回XElement
  • 我不能,因为我不知道,不是因为我不想说。
  • 我的意思是,你能显示请求/结果的接口和类定义吗?如果您认为此处太大,也可以将其复制到 pastebin。
  • 在这种情况下,AForecastRequest 是一个仅包含区域(邮政编码)的字符串,而对于AForecastResult,我得到了具有温度和湿度的字符串列表。对于BForecastRequest,我需要创建他们的.wsdl 对象并用zone、date 和key 填充以进行身份​​验证,对于BForecastResult,我得到了.wsdl 对象,其中包含temperature 的预测列表、forecast、opionion 和 tip。它们彼此不相关。而那些对象没有自己的接口,我也没有把它包裹在自己的接口中

标签: c# generics abstract-class


【解决方案1】:

我认为您需要的是关键字where(不仅如此,它还将在这里发挥非常重要的作用)。

我为请求和响应定义了这样的接口:

public interface IForecastRequest
{

}

public interface IForecastResult
{

}

然后我们可以继续为 Forecast 本身定义接口:

public interface IForecast<out TForecastResult, in TForecastRequest> 
    where TForecastResult : IForecastResult where TForecastRequest : IForecastRequest
{
    TForecastResult GetForecast(TForecastRequest request);
}

这里发生的情况如下。除了您的解决方案之外,我们还限制泛型类型来实现我们的请求和响应接口。然后我们可以添加通用方法来获取 ForecastResult。顺便说一句,GetForecast 似乎也不是一个好名字。更好的是GetResult 或GetForecastResult,否则你会期望不同的返回类型。

现在我们可以实现 BaseForecast。我希望我能正确理解这应该做什么。因为您将其称为 ForecastBase,所以我不确定这是否应该与 IForecast 做同样的事情。也许一个更好的名字在这里是个好主意。我是这样实现的:

public abstract class BaseForecast<TForecastResult, TForecastRequest> 
    where TForecastResult : IForecastResult where TForecastRequest : IForecastRequest
{
    private readonly IForecast<TForecastResult, TForecastRequest> _service;
    protected BaseForecast(IForecast<TForecastResult, TForecastRequest> service)
    {
        _service = service;
    }

    public MappedObject GetForecast(DateTime date, string zip)
    {
        TForecastRequest request = GetRequest(date, zip);
        TForecastResult forecastServiceResponse = _service.GetForecast(request);
        return Map(forecastServiceResponse);
    }

    protected abstract MappedObject Map(TForecastResult response);
    protected abstract TForecastRequest GetRequest(DateTime date, string zip);
}

这里我们还必须添加wheres,因为我们必须使用相同的约束调用 IForecast 接口的通用方法。您可以看到,在构造函数中,我们提供了一个 IForecast 实现的实例,这将是我们的服务。该服务使用我们在这个基类的定义中也有的两种通用类型。 GetForecast 方法现在可以使用 IForecast 的泛型方法和自己的泛型方法 (GetRequest) 来获取 MappedObject(我不知道它是什么)。类型全部对齐,您可以方便地对 IForecastRequest 和 IForecastResult 进行智能感知和编译时类型检查。

如果我正确理解 ForecastBase 的用途,请告诉我。
我希望这有帮助。我很乐意尝试回答您可能对此提出的任何问题。

编辑:

我希望我可以制作这样的映射器..

您在此处显示的方式不使用任何泛型类型参数。据我了解您的意图,如果不指定要使用的结果和请求,您将无法执行此操作。
我为此创建实现的方式如下:

// implement request
public class SomeForecastRequest : IForecastRequest
{

}

// implement result
public class SomeForecastResult : IForecastResult
{

}

// implement forecast itself
public class SomeForecast : IForecast<SomeForecastResult, SomeForecastRequest>
{
    public SomeForecastResult GetForecast(SomeForecastRequest request)
    {
        // return the result you got from wherever
    }
}

public class SomeMapper : BaseForecast<SomeForecastResult, SomeForecastRequest>
{
    public SomeMapper(IForecast<SomeForecastResult, SomeForecastRequest> service) : base(service)
    {

    }

    protected override SomeForecastRequest GetRequest(DateTime date, string zip)
    {
        // return a request from wherever
    }

    protected override MappedObject Map(SomeForecastResult response)
    {
        // map the response and return it
    }
}

编辑 2: 我刚刚阅读了您的评论,内容如下:

.. 它们彼此无关。而且那些对象没有自己的接口,我也没有把它包裹在自己的接口中

如果您想保持这种方式,则无法为您的映射器创建基类。映射器需要知道AForecast 有一个名为GetForecast 的方法。如果没有,它会如何调用它。如果你尝试这个,你将进入编译器错误领域。你需要告诉编译器“这个类可以处理任何类如果它有GetForecast-方法”否则它将拒绝尝试调用GetForecast。告诉编译器的方法是说“伙计,我得到了这个很棒的接口,它有GetForecast-方法。我只允许调用者使用一个实现这个接口的类,这样你就可以确定GetForecast-方法存在。好吗?”。这就是您使用where 关键字的原因,我感谢您的问题,因为这是一个关于如何使用它的非常好的(但不太容易)示例。

编辑 3:
顺便说一句,没有什么能阻止你使用类而不是IForecastRequest 和IForecastResult 的接口(当然你会更改名称,但其他一切都可以保持不变)。我不知道您的请求和响应对象应该做什么/存储,所以我不知道在接口上使用类是否适合您。
我只是想扔在那里,这也是可能的。

我很想听听您的反馈:)

【讨论】:

  • 我认为这种方法将是最佳选择。即使AForecastRequest 很简单string 我也会用AForecastRequestWrapper 类来包装它,这将实现IForecastRequest 并在其中添加简单的string 字段。多亏了这一点,我才能在我的结构中使用你的结构。
  • 很高兴我能帮上忙 :)
猜你喜欢
  • 2016-06-23
  • 1970-01-01
  • 2019-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-20
  • 2013-03-28
  • 2023-01-12
相关资源
最近更新 更多