1

In polymorphism, your base class defines an interface. The inheriting classes fill in the data for that interface. The key is that the interface remains the same.

Given that, if your interface is properties/attributes (not methods), is it more appropriate to override a getter, or to simply define/overwrite the attribute directly?

Example

A trivial example in Python for illustration purposes. Animal is a base class, and it defines attributes max_age and num_legs.

Overriding Getters Version

class Animal:

    @property
    def max_age(self):
        raise NotImplementedError()

    @property
    def num_legs(self):
        raise NotImplementedError()


class Dog(Animal):

    @property
    def max_age(self):
        return 14

    @property
    def num_legs(self):
        return 4

Overwriting Attributes Version

class Animal:
    max_age: int = None
    num_legs: int = None


class Dog(Animal):
    max_age: int = 14
    num_legs: int = 4

Which fits correctly into Polymorphism, or do both? References are greatly appreciate, if possible.

8
  • 1
    Both are an example of polymorphism, but the two versions are not the same: they define different interfaces (in terms of how they can be used). In your first example the @property decorator just defines a getter, meaning that max_age and num_legs cannot be set, wheres in the attributes version, you can set the values from the outside after the Dog object has been instantiated. Use whichever seems more appropriate for what you're trying to do. Jul 8 at 1:19
  • BTW, I generally wouldn't use polymorphism in this way (just to set the value of some properties); usually, it's more useful to override a method to provide new behavior. Values you can set in the constructor (or you can call the base class constructor). Jul 8 at 1:21
  • @Filip In my non-trivial example, the two attributes have significantly different calculations depending on which "type" is used, which is why I opted for Polymorphism. Unfortunately it's not just a data package, or else I would just pass them into a single class's constructor. Jul 8 at 2:34
  • 1
    Where does the limitation to define properties/attributes in the base class come from? Wouldn't it be more natural to have abstract methods in the base class and to redefine them into attributes in descendants? Jul 8 at 6:10
  • 1
    I think this depends far more than you imply on what language you're working in. Some languages don't have dynamic properties, or have them only in limited form; others don't allow sub-classes to redefine properties, or don't constrain them in the same way as methods and dynamic properties. Hence a lot of the advice you're getting is quite Python-specific, even though you say that's just an example.
    – IMSoP
    Jul 9 at 9:06

1 Answer 1

3

In short

I'd strongly recommend to go for the first approach, even if you could reject all my arguments against the second.

More explanations

Your first approach, overriding getters, has many advantages. The first is that it will work without surprise in many languages.

Moreover, in the spirit of the open/closed principle it leaves a lot of possibilities open for the future. For example, since the maximum age is per instance, you could imagine a class Tortoise that calculates the age based on the size of the individual instead of a fixed number. You could even calculate the maximum age on the spot, based on statistical indicators of the individual's location, in real time.

The second approach, overriding attributes, has many inconveniences. First, it will fail in the many languages that do not support attribute overriding. Some languages will seem to accept it but in reality silently create additional members in a different scope (e.g. Java, C++). This might result in different methods accessing different members sharing the same name, and very nasty bugs. But ok: in python it works.

Moreover:

  • you expose the member, which might weaken encapsulation. But ok: in python, it's a risk that has to be accepted.
  • The modification of the overridden attribute is a serious risk regarding Liskov Substitution Principle, and more precisely the history constraint, which requires base variables/state to modified via base methods. This might lead to nasty bugs, in any OOP language.
  • It assumes one fixed max_age per species. And if it's not the case, you'll have to calculate and update the maximum age in different place (size, gender, year of birth, ...) even if you'll never use it.
  • It's just not as flexible as the first one. It's a consequence of the underlying assumption. Take the Tortoise example above. To get the realtime feature would require to track a realtime indicator to always use the latest value. This could be very inefficient. But ok, you might never need this.

That's 3 design principles and a couple of practical points that make the first approach a more recommendable habit. If you would go for the second approach, then it would be safer not to override the member variables, but to define their value via the supertype's constructor, or using a supertype setter.

7
  • Good answer, though I think I'd like to see cons of the first and pros of the second for a more complete picture. In the end, I agree that it depends on the situation, the life-time of the object, the language, etc. I love the second for it's brevity and simplicity (always valuable), but I agree that the second is more robust. Lastly, I do perform calculations, but I perform them in the constructor of the inheritors, then override the attributes. Jul 8 at 15:00
  • (small change to the last statement) Lastly, for my specific case I do perform calculations, but I perform them in the constructor of the inheritors, then override the attributes. Lifetime is short, and the values do not change. Jul 8 at 15:08
  • 1
    @ChrisCollett I see. This weakens my two last bullets in the inconvenience (but only for this specific case). But if you can assume that you have a fixed value and do the calculation in the constructor, why not reuse the existing attributes? The solution would then still be less flexible than the first option, but at least, you'd have a solution that works in many more languages.
    – Christophe
    Jul 8 at 19:06
  • Could you give an example of what you mean? I'm not sure what you mean by "reuse the existing attributes". Jul 8 at 19:36
  • 1
    Thanks for the additional edits and articles. In a simple case in a language that supports it, approach 2 may work OK. But it is a fragile construct that may well need to be refactored with any added complexity. Jul 10 at 11:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.