如果必须指定方法参数,接口如何完全独立于实现

时间:2011-07-24 23:56:40

标签: c# inversion-of-control

如果这是一个天真的问题,请原谅我。 interface应该隐藏实现的细节,但由于其方法采用什么参数,它是否必然会揭示该实现的各个方面?方法所需的参数不可避免地告诉我们该方法如何实现其功能。

这在控制反转领域似乎特别成问题,其中较高级别的组件似乎“拥有”界面。我理解它的方式,更高级别的组件基本上说:我需要一些低级组件来完成一些功能。这是我需要的功能(以接口的形式),然后另一个类可以免费提供该服务。

但是在发布该接口时,更高级别的组件如何知道实现者需要哪些参数?

如果实现者改变了接口的方法签名,那么我们现在在接口和实现之间有一个强大的耦合,我们试图避免这种情况。

是否有一些抽象可以解决这个问题?我们应该只是“过度参数化”接口中的方法签名,让实现者忽略他不需要的参数吗?或者我们是否应始终拥有 no 参数并让实现者处理从其他地方获取所需内容?

更新:让我试着举一个具体的例子。假设我有一个LightController,它实现了一个带有Detect()方法的接口或类似的东西。我是一个更高级别的“房间”组件,我想让它在有人进入房间时打开灯。我将哪些参数定义为Detect签名的一部分?如果我在Sound参数中传递它,以便灯光可以根据声级进行切换怎么办?但是,有一个班级想要使用运动检测来实现这一点,而不是声音。我真的不在乎这种或那种方式。那么我是否只需要传递整个房间状态并让实现者决定它需要什么?

UPDATE2:让我对代码更加明确:

interface ILightController {
   bool Detect(paramIThinkImplementorWillNeed);
}

class Room {
   ILightController lc = GetService(ILightController);
   if (lc.Detect(thatParam))
   ...
}

class MovementLightController : NOTILightController {  // nope
   public bool Detect(aDifferentParam) {
      // i could very well have fulfilled this functionality but the  
      // interface does not allow for the input i need. that's too bad.
   }
}

我认为一个解决方案就是“合同”是双向的。如果ILightController方法的调用者必须满足另一个对 it 施加约束的接口,然后将其自身传递给LightController接口的每个方法,光控制器可以从中获得所需的输入,该怎么办?所以:

interface ILightControllerInput {
   public PropertyNeededByImplementorsOfILightController { get; set; }
}

interface ILightController {
   public Detect(ILightControllerInput input);
   public SomeOtherMethod(ILightControllerInput input);
   // etc..
}

class Room : ILightControllerInput {
   public PropertyNeededByImplementorsOfILightController { get; set; }
   ILightController lc = GetService(ILightController);

   if (lc.Detect(this)) ...
}

class MovementLightController : ILightController {
   public bool Detect(ILightControllerInput input) {
      // that's right baby, it's a 2-way street here. if i'm going to conform
      // to your interface, you're going to conform to what i need!
   }
}

然后从这里开始,您可以实现两种方式的服务实现。更高级别的组件可以找到实现所需接口的低级组件,但是更高级别的组件必须指定它能够传递的输入,并且这将匹配可用的低级组件的子集(所以“需求实现”是双向的。)

请告诉我,我吸烟了吗?或者这样的事情是否已经存在(作为模式等)?

5 个答案:

答案 0 :(得分:2)

是的,它确实显示出一些提示。必要的是这里的关键。如果我正在为某些意图拍摄图像并从某处加载/保存它们的界面创建一个接口,至少我需要知道要加载什么图像以及要保存哪些数据(以及使用哪个键以便我可以加载它再来一次)。

如果不定义键的外观,以及要保存的数据的格式,您将如何做到这一点?没有这个定义的接口是没用的,因为没有它,两个实现者可以自由地提出两种不同的方法。一旦你这样做,我就不能将一个实现换成另一个实现,它不再是一个真正的接口。

“隐藏所有实施细节”是一个理想的选择。实际上,我们并不总能到达那里。 :)

答案 1 :(得分:1)

参数被视为界面的一部分。事实上,他们并没有告诉你有关实施的任何信息。

例如,如果您想将字符串转换为小写。当然你会传递字符串转换为参数,对。它是界面的一部分。

答案 2 :(得分:1)

是的,接口应该解耦并隐藏实现。但是,他们还定义了一个合同,即:输入和输出。要使它工作,必须知道预期输入是什么以及预期输出是什么。实现隐藏就是将输入转换为输出之间的内容。

答案 3 :(得分:1)

您应该仔细确定要抽象的接口。这就是在创建系统时做出正确的设计决策。 C#或.NET中的interface就此而言,由它公开的方法定义。这些方法具有签名,即它们的返回值和它们所采用的参数,方法的名称也很重要。

这定义了您的接口,并告诉使用此接口的客户端,如果接口定义良好,将会执行此操作。 如何它将继续执行,并不重要。

你不能再解耦了,否则你还要留下什么来定义?您的客户和代码之间的沟通必须有某种切入点。接口就是那个通信点。这告诉客户它将要做什么,这就是它所说的,而不是如何,只是什么

答案 4 :(得分:0)

如果实现需要某些特定于实现的方面,那些应该在初始化期间传入,并且可能应该在构造函数中。这就是IoC的全部意义,你不需要知道细节,只知道重要的部分。

例如,假设您IWageProcessor有一个方法decimal GetWage(Employee employee)。该方法中没有任何内容是特定于实现的。如果IWageProcessor的实现需要其他东西(比如数据库连接),IoC会处理这个问题。如果你想改变IWageProcessor的实现,以便它不使用数据库,而是使用WCF,你可以这样做,界面不会改变。

这并不意味着界面永远不会改变。但是,如果它发生变化,那只有在方法的含义发生变化时才会发生变化,而不仅仅是实施。例如,如果确定获得当前工资是不够的并且我们需要根据月份获得工资,则该方法可以更改为decimal GetWage(Employee employee, Month month)。这样做当然会改变实现方式,但事实并非相反。如果实现发生了变化,但没有意义,那么界面将保持不变。