信息隐藏与隐藏依赖关系

时间:2009-08-10 19:38:53

标签: abstraction information-hiding external-dependencies

过程(或函数,模块等)设计中有哪些常见的最佳实践,用于平衡信息隐藏的需求和过程接口中的适当抽象级别以及引入隐藏依赖项所固有的问题?

更具体地说,假设我编写了一个名为getEmployeePhoneNbr(employeeId)的过程。在内部,通过查询从employeeId键入的数据库表来实现该过程。我想隐藏这些实现细节,但现在该过程依赖于外部文件,如果环境发生变化,则会阻碍其使用。

只要过程使用外部资源(文件,数据库等),就会发生同样的情况。以某种方式在程序中硬编码该资源的使用感觉是错误的,但我不确定替代方案是什么。

请注意,我不是使用面向对象的语言; 尽可能地,我最感兴趣的是那些广泛适用于任何类型语言的响应。

谢谢, 马特

5 个答案:

答案 0 :(得分:3)

您遇到的问题通常通过使用依赖性反转原则(又名DIP)来解决。可以找到原始文章here

这篇文章主要是面向对象,但你也可以用一种命令式语言(你可以用命令式语言做OO,这样更难)。

原则是,最好为客户端对象提供对执行某些所需处理(例如数据库访问)的对象的引用,而不是将此对象编码或聚合到客户端对象中。

在功能级别,您可以将其翻译为高级功能低级数据/功能。

非OO语言的最佳方法是传递结构或函数指针,该指针定义更高级函数使用的数据/函数。

答案 1 :(得分:1)

这是一个非常难以解决的问题,无论您的实现语言是否面向对象(无论如何,无论编程语言是否支持它们作为语言结构,通常都可以应用对象方法,所以我描述了我的对象方面的解决方案)

您希望能够做的是对应所有数据存储。实际上这几乎是不可能的,你必须选择一个范例并接受它的限制。例如,可以将抽象设计基于RDBMS范例(connect / query / fetch),并尝试封装对具有相同接口的文件的访问。

我成功使用的一种方法是避免在(在您的情况下)Employee“对象”中嵌入数据检索,因为这会在程序中的Employee抽象和存储之间创建一个耦合。并回顾它的数据。

相反,我创建了一个单独的对象,负责检索数据以构造Employee对象,然后从该数据构造Employee对象。我现在可以从任何数据源构建一个Employee,前提是我可以将数据转换为适当的通用结构。 (我的优势是对关联数组的语言支持,这简化了传递元组的过程,如果你的开发语言难以或不可能,你可能会遇到麻烦。)

这也使应用程序更容易测试,因为我可以直接在单元测试中构建Employee“对象”,而不必担心创建数据源(或者上次存在的数据是否仍然存在)。在复杂的设计中,这种设置和拆卸可以解释大部分测试代码。此外,如果需要创建1000个Employee“对象”,我可以重新使用我的代码,而不必查询我的数据源(文件,数据库,卡索引等)1000次(换句话说,它巧妙地解决了着名的ORM N + 1查询问题)。

总而言之,完全由业务逻辑分离数据,因为您描述的隐藏依赖具有一些非常令人讨厌的陷阱。恕我直言,它是一种反模式,用于封装在“对象”构造中或在函数内检索特定数据以从某些存储数据中检索属性。

答案 2 :(得分:0)

您可以提供某种上下文/环境对象。说:

type Environment = record
      DatabaseHandle: ...;
      ...
   end;

   Employee = record
      ID: integer;
      Name: string;
      ...
   end;


function OpenEnvironment (var Env: Environment): boolean;
begin
   ...
end;

procedure CloseEnvironment (var Env: Environment);
begin
   ...
end;

function GetEmployeeById (var Env: Environment; ID: integer; var Employee: Employee): boolean;
begin
   ... load employee using the data source contained in environment ...
end;

(伪帕斯卡)。优点是,您可以使用Environment结构来存储扩展错误信息和其他全局状态,这样就可以避免使用PITA,它是Unixish errno 或Window的 GetLastError < / em>的。这种方法的另一个优点是,所有API都可以重入,并且每个线程使用一个专用环境, 因此线程安全。

这种方法的缺点是,您必须向所有API传递一个额外的参数。

答案 3 :(得分:0)

您可能希望在此处使用三层方法,您的第一层是客户端,一个使用getEmployeePhoneNbr(employeeId)... 第二层是您的数据访问层< / strong>,第三层将是数据实现层,您的数据访问层将使用它来访问具体的信息源。

数据实施层。

此图层包含:

  1. 表示数据层可以访问的资源位置的数据结构。
  2. 用于创建新结构的API,以及配置它的相应功能。
  3. 数据访问层

    包含:

    1. 指向要用作数据源的数据结构的指针。
    2. 一个公共简单API,包含您访问数据的所有调用,如getEmployeePhoneNbr(employeeId),getEmployeeName(employeeId)....所有这些调用将在内部使用指向数据结构的指针来访问特定数据< / LI>

      使用这种方法,您只需要为数据访问层提供正确的数据实现结构,因此如果它发生变化,您只需要在一个地方进行更改。

答案 4 :(得分:0)

将资源依赖项放在查找函数中。如果有许多资源相关,我会创建一个具有简单函数的模块来检索它们。我个人避免在我可以避免的时候处理这​​些引用。路上的代码没有业务知道或使用它们。

而不是:

getEmployeePhoneNbr(employeeId)
    dbName = "employeedb"
    ... SQL, logic, etc.

或者:

getEmployeePhoneNbr(employeeId, dbName)
    ... SQL, logic, etc.

我会做以下事情:

getEmployeePhoneNbr(employeeId)
    dbName = getEmployeeDbName()
    ... SQL, logic, etc.

这样你可以改变getEmployeeDbName(),每个依赖的函数和模块都会受益。