Programmer's Python - Class & Type |
Written by Mike James | ||||
Monday, 08 February 2021 | ||||
Page 3 of 3
Hierarchical TypingThings are a little more complicated in most class-based languages that implement hierarchical type. In this case inheritance can be used to create a type hierarchy. For example: Class MyClassB:inherits MyClassA{ Now MyClassB has all of the properties of MyClassA plus whatever is added as part of its definition. MyClassB is said to be a subclass of MyClassA and as it has all of the properties of MyClassA you can use it anywhere you could use an instance of MyClassA. After all, typing is all about making sure that an object has the properties that you are using, and an instance of MyClassB has all of the properties of MyClassA and so it can be treated as such. MyClassB is also a subtype of MyClassA in the sense that it is also a MyClassA as well as being a new type all of its own. So it is perfectly OK in most strongly-typed class-based languages to write things like: MyClassA myObject=new MyClassB(); and then proceed to use any properties that belong to MyClassA. So the rule has now become – a variable can reference an object of its declared type or any subtype. If you make a mistake and try to use a property that MyClassA doesn't have, then the compiler will tell you about the error at compile time and you are saved a runtime error. Why is hierarchical typing useful? It is useful because it allows you to write partially generic methods. For example, suppose you have a class hierarchy Animal and two subclasses Cat and Dog. As long as you only want to use the methods and properties of Animal you can use hierarchical typing to write a method that can accept Animal as its parameter and use it with objects of type Animal, Cat or Dog. When you write a method that works with a type – it also works with all of its subtypes. Most languages have a single topmost supertype that all other types derive from – usually called Object or something similar. You can use this to write completely generic methods because a variable of type Object can reference anything. However, notice that because of strong typing the only methods that can be used are those that Object has. Inheritance as the ModelWhy do we use inheritance at all, and why is it related to the idea of subtype? This is a complicated question and one that causes most arguments. We have already looked at these ideas in Chapters 11 and 12 but they also relate to the concept of type. The original idea of using objects was to model the real world. In the real world things are objects with properties and even methods that allow the object to do something. Introducing objects into programming was to make it more like an exercise in simulation. Indeed the first object-oriented language was Simula, a language for simulation. The idea is that in the real world objects are related to one another. The problem is that they are related in complex ways. As we have already stated, in programming the most basic intent of inheritance is to allow code reuse. Code reuse doesn’t have much to say about any type relationships. It is tempting to take the next step and to say that if objectB inherits from objectA then it is an objectA as well as being an objectB. A square is a square but it is also a rectangle, say. The Liskov substitution principle is the best known embodiment of this idea. This says that anywhere you can use an instance of a class, you can use an instance of any subclass. As you can use it in place of the base class, it has to be a subtype of the base class. The reasoning is that the subclass has all of the methods of the base class. This is often true but it isn’t always true. For example, by our previous reasoning a square is a subclass of a rectangle but you can’t use a square everywhere you can use a rectangle. The reason is that you cannot set the sides of a square to different values. There is a restriction on a rectangle to make a square. Restrictions and specializations spoil the neat idea that subtypes can be used in place of their supertype. What this means is that the Liskov substitution principle is more a theoretical simplification than a reflection of the world. This also makes strong typing an arbitrary theoretical decision when it come to rules for how class instances can be used. You can find ways to make subclasses always work as subtypes. For example if you implement a square as a rectangle that still has two sides specified, you can retain all of the rectangle methods and enforce the equality some other way. This is far from natural. There is also the problem that in the real world objects are related to multiple other objects. A square is a special case of a rectangle and it is an n-sided equilateral shape. You could try to model this by using Python’s multiple inheritance but this is usually much more difficult than it seems when you first start out on the enterprise. This is the reason most other languages restrict themselves to single inheritance. Other languages add to classes the idea of interfaces – essentially class declarations with no implementation. This allows for a limited form of multiple inheritance but does nothing for code reuse, forcing programmers to return to copy-and-paste code reuse. The problem is that the real world is often not modeled well by a single inheritance hierarchy, whether used with or without strong typing. This is the main reason that you will hear advice such as “prefer composition over inheritance”. The idea that one object contains another object is in many ways an easier concept to work with. So, for example, a car object might contain a steering wheel object and four road wheel objects which in turn contain wheel objects. However, this doesn’t always fit, how does a square contain a rectangle object, and current languages provide poor support for composition. In chapter but not in this extract
Summary
Programmer's Python
|
||||
Last Updated ( Monday, 08 February 2021 ) |