12

我正在阅读 Steve McConell 的 Code Complete,我正在考虑他在关于松散耦合的部分中给出的示例。它是关于一个计算员工假期数的方法的接口,它是根据员工的入职日期和她的销售额计算的。作者建议将入职日期和销售额作为方法的参数,而不是员工的实例:

int holidays(Date entryDate, Number sales)

代替

int holidays(Employee emp)

争论的焦点是这将方法的客户端解耦,因为它不需要知道有关 Employee 类的任何信息。

我想到了两件事:

  1. 提供计算所需的所有参数会破坏封装。它显示了该方法如何计算结果的内部结构。

  2. 更难改变,例如当有人决定员工的年龄也应该包括在计算中时。一个人将不得不更改签名。

你怎么看?

4

10 回答 10

10

我看到你的论点 2 的问题是

  1. 您假设每个需要的值都来自一个 Employee 实例。这并不总是正确的。例如,假设您必须考虑公司的财务状况来计算给任何员工多少“奖金假期”。您是否会将财务状况信息添加到员工类以避免更改签名?

  2. 更改签名不一定“更难”,尤其是在这些日子里,单击按钮即可突出显示每个呼叫位置的工具。

你的论点 1 的主要问题是它并没有像其他人所说的那样破坏封装。你展示的是什么,而不是如何,这就是封装的意义所在。

于 2008-12-12T08:32:18.170 回答
8

最终松耦合获胜。从高耦合到低耦合,有各种类型的耦合:

  1. 内容耦合(高):一个模块修改或依赖另一个模块的内部工作
  2. 公共耦合:两个模块共享相同的全局数据(例如一个全局变量)。更改共享资源意味着更改所有使用它的模块。
  3. 外部耦合:两个模块共享外部强加的数据格式、通信协议或设备接口。
  4. 控制耦合:一个模块控制另一个模块的逻辑,通过向它传递关于做什么的信息(例如传递一个做什么标志)。
  5. 标记耦合(数据结构耦合):当模块共享一个复合数据结构并且只使用它的一部分,可能是不同的部分(例如,将整条记录传递给只需要它的一个字段的函数)。
  6. 数据耦合:当模块通过例如参数共享数据时。每个数据都是一个基本部分,并且这些是唯一共享的数据(例如,将整数传递给计算平方根的函数)。
  7. 消息耦合(低):模块不相互依赖,而是使用公共接口来交换无参数消息(或事件,请参阅消息传递)。
  8. 无耦合:模块之间根本不通信。

传入的Employee是Stamp耦合,比Data耦合更耦合。如果您真的考虑修改的难易程度,那么低耦合会更好,因为您不必担心不必要的副作用。假设你想改变Employee类的结构。您现在必须检查假期函数的实际实现,以确保它不会破坏数学运算。

最好的最解耦的方法是定义一个 interface IHolidayable,但这对于这种情况来说有点过头了。

于 2008-12-12T09:22:32.860 回答
2

To me the "right" answer boils down to whether you're defining a high-level or low-level API when creating this function. A low-level API puts flexibility above convenience for any particular case and argues for int holidays(Date entryDate, Number sales) . A high-level API is designed to do one thing well with as much convenience as possible for the client. This argues for int holidays(Employee emp) because it requires less boilerplate on the caller's part (extracting the date and sales from the Employee class) and is less verbose.

于 2010-01-15T21:46:32.123 回答
1

1) 提供参数不会破坏封装。它只是表明这些参数用于计算假期。“如何”仍然隐藏在方法中。

2) Holiday 方法不应该是 Employee 类的一部分。

于 2008-12-12T08:00:56.410 回答
1
  1. 这并没有破坏封装。打破封装将揭示它用于计算假期的内部方法。提供起始参数是定义 API 的内容。

  2. 可以改进 API 以允许此类更改 - 但是,大多数方法建议您应该设计以满足当前的需求,而不是针对不可预见的更改进行过度设计(为更改而设计,但不要试图预测更改)。最好现在实现你需要的东西,如果有必要,以后再重构。

在我看来,最好通过尽可能多的解耦和封装来规划变更,以便尽可能轻松地进行重构。试图提前预测每一个可能的场景最终会导致一个臃肿的过度设计的系统。

于 2008-12-12T08:02:47.527 回答
1
int holidays(Employee emp)

在这种情况下,只有员工可以使用相关功能......

于 2008-12-12T08:04:59.973 回答
0

我想说松耦合的主要好处之一是易于更改。松散耦合的类型可以相互独立地改变,所以我不明白你在问题中的“vs”。

此外,我不会说您通过为方法提供参数来破坏封装。您可以Sum(int a, int b)随心所欲地实现 - 但您必须告诉我(作为用户)您期望两个数字。

于 2008-12-12T08:04:05.217 回答
0

与其他一些帖子不同,我同意你的观点,这打破了封装。不是类的封装,而是计算假期的概念。如果你想在未来改变这个计算的工作方式,并且仍然避免紧密耦合,我建议让 Employee 成为一个接口。如果这仍然与 Employee 紧密耦合,因为您认为可能需要为其他事情计算假期,那么您可以拥有一个 GetsHolidays 接口,Employee 可以从该接口继承。

当然,您选择的解决方案的参与程度应该取决于您认为耦合和封装问题的严重程度。我认为这两种原始解决方案在许多情况下都是可以接受的,尽可能保持简单。

于 2008-12-12T08:58:45.560 回答
0

最好的最解耦的方式是定义一个接口 IHolidayable

This sort of blanket statement really annoys me. Just because you use an interface does NOT mean you automatically decouple anything. If your employee class implements an interface to calculate holidays, any code that calls the method, still calls the same method. At worst, the calling code will do that by direct access to an employee object, not an IHolidayable interface reference, in which case you have only made the situation worse (because there is now also a more subtle coupling between the interface and any classes which implement it).

Interfaces can indeed help with decoupling, but they do not automatically do so, and even when they do, they are not necessarily a better solution than an abstract (or some other ancestor) class.

于 2008-12-12T10:08:47.367 回答
0
int holidays (IHolidayInfo obj)

Is might be a way. In this case, any object that implements IHolidayInfo would able to use "holidays" function. Just an alternative.

于 2010-06-03T00:18:58.847 回答