voice field is a member of the
AbstractAnimal class; if you access it in the
Dog class using the
this pointer, then, as far as the subclass is concerned,
voice is part of the base class' public interface towards its subclasses. This means that there's extra coupling between the two classes, as both
Dog now rely on
voice being present in the superclass, and on
This coupling means that it will be difficult to change the internal structure and implementation of
AbstractAnimal (e.g., remove
voice, or represent it in some other way, and/or change some methods that use it) without affecting existing subclasses. This is all assuming that there's actually some role
voice plays in the superclass, some behavior implemented in
AbstractAnimal itself; if that's not the case, consider if
AbstractAnimal should have the
voice field in it at all.
Initializing superclass members via the constructor, and treating the
voice field as private to the superclass, hides the internal details behind the interface of
AbstractAnimal. This is generally a good idea, but again, you'll have to decide if decoupling is worth the hassle for you. If it is, you would design your classes so that each can do its job and collaborate with the other without having to rely on the other's internals. In statically typed languages, you can enforce this to some extent; here, it requires developer discipline and, in a team setting, clear communication among the team members.
Another thing you can do is to have a narrow interface for the client code (code outside of the inheritance hierarchy) and a wider interface for the subclasses (a few extra methods and maybe fields that subclasses are allowed to use, but that are not to be used from the outside). This should be somehow documented and communicated to the readers of the code. In other languages, you would do this by using the public and protected access modifiers, in ES you'd rely on naming conventions (
_name) and/or certain tricks (e.g. leverage closures).
the second option will bubble everything up to one place, but than you could end up with huge constructors taking a lot of arguments.
Well, don't end up with such constructors; don't design your classes in such a way. If you have too many arguments, examine your class design more closely. Maybe your classes really do need a bunch of parameters, in which case you can bundle them in a parameter object, but more likely, your classes are trying to do too many things at once (they handle too many responsibilities), so you should break them up.
Finally, a superclass should be a behavioral abstraction of the subclasses (in the sense of LSP), you should generally avoid inheritance just so that you could inherit data members and some useful functions, although this can be handy. In the famous GoF book there's a famous line:
Favor object composition over class inheritance.
They state this as one of the principles of OO design; composition is a more flexible alternative, and designs can often be made simpler with it. They acknowledge that this is not always achievable in practice, but that more often then not, programmers overuse inheritance.
So, generally speaking, reserve inheritance for creating behavioral abstractions and subsystem facades to program against, and if you need to compose objects out of smaller parts, use traditional composition, or mixins.