Question on the design. Why is it necessary to separate what will not change from what may change, with encapsulation?

  • 2
    To localize changes =) - Dmitriy Simushev
  • @Dmitriy Simushev In order to localize the change, you can use encapsulation more often. But the question is: "Why is it necessary to separate what will not change from what can change?" - Taras
  • 2
    @Taras is obvious, in order to change only what can / should change, and inadvertently, it should not be replaced, which should not change. Let it calmly lie aside and do its constant work - DreamChild
  • 3
    The counter question: why, in fact, need to separate what is changing? The first time I hear about this principle. I have always believed that it is necessary to separate what is an independent entity, and something changes or not - it is the tenth thing. - VladD
  • one
    @VladD so that the changes concern only what needs to be changed, and minimally affect the rest of the code. In practice, following SOLID usually leads to this: the code is broken into small components, dependencies are directed from unstable (often changing) components to stable ones. - andreycha

5 answers 5

Encapsulating what changes is necessary because it saves time and effort on changing what depends on what can be changed.

Practical example

Based on the fact that the encapsulation of the changeable is usually understood as abstraction in the sense of hiding a specific principle of action, we can offer the following example.

You are designing a bank account object that is currently working with a single database. If you imagine that the account object can work with a perfect other database (or even with text files), then if you build on this immediately when designing the interface of this object, you will not have to rewrite the code that uses the account object.

