Поиск

Инкапсуляция.

Как я уже говорил, инкапсуляция, или утаивание информации (information hiding), — это возможность скрыть внутреннее устройство объекта от его пользователей, предоставив через интерфейс доступ только к тем членам объекта, с которыми клиенту разрешается работать напрямую. Поскольку в том же контексте я говорил также об абстрагировании, то считаю нужным пояснить разницу между этими похожими понятиями. Инкапсуляция подразумевает наличие границы между внешним интерфейсом класса (открытыми членами, видимыми пользователям класса) и деталями его внутренней реализации. Преимущество инкапсуляции для разработчика в том, что он может открыть те члены класса, которые будут оставаться статичными, или неизменяемыми, скрыв внутреннюю организацию класса, более динамичную и в большей степени подверженную изменениям. Как уже говорилось, в С# инкапсуляция достигается путем назначения каждому члену класса своего модификатора доступа — public, private или protected.

Абстрагирование

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

При объявлении классов в объектно-ориентированных языках вы можете использовать такие имена и интерфейсы, которые отражают смысл и назначение объектов предметной области. "Удаление" элементов, не связанных напрямую с решением задачи, позволит вам полностью сосредоточиться на самой задаче и решить ее более эффективно. Перефразируя высказывание из книги Брюса Эккеля (Вшсе Eckel) "Thinking in Java", можно сказать: в большинстве случаев умение достичь решения проблемы сводится к качеству применяемого абстрагирования.

Однако язык — это один уровень абстрагирования. Если вы пойдете дальше, то, как разработчику класса, вам нужно придумать такую степень абстрагирования, чтобы клиенты вашего класса могли сразу сосредоточиться на своей задаче, не тратя время на изучение работы класса. На очевидный вопрос — какое отношение интерфейс класса имеет к абстрагированию? — можно ответить так: интерфейс класса и есть реализация абстрагирования.

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

При определении нужной степени абстрагирования класса важно помнить и о программисте клиентского кода. Представьте, что вы пишете основное ядро базы данных. Возможно, вы прекрасно разбираетесь в таких понятиях БД, как курсоры (cursors), управление фиксацией (commitment control) и кортежи (tuples). Однако многие разработчики, не столь искушенные в программировании БД, не собираются вникать в тонкости этих понятий. Используя терминологию, непонятную клиентам вашего класса, вы не достигнете основной цели абстрагирования — повысить эффективность работы программиста путем представления предметной области в понятных ему и естественных терминах.

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

ПРИМЕЧАНИЕ Может показаться, что применение объектно-ориентированных технологий главным образом исчерпывается более упрощенным созданием классов. При этом на самом деле достигается некоторый выигрыш в производительности, однако долговременные выгоды вы получите, поняв, что основное назначение ООП в облегчении программирования клиентам классов. При разработке своих классов вы всегда должны ставить себя на место программиста, которому предстоит работать либо с экземплярами этих классов либо с производными от них классами.

О пользе абстрагирования

Наличие в классах абстрагирования, которое максимально удобно для программистов, работающих с этими классами, имеет первостепенное значение при разработке повторно используемого ПО. Если вы выстроите интерфейс, на который не влияют изменения в реализации, то вашему приложению долгое время не понадобятся никакие модификации. Вспомните пример с расчетом зарплаты. При работе с объектом Employee и функциями, обеспечивающими расчет зарплаты, клиенту нужны лишь несколько методов, таких как CalculatePay, GetAddress и GetEmployeeType. Если вы знакомы с предметной областью задачи, вы без труда определите, какие методы понадобятся пользователям класса. Скажем так: если при проектировании класса вам удается сочетать хорошее знание предметной области с прогнозом относительно дальнейших перспектив использования класса, можно гарантировать, что большая часть интерфейса этого класса останется неизменной, даже в случае возможного совершенствования реализации класса. В данном примере для пользователя главным является только класс Employee, в котором, с его точки зрения, от версии к версии лучше бы ничего не менять.

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