Reputation: 162
I've been learning how to implement composition into my python programming, but I'm struggling to understand why it's preferred over inheritance.
For example, here is my code so far:
class Particle:
# Constructor (public)
def __init__(self, _name, _charge, _rest_energy, _mass, _velocity):
# Attributes (private)
self.__name = _name
self.__charge = _charge
self.__restEnergy = _rest_energy
self.__mass = _mass
self.__velocity = _velocity
# Getter functions (public)
def getName(self):
return self.__name
def getCharge(self):
return self.__charge
def getRestEnergy(self):
return self.__restEnergy
def getMass(self):
return self.__mass
def getVelocity(self):
return self.__velocity
# Setter procedures (public)
def setName(self, _name):
self.__name = _name
def setCharge(self, _charge):
self.__charge = _charge
def setRestEnergy(self, _rest_energy):
self.__restEnergy = _rest_energy
def setMass(self, _mass):
self.__mass = _mass
def setVelocity(self, _velocity):
self.__velocity = _velocity
class Quark:
# Constructor (public)
def __init__(self, _name, _charge, _strangeness):
# Attributes (private)
self.__name = _name
self.__charge = _charge
self.__strangeness = _strangeness
# Getter functions (public)
def getName(self):
return self.__name
def getCharge(self):
return self.__charge
def getStrangeness(self):
return self.__strangeness
class Hadron:
# Constructor (public)
def __init__(self, _name, _charge, _rest_energy, _mass, _velocity, _quarks):
# Attributes (private)
self.__particle = Particle(_name, _charge, _rest_energy, _mass, _velocity)
self.__quarks = _quarks
# Getter functions (public)
def getParticle(self):
return self.__particle
def getQuark(self):
return self.__quarks
def getStrangeness(self):
_quarks = self.__quarks
_strangeness = 0
for _quark in _quarks:
_strangeness += _quark.getStrangeness()
return _strangeness
def getRelCharge(self):
_quarks = self.__quarks
_relCharge = 0
for _quark in _quarks:
_relCharge += _quark.getCharge()
return _relCharge
def getName(self):
return self.__particle.getName()
def getCharge(self):
return self.__particle.getCharge()
def getRestEnergy(self):
return self.__particle.getRestEnergy()
def getMass(self):
return self.__particle.getMass()
def getVelocity(self):
return self.__particle.getVelocity()
# Setter functions (public)
def setName(self, _name):
self.__particle.setName(_name)
def setCharge(self, _charge):
self.__particle.setCharge(_charge)
def setRestEnergy(self, _rest_energy):
self.__particle.setRestEnergy(_rest_energy)
def setMass(self, _mass):
self.__particle.setMass(_mass)
def setVelocity(self, _velocity):
self.__particle.setVelocity(_velocity)
I'm not sure if I've gotten the wrong end of the stick here or what, but it seems incredibly wasteful, when I could just inherit from the Particle class.
Am I doing something wrong?
Upvotes: 2
Views: 1616
Reputation: 45750
Which you use depends on what relationship you're trying to model.
Composition isn't always the better option. "Composition over inheritance" is often repeated because inheritance is often abused with the idea that it reduces the amount of code that you need to write. That's entirely the wrong motivation to base your decision on though.
If you have two classes, A
and B
, a rough, general guide is:
B
is an A
, you probably want inheritance.B
has an A
, you probably want composition.In your case here, from my extraordinarily limited knowledge of particle physics, a Hadron
is a Particle
, so inheritance is probably a better fit. A Hadron
doesn't contain/have a Particle
, so I think you're trying to work against the grain by forcing composition here.
Upvotes: 9
Reputation: 77912
Carcigenicate already provided a very good answer. Just wanted to add something about this part:
I'm struggling to understand why (composition) is preferred over inheritance.
Actually, it's about composition/delegation, not just composition (which by itself doesn't provide the same features). The point is that inheritance actually serves two purposes: subtyping and implementation reuse.
Subtyping denotes a "is a" relationship - if B is a (proper) subtype of A, you can use B everywhere you can use A. Actually the Liskov Substitution Principle put it the other way round: "B is a proper subtype of A if any code accepting an A can accept a B instead". Note that this doesn't say anything about inheritance nor implementation, and that neither of those are required (on a theoretical level that is) for subtyping.
Now with statically typed languages, you have to use inheritance for subtyping, period - and you eventually get implementation reuse as a bonus, if there's any implementation to reuse at least.
OTHO Python being dynamically typed doesn't need inheritance for subtyping (as long as the code using your object don't do any typecheck of course) - just having a compatible interface is enough. So in Python, inheritance is mostly about implementation reuse.
Now technically, implementation reuse through inheritance IS a form of composition/delegation - your object is an instance of it's own class, but also of all it's superclasses, and whatever attribute is not resolved on your instance itself nor on it's class will be looked up on the parent classes. The main difference with "manual" composition/delegation is that inheritance is more restricted - you cannot change who you delegate to at runtime nor on a per-instance basis for example (well... in Python, you actually can, technically, change an instance's class at runtime, but in practice it's a very bad idea and never works as expected - been here, done that xD).
So wrt/ implementation reuse, inheritance is a restricted and mostly static form of composition/delegation. That's fine for quite a lot of use cases - typically framework more-or-less-abstract bases classes that need to be inherited etc -, but some other cases are better solved with a more dynamic solution (canonical examples are the state and strategy design patterns, but there are plenty others).
Also, even if only used for implementation reuse, inheritance STILL imply a "is a" relationship - even if the child class is not a proper subtype of it's base (any incompatibilty will break proper subtyping, and nothing prevents you to change some of your subclass methods with incompatible signatures) -, and Python not having any notion of "private inheritance", your child class will expose all it's inherited interface, which is not necessarily what you want (and actually quite often what you DONT want when just doing implementation reuse). And of course if you decide to change which implementation you want to reuse, then well... inheritance introduces a much stronger coupling (and that's an understatement) than composition/delegation.
A typical "beginner error" example is to inherit from some builtin collection type (let's say list
for example) and try to "restrict" it to their specific needs. This never really works as expected and usually ends up requiring much more work than using composition/delegation. Then they realize that (still for example) an OrderedDict would have been a much better basis for their own use case, and then they have an issue with the client code now being dependent on the inherited list
interface... Using composition/delegation right from the start would have prevented a lot of pain here - by keeping the interface restricted to relevant features instead of leaking the inherited interface, and so keeping the "implementation reuse" thing to what it really is: an implementation detail that the client code should never have been aware of.
The core issue is actually with so many very very poor "OO 101" texts that present inheritance as one of the key OO features (which it's not - the real "key OO features" are encapsulation - not to be confused with data hidding BTW - and type-based polymorphic dispatch), resulting in beginners trying to overuse inheritance without ever realizing that there are other - and sometimes much better - solutions.
To make a long story short: like any other "golden rule", favoring composition/delegation over inheritance is only a "rule" when you don't understand the pros and cons of each solution and when each one is more appropriate - IOW, just like any "golden rule", you do NOT want to blindly accept and apply it (which would lead to stupid design choices), but - as you rightly did - question it until you understand what it's really about and don't even have to think about it anymore.
Oh and yes: you may want to learn about the __getattr__
magic method (for delegation) - and the descriptor protocol and builtin property
type (for computed attributes support) too while you're at it (hint: you don't need those private attributes / public accessors thing in Python).
Upvotes: 5