习题 44: 继承(Inheritance) VS 合成(Composition) **************************************** 童话里经常会看到英雄打败恶人的故事,而且故事里总会有一个类似黑暗森林的场景——要么是一个山洞,\ 要么是一篇森林,要么是另一个星球,反正是英雄不该去的某个地方。当然,一旦反面角色在剧情中出现,\ 你就会发现英雄非得去那片破森林去杀掉坏人。当英雄的总是不得不冒着生命危险进到邪恶森林中去。 你很少会碰到这样的童话故事,说是英雄机智地躲过这些危险处境。你从不会听英雄说:“等等,如果我把\ Buttercup 留在家里,自己跑出去当英雄闯世界,万一我半路死了,Buttercup 就只好嫁给那个叫 Humperdink 的丑八怪王子。 Humperdink 啊卧槽!我还是呆在这儿,做点出租童工的生意吧。”如果\ 他选择了这条路,就不会碰到火沼泽、死亡、复活、格斗、巨人,或者任何算得上故事的东西了。就是因为这个,\ 这些故事里的森林就像黑洞一样,不管英雄是干嘛的,最终都无法避免地陷入其中。 在面向对象编程中,“继承”就是那片邪恶森林。有经验的程序员知道如何躲开这个恶魔,因为他们知道,\ 在丛林深处的“继承”,其实是邪恶女皇“多级继承”。她喜欢用自己的巨口尖牙吃掉程序员和软件,咀嚼\ 这些堕落者的血肉。不过这片丛林的吸引力是如此的强大,几乎每一个程序员都会进去探险,梦想着提着\ 邪恶女皇的头颅走出丛林,从而声称自己是真正的程序员。你就是无法阻止丛林的魔力,于是你深入其中,\ 而等你冒险结束,九死一生之后,你唯一学到的,就是远远躲开这片破森林,而如果你不得不再进去一次,你会\ 带一支军队。 这段故事就是为了教你避免使用“继承”这东西,这样说是不是更带感呢?有的程序员现在正在丛林里跟\ 邪恶女皇作战,他会对你说你必须进到森林里去。他们这样说其实是因为他们需要你的帮助,因为他们\ 已经无法承受他们自己创建的东西了。而对于你来说,你只要记住这一条: 大部分使用继承的场合都可以用合成取代,而多级继承则需要不惜一切地避免之。 什么是继承 ==================== 继承的用处,就是用来指明一个类的大部分或全部功能,都是从一个父类中获得的。当你写 ``class Foo(Bar)`` 时,代码就发生了继承效果,这句代码的意思是“创建一个叫 Foo 的类,并让他继承 Bar”。当你这样写时,\ Python 语言会让 `Bar` 的实例所具有的功能都工作在 `Foo` 的实例上。这样可以让你把通用的功能\ 放到 `Bar` 里边,然后再给 `Foo` 特别设定一些功能。 当你这么做的时候,父类和子类有三种交互方式: 1. Actions on the child imply an action on the parent. 2. Actions on the child override the action on the parent. 3. Actions on the child alter the action on the parent. I will now demonstrate each of these in order and show you code for them. Implicit Inheritance -------------------- First I will show you the implicit actions that happen when you define a function in the parent, but *not* in the child. .. literalinclude:: ex/ex44a.py :linenos: The use of `pass` under the ``class Child:`` is how you tell Python that you want an empty block. This creates a class named `Child` but says that there's nothing new to define in it. Instead it will inherit all of its behavior from `Parent`. When you run this code you get the following: .. literalinclude:: ex/ex44a.txt :linenos: Notice how even though I'm calling ``son.implicit()`` on line 16, and even though `Child` does *not* have a `implicit` function defined, it still works and it calls the one defined in `Parent`. This shows you that, if you put functions in a base class (i.e. `Parent`) then all subclasses (i.e. `Child`) will automatically get those features. Very handy for repetitive code you need in many classes. Override Explicitly ------------------- The problem with implicitly having functions called is sometimes you want the child to behave differently. In this case you want to override the function in the child, effectively replacing the functionality. To do this just define a function with the same name in `Child`. Here's an example: .. literalinclude:: ex/ex44b.py :linenos: In this example example I have a function named `override` in both classes, so let's see what happens when you run it. .. literalinclude:: ex/ex44b.txt :linenos: As you can see, when line 14 runs, it runs the `Parent.override` function because that variabe (`dad`) is a `Parent`. But, when line 15 runs it prints out the `Child.override` messages because `son` is an instance of `Child` and child overrides that function by defining it's own version. Take a break right now and try playing with these two concepts before continuing. Alter Before Or After --------------------- The third way to use inheritance is a special case of overriding where you want to alter the behavior before or after you the parent class's version runs. You first override the function just like in the last example, but then you use a Python built-in function named `super` to get the `Parent` version to call. Here's the example of doing that so you can make sense of this description: .. literalinclude:: ex/ex44c.py :linenos: The important lines here are 9-11, where in the child I do the following when ``son.altered()`` is called: 1. Because I've overridden `Parent.altered` the `Child.altered` version runs, and line 9 executes like you'd expect. 2. In this case I want to do a before and after so after line 9, I want to use `super` to get the `Parent.altered` version. 3. On line 10 I call ``super(Child, self).altered()``, which is a lot like the `getattr` function you've used in the past, but it's aware of inheritance and will get the `Parent` class for you. You should be able to read this as "call `super` with arguments `Child` and `self`, then call the function `altered` on whatever it returns". 4. At this point, the `Parent.altered` version of the function runs, and that prints out the parent message. 5. Finally, this returns from the `Parent.altered` and the `Child.altered` function continues to print out the after message. If you then run this you should see this: .. literalinclude:: ex/ex44c.txt :linenos: All Three Combined ------------------ To demonstrate all of these, I have a final version that shows each kind of interaction from inheritance in one file: .. literalinclude:: ex/ex44d.py :linenos: Go through each line of this code, and write a comment explaining what that line does and whether it's an override or not. Then, run it and see that you get what you expected: .. literalinclude:: ex/ex44d.txt :linenos: The Reason For super() ====================== This should seem like commons sense, but then we get into trouble with a thing called Multiple Inheritance. Multiple Inheritance is when you define a class that inherits from one or *more* classes, like this: .. code-block:: python class SuperFun(Child, BadStuff): pass This is like saying, "Make a class named `SuperFun` that inherits from the classes `Child` and `BadStuff` at the same time." In this case, whenever you have implicit actions on any `SuperFun` instance, Python has to lookup the possible function in the class hierarchy for both `Child` and `BadStuff`, but it needs to do this in a consistent order. To do this Python uses something called "Method Resolution Order" (MRO) and an algorithm called C3 to get it straight. Because the MRO is complex, and a well defined algorithm is used, Python can't leave it to you to get it right. That'd be annoying wouldn't it? Instead, Python gives you the `super()` function which handles all of this for you in the places that you need the altering type of actions as I did in `Child.altered` above. With `super()` you don't have to worry about getting this right, and Python will find the right function for you. Using super() With __init__ --------------------------- The most common use of `super()` is actually in `__init__` functions in base classes. This is usually the only place where you need to do some things in a child, then complete the initialization in the parent. Here's a quick example of doing that in the `Child` from these examples: .. code-block:: python class Child(Parent): def __init__(self, stuff): self.stuff = stuff super(Child, self).__init__() This is pretty much the same as the `Child.altered` example above, except I'm setting some variables in the `__init__` before having the `Parent` initialize with its `Parent.__init__`. Composition =========== Inheritance is useful, but another way to do the exact same thing is just to *use* other classes and modules, rather than rely on implicit inheritance. If you look at the three ways to exploit inheritance, two of the three involve writing new code to replace or alter functionality. This can easily be replicated by just calling functions in a module. Here's an example of doing this: .. literalinclude:: ex/ex44e.py :linenos: In this code I'm not using the name `Parent`, since there is *not* a parent-child is-a relationship. This is a has-a relationship, where `Child` has-a `Other` that it uses to get its work done. When I run this I get the following output: .. literalinclude:: ex/ex44e.txt :linenos: You can see that most of the code in `Child` and `Other` is the same to accomplish the same thing. The only difference is that I had to define a `Child.implicit` function to do that one action. I could then ask myself if I need this `Other` to be a class, and could I just make it into a module named `other.py`? When To Use Inheritance Or Composition ======================================= The question of "inheritance vs. composition" comes down to an attempt to solve the problem of reusable code. You don't want to have duplicated code all over your code, since that's not clean and efficient. Inheritance solves this problem by creating a mechanism for you to have implied features in base classes. Composition solves this by giving you modules and the ability to simply call functions in other classes. If both solutions solve the problem of reuse, then which one is appropriate in which situations? The answer is incredibly subjective, but I'll give you my three guidelines for when to do which: 1. Avoid multiple inheritance at all costs, as it's too complex to be useful reliably. If you're stuck with it, then be prepared to know the class hierarchy and spend time finding where everything is coming from. 2. Use composition to package up code into modules that is used in many different unrelated places and situations. 3. Use inheritance only when there are clearly related reusable pieces of code that fit under a single common concept, or if you have to because of something you're using. However, do not be a slave to these rules. The thing to remember about object oriented programming is that it is entirely a social convention programmers have created to package and share code. Because it's a social convention, but one that's codified in Python, you may be forced to avoid these rules because of the people you work with. In that case, find out how they use things and then just adapt to the situation. Extra Credit ============ There is only one extra credit for this exercise because it is a big exercise. Go and read this http://www.python.org/dev/peps/pep-0008/ and start trying to use it in your code. You'll notice that some of it is different from what you've been learning in this book, but now you should be able to understand their recommendations and use them in your own code. The rest of the code in this book may or may not follow these guidelines depending on if it makes the code more confusing. I suggest you also do this, as comprehension is more important than impressing everyone with you knowledge of esoteric style rules. Frequently Asked Questions ========================== How do I get better at solving problems that I haven't seen before? The only way to get better at solving problems is to solve as many problems as you can *by yourself*. Typically people hit a difficult problem and then rush out to find an answer. This is fine when you have to get things done, but if you have the time to solve it yourself, then take that time. Stop and bang your head against the problem for as long as possible, trying every possible thing, until you solve it or give up. After that the answers you find will be more satisfying and you'll eventually get better at solving problems. Aren't objects just copies of classes? In some languages (like JavaScript) that is true. These are called prototype languages and there's not many differences between objects and classes other than usage. In Python however classes act as templates that "mint" new objects, similar to how coins were minted using a die (template).