How to deal with polymorphism inside a class

In languages ​​with dynamic typing, the use of polymorphism can lead to errors in the superclass.

I will try to explain my question with a simple example: Suppose that a language with dynamic typing (for example, ECMAScript) and the following class structure:

diagram

class A{ private idA; public A(){ idA=0; } public foo(){ update(); if (this.idA!=3) Throws new Exception(" What is happening? "); } private update(){ this.idA = 3; } } class B extends A{ private idB; public B(){ super(); idB=0; } public foo(){ super.foo(); // Any operation between A::update and B::update() if (this.idB!=0) Throws new Exception("hmmm, that could not happend!"); update(); } private update(){ this.idB = 5; } } 

In this very simple example, when I create an object of class B, B :: foo () calls the parent A :: foo (), which causes an “update”. The object is an instance of B, so the called update functions are called B :: update, after which the update function (B :: update) appears again in B :: foo. The end result is that A :: update is never called, and idA is still 0.

Class A works correctly if one is used, but after extending it with B, the foo () function fails.

What is the correct solution to this problem:

1) Make class A call A :: update, which means ugly code, every call to its own function (superclass protection):

 A::foo(){ A::update(); if (this.idA!=3) Throws new Exception(" What is happening? "); } 

2) B :: update is an extension of A :: update, so B :: update should call itself a parent function (prepare a subclass and solve problems):

 B::foo(){ super.foo(); ... // Any operation that must be performed between A::update and B::update } B::update(){ super.update(); this.idB = 5; } 

But in this case, it is A :: foo, which causes the update, not B :: foo. This means other problems.

3) Any other solution.

As a summary:

How to protect superclass code from polymorphism?

  • Add protections to the superclass.
  • The solution to this problem when creating a child class
  • The tongue must do it! (I don't know if this is possible with dynamically typed languages)

I am looking for a very theoretical / canonical solution to this question.

EDITED: get the problem out of the constructor and clarify some points.

+6
source share
4 answers

The base class must protect itself from malicious overrides. In accordance with the open / close principle, it should be open for expansion, but closed for modification. Overriding update is a harmful modification to the behavior intended for the base class. In your example, there is no advantage to overriding update , since both A::update and B::update are private methods that deal with private variables. It is not even expected that they should be executed together, judging by your exception in B::foo . If B::update was named differently, there would be nothing wrong with your implementation. In any case, everything will probably be fine: since not a single language that I know about allows you to override a private method, B::update can hide A::update rather than override it.

Depending on the language, you can limit which methods can be overridden in different ways. Some languages ​​require an indicator (a keyword or attribute usually) that the method can be overridden, while others show that it cannot. Private methods, as a rule, are not redefined, but not all languages ​​have access modifiers, and everything becomes public. In this case, you will have to use some kind of convention suggested by @PoByBolek.

tl; dr: Children do not deal with ordinary parents.

+3
source

It is generally considered that it is very bad practice to call instance methods, and especially virtual instance methods from the constructor, precisely for this reason (but also for the reason that the object does not execute when being "initialized").

3) Any other solution.

Doc, it hurts me when I do this.

Then do not do it!

Seriously, if you need to set IdA in constructor A , don't do this by calling update , do this by explicitly setting the IdA value in constructor for A

+8
source

You probably won't like my answer, but: agreement and discipline .

Set agreements for

  • when it is safe for a child class to override a method without invoking an implementation of the parent class,
  • when a child class must invoke the implementation of the parent class of the overridden method,
  • when the child class should not override the method of the parent class.

Document these conventions and stick to them. They should probably be part of your code; either in the form of comments or naming conventions (whatever works for you). I might think of something like this:

 /* * @final */ function shouldNotBeOverridden() { } /* * @overridable * @call-super */ function canBeOverriddenButShouldBeCalledFromChildClasses() { } /* * @overridable */ function canBeOverridenWithoutBeingCalledFromChildClasses() { } 

This can help someone read your code to find out what methods it can or cannot override.

And if someone still overrides your @final methods, you hopefully do some rigorous testing;)


I like this answer to a somewhat similar question regarding python:

Here you can add a comment:

 # We'll fire you if you override this method. 
+2
source

If a language allows one class to invoke a private method of another class this way, the programmer must understand and live with it. If I understand your purpose, foo and update should be overridden and the update must be protected. They then call the method in the parent class when necessary. The resulting foo method will not need to call the update because calling foo in the parent class will take care of this. The code may work as follows:

 class A{ private idA; public A(){ idA=0; } public foo(){ update(); if (this.idA!=3) Throws new Exception("idA not set by update"); } protected update(){ this.idA = 3; } } class B extends A{ private idB; public B(){ super(); idB=0; } @Override public foo(){ super.foo(); // Any operation between A::update and B::update() if (this.idB!=5) Throws new Exception("idB not set by super.foo"); } @Override protected update(){ super.Update() this.idB = 5; } } 

I modified the exceptions to meet expectations.

+1
source

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


All Articles