您会考虑更改界面或使用适配器吗?

时间:2017-02-02 00:25:50

标签: c# oop interface

我有一个返回类型的Enumerable的接口。

public interface IMapper
{
    IEnumerable<IContract> Get(params object[] objects);
}

它基本上接受一个或多个类型也未知的参数,并返回实现IContract的任何类型的Enumerable。随意为此提出替代方案。

现在看起来很简单并且有效。但是,这是一个总是返回一个对象(类型为IContract)的场景。我无法改变它只返回一个对象的事实。

var escalationMapper = _factory.GetEscalationMapper();

//we only get one object with a list of triggers but the interface returns a list. Change the interface? 
var escalations = escalationMapper.Get(trackingGroupCode);

_factory.Release(escalationMapper);

var contracts = escalations as IList<IContract> ?? escalations.ToList();
response = Request.CreateResponse(!contracts.Any()
            ? HttpStatusCode.NotFound : HttpStatusCode.OK, contract);

我们感兴趣的代码行是注释之后的代码,因为我不愿意只返回一个对象并将其称为列表。 Get()方法还有其他用法可以返回正确的列表。这恰好最多返回1个对象。

将单个对象作为列表返回是否合理,还是有更好的方法来实现它?

3 个答案:

答案 0 :(得分:1)

这是一个权衡。目标是适应客户期望的类型。权衡在1)具有附加类和2)强制现有类型改变之间。

1)的pro是现有类型保持不变(假设它现在最有意义的类型,返回单个IContract); 1)的骗局是它需要更多的代码和更多的依赖。

2)的代表是代码大小和依赖数量仍然较低; 2)的骗局是你通过改变类型来破坏项目的类型设计,以返回一个总是包含单个元素的列表,纯粹是出于客户的期望。

在强类型语言中,类型系统旨在帮助程序员。更大的详细程度购买类型系统的好处,并且颠覆该系统对减少冗长度没有多大帮助,但却失去了 类型系统的好处。因此,我将使用适配器以强类型语言解决此问题,而不是通过更改接口。

换句话说,用最少惊喜(https://en.wikipedia.org/wiki/Principle_of_least_astonishment)原则解决问题。如果项目中的另一个程序员(或者你自己,在几个月后)通常会期望EscalationMapper的一个实例返回一个唯一的IContract,那么看到它返回一个IEnumerable会感到惊讶,然后使用一个适配器({{ 3}})。相反,如果看到它返回单个项目,他们会感到惊讶,然后更改界面。

答案 1 :(得分:1)

我同意@ Abion47和@chrylis评论 - 0或1项是一个完全有效的案例集合案例。

但是 - 如果您不同意并且仍然想要一种方法来传达没有IEnumerable语义的单个元素,则可以使用一个方法来重载Get,该方法接受单个对象并返回单个IContract。这将优先于params []重载,因为object将比params[] object更好地匹配单个输入。

这看起来像是:

public interface IMapper
{
    IContract Get(object input);
    IEnumerable<IContract> Get(params object[] input);
}

假设找不到null值,您的调用代码将变为:

var escalationMapper = _factory.GetEscalationMapper();

//we only get one object with a list of triggers but the interface returns a list. Change the interface? 
var escalation = escalationMapper.Get(trackingGroupCode);

_factory.Release(escalationMapper);

response = Request.CreateResponse(contract == null ? HttpStatusCode.NotFound : HttpStatusCode.OK, contract);

我必须提醒一下,建议这样做很奇怪,我认为它会在界面中引入不诚实行为。我也认为很有可能破坏任何现有的代码,这些代码也只用一个输入来调用接口。

至于替换object,您可以随时添加一些标记界面,例如IContractInput,并将其传递到Get而不是object

public interface IContractInput { /* Intentionally empty */ }

然后,使用IContractInput标记任何输入类:

public class SomeInput : IContractInput { /* implementation.. */ }

通过这种方式,可以强类型地输入方法,以取代IContractInput而非通用object类型。

答案 2 :(得分:1)

就像我在评论中所说的那样,你的代码在设计上没有任何错误。即使您调用Get的特定位置保证在返回列表中最多返回一个项目,但这并不意味着它将在其他任何调用{{1}的地方得到保证。 }}

如果你真的想简化它以便它返回单个对象而不是一个长度的列表,不过,我也不建议改变界面以获得额外的方法。这将破坏实现IMapper.Get的所有类并迫使您实现该新方法,即使在新方法不会添加任何有用的地方也是如此。如果其他人将您的代码用于自己的目的,这将是一个特别大的问题,因为它会迫使他们执行重写。

然而,要解决这个问题,你可以做的一件事就是将新方法声明为扩展方法:

IMapper

然后你可以这样称呼它:

public static class IMapperExtensions
{
    public static IContract GetSingle(this IMapper mapper, params object[] objects)
    {
        return mapper.Get(objects).FirstOrDefault();
    }
}

这将为您提供所需的功能,而不会破坏您界面的任何现有实现。