Автор: Sergey Teplyakov

В области проектирования существует два понятия, которые часто используются совместно инкапсуляция (encapsulation) и сокрытие информации (information hiding).

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

(*) иногда понятие инкапсуляции применяется в более широком смысле. Например, говорят, что фабрика инкапсулирует информацию о конкретном типе создаваемого объекта. В этом контексте инкапсуляция является синонимом сокрытия информации.

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

В результате, при проектирования класса/модуля, появляется две составляющие: открытая часть интерфейс, и закрытая часть реализация. При этом интерфейс класса или модуля должен не просто дублировать закрытую часть через аксессоры (свойства или get/set-методы), но он должен давать клиенту более высокоуровневый, абстрактный интерфейс. Другими словами, открытая часть класса должна в большей степени говорить о том, что делает этот класс или модуль, и скрывать от клиентов ненужные детали, скрывать, как он это делает.

Абстракция и икапсуляция дополняют друг друга: абстрагирование направлено на наблюдаемое поведение объекта, а инкапсуляция занимается внутренним устройством.
Гради Буч, Объектно-ориентированный анализ и проектирование

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

Вот небольшой и наивный пример: public class Employee { }
public class Paystub
{
private readonly List<,Employee>, _employees = new List<,Employee>,(),
public IList<,Employee>, Employees =>, _employees,
public decimal ComputePayroll()
{
// Используем _employees
return 42
,
}
}
public class Paystub2
{
private readonly List<,Employee>, _employees = new List<,Employee>,(),
public void AddEmployee(Employee e) { _employees.Add(e), }
public decimal ComputePayroll()
{
// Используем _employees
return 42,
}
}

В обоих случаях у нас нет открытых данных, но качество дизайна явно разное. Оба класса обладают инкапсуляцией, но первая реализация скрывает детали реализации не полностью.

Вот еще один пример, который показывает, что сокрытие информации это нечто большее, нежели закрытые данные.

Допустим, вы разрабатываете ынтырпрайз приложение, которое делает что-то важное. Любое нормальное приложение обычно требует некоторой конфигурации: ну, параметры подключения к БД или серверу, и другую ценную информацию. И вот, матерый артихектор вместе с не менее матерым клиентом просят прочитать конфигурацию из конфигурационного файла.

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

Теперь вы становитесь перед выбором: вы можете размазать сведения о конфигурации ровным слоем по всему приложению. Каждый компонент, которому нужны некоторые параметры, сам полезет в app config, вытянет оттуда нужные данные, пропарсит xml или json и будет готов служить. С другой стороны, очевидно, что решение о том, где именно хранится конфигурация и в каком формате, может измениться в будущем. Поэтому более вменяемым решением будет скрыть информацию о местоположении и формате конфигурации в одном модуле, например, с помощью классов Configuration и ConfigurationProvider. В этом случае, когда (да, именно, когда, а не если) требования изменятся, то поменяется лишь реализация класса ConfigurationProvider, а все остальные пользователи этого класса или конфигурации останутся неизменными. Аналогично, при изменении формата, поменяется тоже только процесс парсинга, а не потребители конфигурации.

Этот пример кажется надуманным, но это не так! Мы довольно часто сталкиваемся с изменчивостью требований, но используем, к сожалению, один из двух подходов:

  • Полностью игнорируем возможность изменения требований и делаем все в лоб или
  • Создаем супер сложное решение, с десятком уровней косвенности, которое должно выдержать изменения требований в любом направлении без изменения кода вообще.

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

Инкапсуляция и сокрытие информации предназначены для борьбы со сложностью и для упрощения развития системы. Абстракция и инкапсуляция позволяют думать о проблеме, не вдаваясь в ненужные детали: чтобы вычислить размер заработной платы, мне нужно собрать данные о сотрудниках и класс вычисления заработной платы сделает все остальное. Чтобы правильно использовать этот класс мне не нужно знать или думать о том, как он это сделает. Мне нужно лишь предоставить нужные входные данные.

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

Теперь у нас есть достаточно примеров, чтобы понять, что такое сокрытие информации.

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

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

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).