Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Fragile Base Class the only reason why "inheritance breaks encapsulation"?

Tags:

As the Gang of Four states it in "Design Patterns": "it's often said that 'inheritance breaks encapsulation'", paraphrasing Snyder in "Encapsulation and Inheritance in Object-Oriented Programming Languages".

However, each time I read that "inheritance breaks encapsulation", the reasons behind this claim are either vaguely explained, or explained with an example of the Fragile Base Class problem.

While reading papers, I have the feeling that the only property of inheritance that really breaks encapsulation is downcalls, a feature allowed by open recursion (dynamic dispatch on this) and defined as "when a superclass method calls a method that is overridden in the subclass", according to Ruby & Leavens in "Safely Creating Correct Subclasses without Seeing Superclass Code".
Moreover, open recursion apparently is the cause of the Fragile Base Class problem, according to Aldrich in "Selective Open Recursion: A Solution to the Fragile Base Class Problem".

So, if the Fragile Base Class problem is the only reason why "inheritance breaks encapsulation", it would be clearer to say that downcalling breaks encapsulation. Since some solutions exist to avoid downcalls while still using inheritance, inheritance itself is not really involved in breaking encapsulation. Moreover, the Delegation pattern proposed by the Gang of Four to get rid of inheritance can also allow open recursion and downcalls, since the delegator's context (this) is used by the delegate (which can lead to a kind of Fragile Delegate Class problem).

Hence my question is:
Is the Fragile Base Class problem the only reason why it is said that "inheritance breaks encapsulation"?

like image 350
CidTori Avatar asked Jun 23 '18 14:06

CidTori


1 Answers

Yours is an interesting question. I tend to agree with you: the major issue with inheritance is the coupling between the parent and its children. This relationship hinders the ability of the parent class to evolve without breaking its children.

I'm interpreting that you meant to ask if the fragile base class problem is the only manifestation of violating the principle of "inheritance breaks encapsulation," right?

TLDR;

I believe (like you) that if we break encapsulation when using inheritance then, for sure, the manifestation of this strong coupling issue is evident in the fragility of the parent class that breaks its children when it changes.

So, in this sense or interpretation, I think you're probably right.

The opposite is not necessarily true, i.e., having a fragile base class does not necessarily imply you broke inheritance-encapsulation rules.

Coupling is the Issue

I did a careful reading of the same bibliography that you have provided, and this excerpt can shed some light into this matter.

From Design Patterns: Elements of Reusable Object-Oriented Software:

“ parent classes often define at least part of their subclasses’ physical representation. Because inheritance exposes a subclass to details of its parent’s implementation, it’s often said that “inheritance breaks encapsulation” [Sny86].”

So, it seems that GoF implied here that the fundamental problem is that of coupling. Clearly, the fragile base class is a manifestation of a coupling problem, so you might still be onto something

An object typically has a public interface that exposes to the world a contract of what it can do. These methods in the public interface of an object must satisfy several preconditions, invariants, and postconditions for the services the object provides. So users of the object interact with it based on that contract.

Users of the object need not know the implementation details of the contract. So if we ever break that contract, all users of the object suffer.

This dependency on the object's public interface is a form of coupling, and when there's coupling, there is a form of fragility to change.

For example, drivers don't need to know how the hydraulics steering wheel system works in their cars, but they can still drive a sedan or an SUV as if they were the same thing because they understand the abstraction of a steering wheel and the contract that governs its public interface. However, if we ever change the steering wheel, in general, to work as if it was that of a forklift then probably everybody would crash their cars (we broke the steering wheel public interface).

So, it is expected that the public interface of a class is pretty stable, and it is expected that any changes there will certainly break the functionality of its clients.

Inheritance breaks encapsulation when the subclasses cannot just depend on the public interface of the object, when they require additional knowledge of how their parent class is implemented in order to provide a functional child implementation (and that's why delegation is a good alternative in some cases since delegation just depends on the public interface of objects).

State Coupling

This manifests in different ways, for example, you could break encapsulation if you had direct access to state variables in the parent class (Also mentioned in Snyder's article that you shared).

From Encapsulation and Inheritance in Object-Oriented Programming Languages:

"To preserve the full benefits of encapsulation, the external interfaces of a class definition should not include instance variables"

In static languages with accessibility modifiers (e.g., Java or C#), such violation could manifest if we expose non-private fields from the parent class. In dynamic languages, without accessibility modifiers, such violation manifests when a subclass implementor accesses a field that was only intended for private use of the parent class (e.g., a _field in Python).

Would you consider this field access issue part of the fragile base class problem? It appears to me this form of inheritance coupling was not necessarily covered in the idea of the fragile base class problem of the famous article you shared.

Protected Interface Coupling

Another way this manifests is in the fact that now the parent class may need to expose a new interface other than the public interface but only intended for inheritance purposes: a protected interface. Thus, it may need to expose a set of "protected" or "privileged" methods that give access to additional details typically not exposed in the public interface provided for regular object users.

This is obviously necessary because children classes require these additional details to be able to provide a sensible implementation of the parent class with some extended functionality or altered behavior.

Now the parent class will also need to ensure that this protected interface is pretty stable since any changes there will break inheriting classes pretty much as changes in its public interface will break regular class users.

At this point, we incur into a strong form of coupling, one that might prevent the parent class from evolving in the future due to the potential issues it might cause in its children.

Now, notice that coupling and breaking of encapsulation manifested at design time, the fragility of the base class, therefore, was also introduced here, even if this coupling issue never manifests in code because we never cause a change in the parent class.

So, my interpretation is that the coupling introduced by inheritance leads to the breaking of encapsulation, which in turn leads to the fragile base class problem issues that you described.

In a way your question seems to suggest a causality chain where it appears that you suggest the fragile base class problem is what breaks inheritance, but in my case I believe it is the other way around: coupling between parent and child breaks encapsulation and this high level of coupling manifests in design as a fragile base class problem.

Fragility without Encapsulation Breakage

That being said, we now have the question, can we have a fragile base class without breaking encapsulation?

I believe we do. A child class may only entirely depend on the public interface of the parent class. For example, the child class may have provided a entirely new method that is not inherited and it is not part of the parent's public or protected interfaces.

Then one day, we add a new method in the parent class that has the exact same signature of that we added long ago in the child class, but with an entirely different intent in mind.

Now we have broken something since users of this object would expect the new method to behave as stated in the parent class interface and not as implemented in the child class.

This bug might be hard to catch. In some languages, this might cause a failure in the child class; in others it could be assumed the child overridden version is the right to use.

A variation of this problem would be if the new method defined in the parent class had a different accessibility modifier than that exact same method defined in the child class as part of the independent evolution of both classes.

At any rate, these discrepancies did not necessarily break encapsulation, but they did make the parent/child relationship fragile due to the coupling introduced by inheritance, right?

In other words, the parent class is fragile in spite of encapsulation being fine between the parent and the child class in this case.

Breaking encapsulation with inheritance does cause a fragile base class, but a fragile base class many not necessarily imply an encapsulation issue in the inheritance relationship as far as I can tell.

like image 57
Edwin Dalorzo Avatar answered Oct 11 '22 13:10

Edwin Dalorzo