Why is it necessary to encapsulate what changes?

A question about design. Why is it necessary to separate what will not change from what can change by encapsulation?

Author: Nofate, 2016-03-04

5 answers

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 currently works with a single database. If imagine that an invoice object can work with a completely different database (or even with text files), then if you immediately lay down on this when designing the interface of this object, you do not have to rewrite the code that uses the invoice 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 that contains only the amount and justification among the parameters, then the user of the object will not need to know which database it is accessing the invoice object to save the changes.

For example, this function does not make any difference from whether the account passed to it uses some database or files - all the difference in the operation of the account is encapsulated:

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

    /* Пропустим другие методы */
}

Function addToAccount 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 well-designed interface of the Account class, you can change the principles of data storage in any way you want inside Account without having to change anything in the classes that use the account class.

Why else would it be necessary?

In addition to the convenience for subsequent changes, it is worth considering the factor of the cognitive load on the programmer. Both abstracting and hiding information can reduce it. For example, to use the same invoice object, the programmer does not need to think about either the database or the manual handling of exceptions. Encapsulation makes specific details irrelevant, which the programmer does not need to change, ideally completely eliminating the need to think about them.

 19
Author: sanmai, 2016-03-09 03:12:41

Using encapsulation creates an entity with a known behavior (external interface). At the same time, all the necessary information for the" functioning " of this entity is contained in it.

That is, 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 only be done with functions (behaviors), but not with entities (by classes).

When properly designed, only the interface part of the class is used, without any assumptions about its internal implementation. Because of this, changes in the implementation (even radical ones) of one class do not affect others, which makes it easier to change programs and makes it less error-prone.

For example, the driver's car direction control system consists of a steering wheel, the rotation of which causes the wheels to turn ( interface). At the same time, the system itself can be implemented in different ways and from different components (steering rack, trapezoid, etc.-implementation). However, thanks to the encapsulation of with the separation of the interface and the implementation, the change of cars from the point of view of steering is almost imperceptible for drivers.

In general, the idea of encapsulation is well illustrated (and apparently came out of there) by the concept of abstract data types (you can read, for example, in Aho, Hopcroft, Ullman. Data structures and algorithms). For example, there is a stack with methods (interface) push and pop. At the same time, the implementation of the stack can be anything, but from the point of view of the interface, there are no differences. Before there was support for this concept in programming languages, you had to implement encapsulation yourself, which imposed requirements for self-discipline. However, then, this concept was supported at the level of programming language constructs. This difference can be it is good to see the example of the related languages C and C++.

 8
Author: Konstantin Les, 2016-03-07 11:13:22

Encapsulate what varies-Encapsulate what will change
P.S. The word encapsulate should not be taken literally as an OOP principle.


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

The idea is to localize when writing code that may become more complex/change in the future due to new requirements, so that these the changes did not affect other parts of the app. This is usually done using interfaces, abstract classes, and template methods .

The simplest example is that if you are making a duck simulator, you don't need to implement the quack() method in the base Duck class, because rubber ducks don't quack. Of course, we can say that this method can be redefined to an empty plug for a rubber duck, but there may be other non-quacking ducks. The idea is to anticipate the changing environment. the part in the application (implementation of quack) and localize it from the rest of the application (in the book, this is the use of the strategy pattern).

 8
Author: AdamSkywalker, 2016-03-07 22:12:12

In this way, you can separate the interface of an object 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 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 is removed from memory in time - that is, when it is no longer needed, and not later and not earlier. To do this an interface with link 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 - the unique interface identifier.

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

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

 1
Author: Vanyamba Electronics, 2016-03-31 22:33:47

Encapsulation is primarily needed to hide implementation details that no one should know about. 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 the class, we hid almost all the work in the private methods, leaving only one public. Why did we do this, because it was possible to make all the methods public? - if we made all the methods public, then our class would not be protected from misuse, which could easily happen lead to an error.

Accordingly, the use of encapsulation helps to protect the object data from possible errors., which may occur when accessing this data directly.

 -1
Author: S. Pronin, 2016-03-06 18:04:54