2

I have a class Person like this:

 class Person {
    constructor(name, age, gender, interests) {
        Object.assign(this, {name, age, gender, interests});
    }
}

And I can make sub-class like this:

class Teacher extends Person {
    constructor(name, age, gender, interests, subject, grade) {
        super(name, age, gender, interests);
        Object.assign(this, {subject, grade});
    }
}

But what if I want to make sub-class but I don't want to inherit all the properties from the Person class. For example I don't want to inherit the interests property. Do I just exclude it like this:

class Student extends Person {
    constructor(name, age, gender, height, weight) {
        super(name, age, gender); // I haven't included the interests property here
        Object.assign(this, {height, weight});
    }
}

I am still beginner so I am not sure if this is good practice or not. Have a nice day!

Happy Coconut
  • 973
  • 4
  • 14
  • 33
  • 5
    Good practice is kind of inherently opinion-based. For instance, in my opinion, you should not inherit from a class if you don't want everything from that class. In this case, `Person` should perhaps not include `interests` and another type `InterestedPerson` be created to have that property. – Heretic Monkey May 29 '19 at 13:12
  • 5
    If this is the case: _But what if I want to make sub-class but I don't want to inherit all the properties from the Person class._ - then your design is flawed. – Randy Casburn May 29 '19 at 13:12
  • 4
    Inheritance always is an *is-a* relationship. `Student` *is a* `Person`. If a `Person` has `interests`, so does the `Student`. A child class can always be substituted anywhere the parent class could be used. – deceze May 29 '19 at 13:15
  • 2
    @HereticMonkey just to build on top of your comment, the `InterestedPerson` need not even be a class you inherit, you could acquire it through object composition. Otherwise what happens if you do have a teacher who has an interest? – VLAZ May 29 '19 at 13:15
  • But what if for example 10 sub-classes need that property but only 1 doesn't. Do I write that property on all 10 sub-classes? – Happy Coconut May 29 '19 at 13:16
  • 2
    @HappyCoconut inheritance is *not* for code sharing. If your goal is to save yourself some typing by using inheritance, then you are approaching this the wrong way. – VLAZ May 29 '19 at 13:17
  • 1
    Decide on a class hierarchy that encapsulates your logical hierarchy. If you have "special cases" in your subclasses, then your hierarchy isn't correct. – deceze May 29 '19 at 13:17
  • 3
    Look into favoring composition over inheritance. https://en.wikipedia.org/wiki/Composition_over_inheritance – Todd Chaffee May 29 '19 at 13:18
  • Ok this helped me a lot. Thanks a lot to all of you for your time to explain. – Happy Coconut May 29 '19 at 13:19

2 Answers2

4

