Скрытие информации против скрытых зависимостей - PullRequest
5 голосов
/ 10 августа 2009

Каковы некоторые распространенные передовые практики в дизайне процедуры (или функции, модуля и т. Д.) Для уравновешивания желания скрыть информацию и соответствующего уровня абстракции в интерфейсе процедуры с проблемами, присущими введению скрывающих зависимостей?

Чтобы быть более конкретным, предположим, что я кодирую процедуру getEmployeePhoneNbr (employeeId). Внутренне процедура реализуется путем запроса таблицы базы данных, отключенной от employeeId. Я хочу скрыть эти детали реализации, но теперь процедура зависит от внешнего файла, который препятствует его использованию в случае изменения среды.

Та же самая ситуация будет происходить всякий раз, когда процедура использует внешний ресурс - файл, базу данных, что угодно. Как-то нехорошо кодировать использование этого ресурса в процедуре, но я не уверен, какова альтернатива.

Обратите внимание, что я не работаю с объектно-ориентированным языком; , насколько это возможно, меня больше всего интересуют ответы, которые широко применимы для любого типа языка.

Спасибо, Matt

Ответы [ 5 ]

3 голосов
/ 11 августа 2009

Тип проблемы, с которой вы сталкиваетесь, обычно решается с использованием принципа инверсии зависимости (он же DIP). Оригинальную статью можно найти здесь .

Статья в основном ОО, но вы также можете подать заявку на императивном языке (вы можете сделать ОО с императивным языком, это просто сложнее).

Принцип состоит в том, что лучше дать клиентскому объекту ссылку на объект, который выполняет некоторую необходимую обработку (например, доступ к базе данных), чем кодировать или объединять этот объект в клиентский объект.

На функциональном уровне вы можете перевести его, чтобы дать функции высокого уровня данные / функции низкого уровня.

Лучший способ не на ОО-языке - это передать структуру или указатель на функцию, которые определяют данные / функции, используемые функцией более высокого уровня.

1 голос
/ 11 августа 2009

Это очень сложная проблема, чтобы решить, является ли ваш язык реализации объектно-ориентированным или нет (и в любом случае методологии объекта обычно могут применяться независимо от того, поддерживает ли язык программирования их как языковую конструкцию, поэтому я описал свой решение по объектам)

То, что вы хотели бы сделать, - это одинаково относиться ко всему хранилищу данных. На самом деле это практически невозможно, и вы должны выбрать парадигму и принять ее пределы. Например, можно создать дизайн вашей абстракции на основе парадигмы СУБД (connect / query / fetch) и попытаться инкапсулировать доступ к файлам с помощью того же интерфейса.

Подход, который я использовал с успехом, состоит в том, чтобы избежать встраивания извлечения данных в (в вашем случае) «объект» Сотрудника, поскольку это создает связь, которая должна закрывать абстракцию Сотрудника в программе и хранилище. и получение его данных.

Вместо этого я создаю отдельный объект, отвечающий за извлечение данных для создания объекта Employee, и, в свою очередь, создаю объект Employee из этих данных. Теперь я могу построить Employee из любого источника данных, при условии, что я могу перевести данные в соответствующую общую структуру. (У меня есть преимущество языковой поддержки ассоциативных массивов, которая значительно упрощает процесс передачи кортежей, у вас могут возникнуть проблемы, если ваш язык разработки затруднит или сделает невозможным это).

Это также облегчает тестирование приложения, поскольку я могу создать «объект» Employee непосредственно в своем модульном тесте, не беспокоясь о создании источника данных (или о том, все ли еще были данные, которые были там в прошлый раз). В сложной конструкции эта настройка и разбор могут составлять большую часть тестового кода. Кроме того, в случае необходимости создания 1000 «объектов» сотрудников, я могу повторно использовать свой код, не запрашивая источник данных (файл, БД, картотека и т. Д.) 1000 раз (другими словами, это аккуратно решает знаменитый ORM N + 1 проблема запроса).

Итак, подведем итог: отделите извлечение данных от бизнес-логики полностью, так как описанная вами скрытая зависимость имеет некоторые очень неприятные подводные камни. ИМХО, это анти-шаблон для инкапсуляции извлечения определенных данных в конструкции "объекта" или в функции для извлечения свойства из некоторых сохраненных данных.

0 голосов
/ 11 августа 2009

Поместите зависимость ресурса в функцию поиска. Если количество ресурсов связано, я бы создал модуль, который имеет простые функции для их извлечения. Я лично избегаю таких ссылок, когда могу этого избежать. Код на пути не имеет никакого дела, зная или используя их.

Вместо:

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

Или:

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

Я бы сделал следующее:

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

Таким образом вы можете изменить getEmployeeDbName (), и каждая зависимая функция и модуль получат выгоду.

0 голосов
/ 11 августа 2009

Здесь вы можете использовать трехуровневый подход, ваш первый уровень - это ваш клиент , один из которых использует getEmployeePhoneNbr (employeeId) ... второй уровень - это ваш уровень доступа к данным и третий уровень будет уровнем реализации данных , который будет использоваться вашим уровнем доступа к данным для доступа к конкретному источнику информации.

Уровень реализации данных.

Этот слой содержит:

  1. Структура данных, представляющая местоположение ресурса, к которому может обращаться уровень данных.
  2. API для создания новой структуры и соответствующие ей функции для ее настройки.

Уровень доступа к данным

Содержит:

  1. Указатель на структуру данных, которая будет использоваться в качестве источника данных.
  2. Открытый простой API со всеми вызовами, которые вам нужны для доступа к данным, такими как getEmployeePhoneNbr (employeeId), getEmployeeName (employeeId) .... Все эти вызовы будут использовать внутренний указатель на структуру данных для доступа к конкретным данным

Используя этот подход, вам нужно будет только позаботиться о том, чтобы обеспечить правильную структуру реализации данных для вашего уровня доступа к данным, поэтому, если она изменится, вам нужно будет изменить ее только в одном месте.

0 голосов
/ 10 августа 2009

Вы можете предоставить некоторый объект контекста / среды. Скажи:

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 . Еще одним преимуществом этого подхода является то, что все ваши API становятся реентерабельными, и, используя выделенную среду для каждого потока, как потокобезопасный.

Недостаток этого подхода заключается в том, что вам придется передавать дополнительный аргумент всем вашим API.

...