Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Почему нужно инкапсулировать то, что изменяется?

#проектирование #анализ


Вопрос по проектированию. 
Почему нужно отделить то, что не будет изменяться, от того, что может изменится,
при помощи инкапсуляции? 
    


Ответы

Ответ 1



Инкапсулировать то, что изменяется, нужно потому что это экономит время и силы на изменения того, что зависит от того, что может быть изменено. Практический пример Исходя из того, что под инкапсуляцией изменяемого обычно понимается абстрагирование в смысле скрытия конкретного принципа действия, можно предложить следующий пример. Вы проектируете объект банковского счета, который сейчас работает с одной базой данных. Если представить что объект счета может работать с совершенной другой базой данных (или даже с текстовыми файлами), то, если вы сразу заложитесь на это при проектировании интерфейса этого объекта, вам не придётся переписывать код, который задействует объект счета. balance += $amount; $this->account[] = [$amount, $explanation]; $this->save(); } public function withdraw($amount, $explanation) { $this->balance -= $amount; $this->account[] = [-$amount, $explanation]; $this->save(); } /* Пропустим другие методы */ } Если для изменения баланса счета вызывается метод, в числе параметров содержащий только сумму и обоснование, то пользователю объекта будет совершенно не нужно знать, к какой именно базе данных обращается объект счета чтобы сохранить изменения. Например, этой функции нет никакой разницы от того, использует ли переданный её счет какую-то базу данных или файлы - вся разница в работе счета инкапсулирована: add($this->totalCharge, $this->getExplanation()); } /* Пропустим другие методы */ } Функция addToAccount будет одинаково хорошо работать как с экземпляром класса Account, так и с экземпляром класса DatabaseAccount: account as list ($amount, $explanation)) { $this->database->updateRec($amount, $explanation); } } /* Пропустим другие методы */ } Благодаря правильно спроектированному интерфейсу класса Account вы сможете как угодно менять принципы сохранения данных внутри Account без необходимости менять что-то в классах, использующих класс счета. Зачем ещё это может быть нужно? Кроме удобства для последующего изменения стоит учитывать и фактор когнитивной нагрузки на программиста. И абстрагирование, и сокрытие информации позволяют её снизить. Например, для использования того же объекта счета программисту не нужно думать ни о БД, ни об ручной обработке исключительных ситуаций. Инкапсуляция делает несущественными конкретные детали, которые программисту не нужно изменять, в идеальном случае полностью избавляя от необходимости думать о них.

Ответ 2



При помощи инкапсуляции создается некая сущность с известным поведением (внешним интерфейсом). При этом вся необходимая информация для "функционирования" этой сущности содержится в ней же. Т.е. производится разделение интерфейса и реализации. При этом предполагается, что интерфейс будет стабилен (не подвержен частым изменениям), а реализация может претерпевать частые изменения. Без инкапсуляции это можно сделать только с функциями (поведением), но не с сущностями (классами). При правильном проектировании используется только интерфейсная часть класса без каких-либо предположений о его внутренней реализации. Благодаря этому изменения в реализации (даже радикальные) одного класса не влияют на другие, что облегчает процесс изменения программ и делает его менее подверженным ошибкам. Например, система управления направлением движения автомобиля для водителя состоит из руля, вращение которого приводит к повороту колес (интерфейс). При этом сама система может быть реализована по-разному и из разных компонентов (рулевая рейка, трапеция и т.д. - реализация). Однако, благодаря инкапсуляции с разделением интерфейса и реализации смена автомобилей с точки зрения рулевого управления происходит для водителей практически незаметно. Вообще, идея инкапсуляции хорошо иллюстрируется (и, видимо, оттуда вышла) концепцией абстрактных типов данных (можно почитать, например, в Ахо, Хопкрофт, Ульман. Структуры данных и алгоритмы). Например, есть стек с методами (интерфейсом) push и pop. При этом реализация стека может быть какой угодно, однако с точки зрения интерфейса различий нет. До появления поддержки этой концепции в языках программирования, реализовывать инкапсуляцию приходилось самому, что накладывало требования к самодисциплине. Однако, затем, эта концепция была поддержана на уровне конструкций языков программирования. Эту разницу можно хорошо увидеть на примере родственных языков C и C++.

