📜 ⬆️ ⬇️

Inheritance implementations: bury stewardess

Key Contradiction OOP


As you know, the classic OOP rests on three pillars:


  1. Encapsulation
  2. Inheritance
  3. Polymorphism

Classic default implementation:


  1. Encapsulation - public and private class members
  2. Inheritance is the implementation of a functional due to the expansion of one ancestor class, protected class members.
  3. Polymorphism - virtual methods of ancestor class.

But back in 1986, a serious problem was identified , briefly formulated as follows:


Inheritance breaks encapsulation


  1. The descendant class has protected ancestor class members. All the rest is available only public class interface. The ultimate case of hacking is the antipublic frost pattern;
  2. Actually, ancestor behavior can only be changed by overlapping virtual methods;
  3. The principle of substitution Liskov obliges the descendant class to satisfy all the requirements for the ancestor class;
  4. To carry out step 2 in exact accordance with clause 3, the descendant class needs complete information about the time of the call and the implementation of the overlapped virtual method;
  5. The information from clause 4 depends on the implementation of the ancestor class, including private members and their code.

In theory, we already have an epic rejection, but what about practice?


  1. The dependency created by inheritance is extremely strong;
  2. Heirs are hypersensitive to any changes in the ancestor;
  3. Inheriting from someone else's code adds hellish pain when accompanied: library developers are at risk of getting obstructed due to broken backward compatibility with the slightest change in the base class, and applied programmers regress with any updating of the libraries used.

Anyone who uses frameworks that require inheritance from their classes (WinForms, WPF, WebForms, ASP.NET) will easily find confirmation of all three points in their experience.
is it so bad?


Theoretical solution


The effect of the problem can be weakened by the adoption of certain conventions:


1. Protected members are not needed.
This agreement eliminates frost-like publics as a class.


2. Ancestor Virtual Methods Do Nothing
This agreement allows you to combine knowledge about the implementation of the ancestor with the independence of her implementation already in the offspring.


3. Ancestor virtual methods are never called in its code.
This agreement allows descendants not to depend on the internal implementation of the ancestor, and also requires the publicity of all virtual methods.


4. Ancestor instances are never created.
This agreement allows you to get rid of the inconsistency of requirements for virtual methods (public class contract) on the one hand and the duty to do nothing (protected class contract) on the other. Now the Liskov substitution principle can be observed without entering into a vicious relationship with the closed contents of the ancestor.


5. Ancestor has no non-virtual members
Given previous agreements, non-virtual members of the ancestor become useless and are subject to liquidation.


Result: if the ancestor class consists of public virtual empty methods and requirements for them for descendants, then inheritance no longer breaks the encapsulation. Q.E.D.


Along the way, we are able to solve the rhombus problem in the case of multiple inheritance from conventional ancestors. But this is all theory, and we need ...


Practical solutions


  1. Virtual dummy methods already exist in many languages ​​and carry the proud title of the abstract .
  2. Classes, instances of which can not be created, also exist in many languages ​​and even have the same title .
  3. Full compliance with these agreements in the C ++ language was used as a pattern for the design and implementation of the Component Object Model .
  4. And the best part: in C # and many other languages, conventions are implemented as a first-class "interface" element.
    The origin of the name is obvious - as a result of compliance with the agreements, the class leaves only its public interface. And if multiple inheritance from ordinary classes is rare, it is accessible from interfaces without any restrictions.

Results


  1. Languages ​​where there is no inheritance from classes, but there are - from interfaces (for example, Go), cannot be stripped of object-oriented titles. Moreover, such an implementation of OOP is more theoretically and safer in practice.
  2. Inheritance from ordinary classes (with implementation) is an extremely specific and extremely dangerous archaism.
  3. Avoid inheriting implementations unless absolutely necessary.
  4. Use the sealed modifier (for .NET) or its equivalent for all classes, except those specifically designed for implementation inheritance.
  5. Avoid public unprinted classes: as long as inheritance does not go beyond the scope of your assemblies, you can still benefit from it and limit the damage.

PS: Additions and criticism are traditionally welcome.


')

Source: https://habr.com/ru/post/310314/


All Articles