<?php class Account { private $balance = 0; private $account = []; protected function save() { // сохраним в файл file_put_contents('account.txt', json_encode($this)); } public function add($amount, $explanation) { $this->balance += $amount; $this->account[] = [$amount, $explanation]; $this->save(); } public function withdraw($amount, $explanation) { $this->balance -= $amount; $this->account[] = [-$amount, $explanation]; $this->save(); } /* Пропустим другие методы */ } 

If a method is called to change the account balance, including only the amount and justification among the parameters, then the object user will absolutely not need to know which database object the account object refers to in order to save the changes.

For example, this function does not make any difference on whether the transferred account uses some database or files — the whole difference in the operation of the account is encapsulated :

 <?php class AccountConsumer { function addToAccount(Account $account) { // этой функции нет никакой разницы от того, использует // ли счет какую-то базу данных или файлы - вся разница // в работе счета инкапсулирована $account->add($this->totalCharge, $this->getExplanation()); } /* Пропустим другие методы */ } 

The addToAccount function will work equally well with both an instance of the Account class and an instance of the DatabaseAccount class:

 <?php class DatabaseAccount extends Account { protected function save() { // сохраним в базу данных foreach ($this->account as list ($amount, $explanation)) { $this->database->updateRec($amount, $explanation); } } /* Пропустим другие методы */ } 

Thanks to a properly designed Account class interface, you can somehow change the principles for storing data within Account without having to change something in classes that use an account class.

Why else would it be necessary?

In addition to convenience, the subsequent change should also take into account the factor of cognitive load on the programmer. Both abstraction and information hiding allow to reduce it. For example, to use the same account object, the programmer does not need to think about either the database or manual handling of exceptional situations. Encapsulation makes irrelevant specific details that the programmer does not need to change, ideally completely eliminating the need to think about them.

  • Just noticed a comment to your competition, which is about a beautiful number. :) - Nick Volynkin

With the help of encapsulation , an entity with a known behavior (front end) is created. Moreover, all the necessary information for the "functioning" of this entity is contained in it.

Those. the interface and implementation are separated. It is assumed that the interface will be stable (not subject to frequent changes), and the implementation may undergo frequent changes. Without encapsulation, this can be done only with functions (behavior), but not with entities (classes).

With proper design, only the interface part of the class is used without any assumptions about its internal implementation. Due to this, changes in the implementation (even radical) of one class do not affect others, which facilitates the process of changing programs and makes it less error-prone.

For example, the system of controlling the direction of movement of the car for the driver consists of a steering wheel, the rotation of which leads to the rotation of the wheels ( interface ). In this case, the system itself can be implemented in different ways and from different components (steering rack, trapezoid, etc. - implementation ). However, thanks to the encapsulation with the separation of the interface and the implementation of the change of vehicles from the point of view of steering occurs for drivers almost imperceptibly.

In general, the idea of ​​encapsulation is well illustrated (and, apparently, came from there) by the concept of abstract data types (you can read, for example, in Aho, Hopkroft, Ulman. Data structures and algorithms ). For example, there is a stack with methods (interface) push and pop . In this case, the implementation of the stack can be anything, but from the point of view of the interface there are no differences. Prior to the emergence of support for this concept in programming languages, it was necessary to implement encapsulation itself, which imposed requirements for self-discipline. However, then, this concept was supported at the level of programming language constructs. This difference can be clearly seen in the example of the related languages ​​C and C ++.

    Encapsulate what varies - Encapsulate what changes
    PS The word encapsulate should not be literally taken as the OOP principle.


    One of the basic principles of the design of the application, considered in many books on the design patterns, for example, he goes first to O'Reilly Head First Design Patterns.

    The idea is to localize when writing code that may become more complicated / change in the future due to new requirements so that these changes do not affect other parts of the application. Usually, interfaces, abstract classes and template methods are used for this.

    The simplest example is if you make a duck simulator, then you do not need to use the quack () method in the Duck base class, because rubber ducks do not quack. Of course, we can say that this method can be overridden to an empty cap for a rubber duck, but there may be other non-quacking ducks. The idea is to anticipate the changing part in the application (quack implementation) and localize it from the rest of the application (in the book, this is the use of the pattern strategy).

      In this way, you can separate the object's interface from its implementation. For example:

       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); } } 

      Interface export is widely used, for example, in Windows in the COM (Component Object Model) technology, which has replaced OLE (Object Linking and Embedding).

      When sharing objects created inside a DLL and used in the application code, it is important that the object be removed from memory in time — that is, when it is no longer needed, and not later and not earlier. For this, an interface with reference counting functions is used.

       typedef struct { void (*AddRef)(void* pObject); void (*Release)(void* pObject); } ICOMObject; 

      The library exports the interface request function by its UUID, which is the unique identifier of the interface.

       HRESULT getInterface(const char *pUUID, IComObject** ppObject); 

      The client requests the interface, gets it, and calls the AddRef () function, increasing the reference count to one. When the interface is no longer needed, the client calls the Release () function, which reduces the reference count to one. If the reference count becomes zero, the Release () function deletes the created COM object.

        Encapsulation, first of all, is necessary to hide implementation details about which no one should know. For example, take the class of some abstract parser:

         /** * Пусь это будет парсер 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; } } 

        When writing a class, we hid almost all the work in private methods, leaving only one public. Why did we do this, because it was possible to make all methods public? - if we had made all methods public, then our class would not be protected from misuse, which could easily lead to an error.

        Correspondingly, the use of encapsulation helps to protect the object's data from possible errors that may occur during direct access to this data.

        • one
          You say everything correctly, just answer the wrong question. The TS is interested in why it is necessary to encapsulate logic that undergoes frequent changes . - Dmitriy Simushev
        • one
          So that the interface for working with constantly changing logic remains unchanged and, accordingly, compatibility with the rest of the application code is maintained. I think something like that. - S. Pronin
        • I think you should add this to the answer itself =) Because now your answer, it seems to me, does not correspond to the question. - Dmitriy Simushev
        • Who is there and what is hiding? Perhaps only a pathetic attempt to govnokod under the mat Licensing & Co. sweep up With decompiling and refluxing, no one can hide anything for a long time. And hiding the implementation has always been understood as минимизация зависимостей , which is achieved by a small number of публичных методов , and subsequently their subset, which is brought to the interfaces. - Webaib