Ответ 3



Encapsulate what varies - Инкапсулировать то, что изменятся P.S. Слово encapsulate не стоит буквально воспринимать как ООП принцип. Один из основных принципов проектирования приложения, рассмотрен во многих книжках по паттернам проектирования, например он идет первым в O'Reilly Head First Design Patterns. Идея заключается в том, чтобы при написании кода локализовать то, что в будущем может усложняться/изменяться из-за новых требований, чтобы эти изменения не затронули другие части приложения. Обычно для этого используются интерфейсы, абстрактные классы и шаблонные методы. Самый простой пример - если вы делаете симулятор уток, то не нужно метод quack() реализовывать в базовом классе Duck, потому что резиновые уточки не крякают. Конечно можно сказать, что такой метод можно переопределить на пустую заглушку для резиновой уточки, но могут найтись и другие не крякающие утки. Идея в том, чтобы предвидеть изменяющуюся часть в приложении (реализация quack) и локализовать ее от остальной части приложения (в книжке это использование паттерна стратегия).

Ответ 4



Таким способом можно отделить интерфейс объекта от его реализации. Например: class IFigure { public: virtual void draw() = 0; }; class Ellipse : public IFigure { public: void draw() { // Нарисовать эллипс. } }; class Polygon : public IFigure { public: void draw() { // Нарисовать многоугольник. } }; void drawFigure(IFigure* figure) { if (figure) figure->draw(); } IFigure* newFigure(Figure type) { switch(type) { case 'e': return new Ellipse(); case 'p': return new Polygon(); } return NULL; } using namespace std; void main() { IFigure* figure; while(true) { cout << "Что нарисовать? (e - эллипс, p - многоугольник) "; figure = newFigure(getch(cin)); drawFigure(figure); free(figure); } } Экспорт интерфейсов широко используется, например, в Windows в технологии COM (Component Object Model), пришедшей на смену OLE (Object Linking and Embedding). При совместном использовании объектов, создаваемых внутри DLL, и используемых в коде приложения, важно, чтобы объект был вовремя удалён из памяти - то есть тогда, когда он больше не нужен, а не позже и не раньше. Для этого используется интерфейс с функциями подсчёта ссылок. typedef struct { void (*AddRef)(void* pObject); void (*Release)(void* pObject); } ICOMObject; Библиотека экспортирует функцию запроса интерфейса по его UUID - уникальному идентификатору интерфейса. HRESULT getInterface(const char *pUUID, IComObject** ppObject); Клиент запрашивает интерфейс, получает его и вызывает функцию AddRef(), увеличивая счётчик ссылок на единицу. Когда интерфейс больше не нужен, клиент вызывает функцию Release(), уменьшающую счётчик ссылок на единицу. Если счётчик ссылок становится равен нулю, функция Release() удаляет созданный COM-объект.

Ответ 5



Инкапсуляция, прежде всего, нужна для того, чтобы скрыть детали реализации о которых никто не должен знать. Например, возьмем класс какого-нибудь абстрактного парсера: /** * Пусь это будет парсер e-mail'ов со страницы */ class Parser { /** * Про * @var type */ private $clear_url = null; public function parse($url) { if ($this->checking_url($url) == true) { $page_content = $this->load_page(); return $this->content_analize($page_content); } } /** * Метод проверяет URL * @param type $url */ private function checking_url($url) { //Проверяем каким-нибудь способом корректность $url $this->clear_url = $url; return true; } /** * По $this->clear_url загружаем контент * @return type */ private function load_page() { //По $this->clear_url загружаем контент и возвращаем его $content return $content; } /** * Анализируем контент и получаем все E-mail адреса * @param type $content * @return type */ private function content_analize($content) { //Анализируем контенте и из полученных адресов формируем массив $result return $result; } } При написании класса, мы скрыли почти всю работу в private методах, оставив публичным только один. Почему мы так поступили, ведь можно было сделать все методы публичными? - если бы мы сделали все методы публичными, то наш класс был бы не защищен от неправильного использования, что запросто могло бы привести к ошибке. Соответсвтено, использование инкапсуляции помогает защитить данные объекта от возможных ошибок, которые могут возникнуть при прямом доступе к этим данным.

Комментариев нет:

Отправить комментарий