信息隐藏与隐藏依赖关系

发布于 2024-08-01 15:12:53 字数 397 浏览 3 评论 0原文

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

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

每当过程使用外部资源(文件、数据库等)时,都会发生相同的情况。 在过程中对该资源的使用进行硬编码感觉有点不对,但我不确定替代方案是什么。

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

谢谢, 马特

What are some common best practices in procedure (or function, module, etc.) design for balancing the desire for information hiding and an appropriate level of abstraction in the procedure's interface with the problems inherent in introducing hidding dependencies?

To be more concrete, suppose I code a procedure called getEmployeePhoneNbr(employeeId). Internally, the procedure is implemented by querying a database table keyed off of employeeId. I want to hide those implementation details, but now the procedure depends upon an external file, which hinders its use if the environment changes.

The same situation would occur any time a procedure uses an external resource - file, database, whatever. It feels wrong somehow to hard-code the use of that resource within the procedure, but I'm not sure what the alternative is.

Please note that I'm not working in an object-oriented language; to the extent possible, I'd be most interested in responses that were broadly applicable across any type of language.

Thanks,
Matt

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

指尖上的星空 2024-08-08 15:12:53

这是一个非常难以解决的问题,无论您的实现语言是否是面向对象的(并且在任何情况下,通常都可以应用对象方法,无论编程语言是否支持它们作为语言构造,所以我用术语描述了我的解决方案对象的数量)

您希望能够平等地对待所有数据存储。 实际上,这几乎是不可能的,您必须选择一个范式并接受它的局限性。 例如,可以将抽象设计基于 RDBMS 范例(连接/查询/获取),并尝试使用同一接口封装对文件的访问。

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

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

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

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

This is a very difficult issue to resolve, whether your implementation language is object oriented or not (and in any case object methodologies can usually be applied regardless of whether to programming language supports them as a language construct, so I have described my solution in terms of objects)

What you would like to be able to do is treat all data storage equivilantly. In reality this is almost impossible and you must choose a paradigm and accept it's limts. For instance it is possible to base the design of your abstraction upon an RDBMS paradigm (connect/query/fetch) and attempt to encapsulate access to files withion the same interface.

An approach I have used with success is to avoid embedding the retrieval of data within (in your case) the Employee "object" as this creates a coupling that is to close between the abstraction of the Employee within the program and the storage and retrival of it's data.

Instead I create a seperate object, responsible for retrieving the data to construct the Employee object, and in turn construct the Employee object from that data. I can now construct an Employee from any data source provided I can translate the data into an appropriately generic structure. (I have the advantage of language support for associative arrays, which simplifies the process of passing tuples around considerably, you may have trouble if your development language makes it difficult or impossible to do this).

This also make the application easier to test, since I can construct the Employee "object" directly within my unit test without having to worry about creating the data source (or whether the data that was there last time is still there). In a complex design this setup and tear down can account for the majority of the test code. In addition, should the need arise to create 1000 Employee "objects" I can re-use my code without having to query my datasource (file, db, card index etc) 1000 times (in other words it neatly solves the famous ORM N+1 query problem).

So to summarise, seperate data retrival from business logic entirely as the hidden dependency you describe has some very nasty pitfalls. IMHO it is an anti-pattern to encapsulate retrieval of specific data within the construction of an "object" or within a function to retrieve a property from some stored data.

椵侞 2024-08-08 15:12:53

您遇到的这类问题通常可以通过使用依赖倒置原则(又名 DIP)来解决。 原始文章可以在此处找到。

这篇文章主要是面向对象的,但你也可以应用命令式语言(你可以用命令式语言进行面向对象,只是更难)。

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

在功能级别,您可以将其转换为提供高级功能低级数据/功能。

非 OO 语言中最好的方法是传递一个结构体或函数指针来定义高层函数使用的数据/函数。

The kind of problem you have is usually solved by using the dependancy inversion principle (aka DIP). The original article can be found here.

The article is mainly OO but you can apply in an imperative language too (you can do OO with imperative language it is just harder).

The principle is that it is better to give a client object a reference to an object that do some needed processing (database access for instance) than to code or aggregate this object into the client object.

At a function level you can translate it to give a high level function low level data / functions.

The best way in non OO language is to pass a struct or a function pointer that defines the data / functions used by the higher level function.

我是男神闪亮亮 2024-08-08 15:12:53

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

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;

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

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

You could provide some kind of context/environment object. Say:

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;

(Pseudo-Pascal). The advantage is, that you could use the Environment structure to store, say, extended error information and other global state, too, this way avoiding the PITA which is the Unixish errno or Window's GetLastError. Another advantage of this approach is, that all your APIs become re-entrant, and by using a dedicated environment per thread,
thread-safe as a consequence.

The drawback of this approach is, that you will have to pass an additional argument to all of your APIs.

狼性发作 2024-08-08 15:12:53

将资源依赖项放入查找函数中。 如果许多资源是相关的,我将创建一个具有简单功能的模块来检索它们。 我个人在可以避免的情况下会避免传递此类参考资料。 途中的代码与了解或使用它们无关。

而不是:

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

或者:

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

我会执行以下操作:

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

这样您就可以更改 getEmployeeDbName() 并且每个依赖函数和模块都会受益。

Put the resource dependency in a lookup function. If a number of resources are related I would create a module that has simple functions for retrieving them. I personally avoid handing around such references when I can avoid. The code on the way has no business knowing or using them.

Instead of:

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

Or:

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

I would do the following:

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

This way you can change getEmployeeDbName() and every dependent function and module will benefit.

骄兵必败 2024-08-08 15:12:53

您可能想在这里使用三层方法,第一层是您的客户端,使用 getEmployeePhoneNbr(employeeId)...第二层是您的数据访问层第三层是数据实现层,数据访问层将使用它来访问具体的信息源。

数据实现层。

该层包含:

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

数据访问层

包含:

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

。只需要负责为数据访问层提供正确的数据实现结构,因此如果它发生变化,您只需在一处进行更改。

You may want to use a three layer approach here, your first layer is you client, the one consuming getEmployeePhoneNbr(employeeId)... the second layer is your data access layer, and the third layer would be the data implementation layer which will be used by your data access layer to access the concrete source of information.

The data implementation layer.

This layer contains:

  1. A data structure that represent the location of a resource that can be accessed by the data layer.
  2. An API to create a new structure and it's correspondent functions to configure it.

The data access layer

Contains:

  1. A pointer to the data structure to be used as source of data.
  2. A public simple API with all the calls you have to access the data, as getEmployeePhoneNbr (employeeId), getEmployeeName (employeeId) .... All this calls will use internally the pointer to the data structure to access the specific data

Using this approach you will only will have to take care of providing with the right data implementation structure to your data access layer, so if it changes, you will only need to change it in one place.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文