Inheritance means what it means ... you inherit what the parent gives to you. So 'avoiding attributes' it not really recommended (and I'm not sure you can even do it).

Two solutions :

  • Architecture wise (that I recommend): in your specific case, I would just put interests in the Teacher class. If other classes would have the interests too, I would create a sub-class like PersonInterest on which Teacher would inherit from.
  • Code wise: you set interests to null or undefined in the class where you don't need it.
HRK44
  • 2,382
  • 2
  • 13
  • 30
  • 1
    The second solution could be error prone, e.g. if a number is expected and suddenly `undefined` gets added through a Subclass. – Jonas Wilms May 29 '19 at 13:19
  • 1
    @JonasWilms honestly, both solutions are problematic. But I take it as these are direct solutions for the code OP already has. – VLAZ May 29 '19 at 13:20
  • 1
    @JonasWilms well he is the one designing it, so if he goes that way, he will have to check for null or undefined values in his code. This is JS code not Java or other strongly typed language – HRK44 May 29 '19 at 13:21
  • 1
    @vlaz no, the first advice is actually quite clean. – Jonas Wilms May 29 '19 at 13:27
  • 1
    @HRK44 sure, but when giving an advice you should always include the downsides a possible solution has. – Jonas Wilms May 29 '19 at 13:28
  • And what are the downsides of the first solution? – Happy Coconut May 29 '19 at 13:31
  • 1
    There is none (in this simple case), if you however add another property that Teachers and Students have, but "Workers" that are also Persons do not, then it gets quite complicated – Jonas Wilms May 29 '19 at 13:33
  • 2
    @HappyCoconut the downside is that you now have an inheritance hierarchy for an optional attribute. The abstraction can very quickly break down. Let's assume that every `Teacher` is indeed a person with an interest and every `Student` isn't (that's a big assumption). But then you try to add another optional property - `PersonWithFavouriteColor`. You now have to inherit from two things to get the two optional properties. Add a couple more and a couple more actual classes (e.g., `Accountant` and `Merchant`) and try to give each a different set of the optional properties. It's not scalable. – VLAZ May 29 '19 at 13:39
  • @VLAZ JS is kinda limited for clean OOP, what you are talking about is more about interfaces than inheritance. See this answer : https://stackoverflow.com/questions/3710275/does-javascript-have-the-interface-type-such-as-javas-interface – HRK44 May 29 '19 at 13:48
  • @HRK44 no, you don't need inheritance nor interfaces. You can do that via composition or mixins. The latter is very easily supported in JS via prototype inheritance or straight up `Object.assign`. – VLAZ May 29 '19 at 13:51
  • @VLAZ in your previous comment you are talking about optional properties and scalability using inheritance - which in traditional OOP would be mostly done by using interfaces. In JS, you would need to do `checks` because you can not assume that a function/attribute is present, nothing guarantees that with JS classes. Even with `Object.assign`, you don't know if you would have `interests` for sure. – HRK44 May 29 '19 at 14:00
  • @HRK44 correction, I am talking about the *lack of* scalability when optional properties are encased in inheritance. I stand by that. You would have the same problem even in Java - inheritance is going to lead to the same problem there. And you can just have something like `traits` that holds extra stuff for a person and then add `Interest` object(s) to it but also `FavouriteColour` objects or whatever else. Does a person have interests? `person.traits.some(t => t instanceof Interest)`. There are many ways to avoid inheritance here and inheritance is very brittle in this case. – VLAZ May 29 '19 at 14:09
  • @VLAZ Yes, I was talking about the subject of `scalability`, I agree that there is a lack of scalability by using inheritance in the case you mentioned in the comments. – HRK44 May 29 '19 at 14:11
4
  super(name, age, gender); // I haven't included the interests property here

By not adding an argument to a function call, the parameter will implicitly be undefined. The upper therefore equals:

 super(name, age, gender, undefined)

Therefore the interests property does still exist, it is just undefined. That is actually a good solution if all your code assumes that interests could not be defined. If not, e.g. if you are doing calculations with it without an explicit check, your calculations might suddenly be NaN, which gets you into some trouble:

  if(person.age > 18) {
   alert("adult");
  } else alert("child"); // or maybe the person is not a child, and it's age property was just not set?

Now instead of setting that existing property to a value that indicates that it is undefined, you could omit the interests property at all, by:

1) Moving it to a subclass:

 class Person {
   constructor(name, age, gender) {
    Object.assign(this, {name, age, gender });
  }
 }

 class PersonWithInterests extends Person  {
   constructor(name, age, gender, interests) {
    super(name, age, gender);
    Object.assign(this, { interests });
  }
}

2) Create a Mixin:

A Mixin is a class, that can extend more than one class. If more than a Person has an interest, it might be benefitial to create a mixin for it:

 const Interested = Super => class InterestMixin extends Super {
  constructor(args) { // passing in an object here makes the Mixin more flexible, all superclasses have to deal with it though
    super(args);
    this.interests = args.interests;
  }
};

class Animal { }

const PersonWithInterest = Interested(Person);
const AnimalWithInterest = Interested(Animal);

new PersonWithInterest({ name: "Jonas", interests: 10 })
new AnimalWithInterest({ type: "bear", interests: 20 })

(If you end up creating a new Mixin for every single property, this solution is not really viable anymore. If you can't group multiple properties into a useful Mixin, go with the first way instead (having optional properties)).

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151