Ok, Object Oriented Programming (OOP)is not exactly new, and there are lots of pages on OOP on the web. This page comes from a background of both Python and Kotlin, and how improved programming styles offered by Kotlin can change how you program. Although Kotlin automates newer techniques and provides assistance coding, you can ‘backport’ many of the ideas to also improve Python Code.
First a recap of the basics, then how OOP is evolving and how the newer Kotlin enables newer thinking that is more difficult to embrace while only coding with Python.
- Recap – The Basics: Concept
- Basics Recap: The four pillars
- What has changed with OOP
- OOP and Python
- OOP and Kotlin
Recap – The Concept
The first programs only dealt with numbers. Then, the letters were mapped to numbers and then, even though computers were still dealing with numbers, they could appear to dealing with letters and text. It turns out anything can be represented as set of numbers, the number of ‘types’ of data expanded. This created the complication that when operating on data, ‘add’ or ‘print’ needs to execute different code depending on the data type. OOP represents a breakthrough in that the program can define its own types, and an number of operations for each type, allowing the possibility of any real world ‘object’ to be represented within a program. At least in theory anyway.
Basics Recap: The four pillars of OOP
There are four traditional pillars of OOP.
Strangely, a web search for a good explanation of abstraction returned several answers that confused abstraction and encapsulation. These confusion was to describe abstraction as hiding the details inside the class, which is not abstraction, but encapsulation.
Abstraction is to define what an object is in an abstract manner, so the details are not needed by code that uses the class. For example, consider a duck. A duck quacks, waddles and flies… and what else? Well, each duck has size for example. At an abstract level, exactly how it does these things is immaterial. To be abstract perhaps ‘waddle’ is too prescriptive, and is how a duck walks, so at abstract level, the duck is walking. The idea is to reduce the ‘attributes’ of a duck to the simplest form, and focus only on those that are relevant.
Another example is a automatic coffee machine. There can be a button that grinds beans, tamps the ground coffee, heats water to the ideal temperature and by pressure forces the heated water through the grinds, then froths milk and adds it to coffee, all but the one button. At an abstract level we describe this button as ‘cappuccino’, and all the step are irrelevant, because at an operation level we press a button and get a cappuccino. We don’t care how it works, as long as it makes a cappuccino. The interface is ‘press button” -> get Cappuccino. How the machine does that does not affect the operation, and keeping and allow a person using the machine to bother with how it works is abstracting the interface. An new machine with different internal workings could keep the same interface, and there would be no need to change how we use the machine. This is because the operation was kept abstract.
We have our abstract concepts of the attributes of object. Encapsulation is ensuring all the details of how this is achieved is kept internal to the object. Think medicine inside a capsule. We see the outside of the capsule, not what is inside unless the capsule breaks! For our coffee machine, encapsulation means that if cappuccino is a function of the machine, then using the machine should not require understanding what steps are in the process. With software, the idea is the internal steps can change if they remain internal. If a separate ‘grind’ button is offered, then it will need to always work the same way, but if ‘grind’ is only a step within ‘cappuccino’ we don’t have to grind the coffee ourselves with a separate control if . If the machine does not have a button for the operator to do their own grinding, the how the grinding is activated can change during production and the instructions will not need changing as this become an internal change. Encapsulation means the instructions to use the class, and thus code that uses the class, will not need changing as the internals of the class are updated.
We had a class of object as ‘duck’. This allows for having any number of objects of the duck class. Lots of ducks. Now consider these are either robot ducks, or 3D animations, and we want to have them quack and move. Our duck has waddle, fly and quack methods and each duck can have a size. So we can have ducks the quack and move in two ways. But now consider if we also want other animals? Perhaps we want a dog that could chase the ducks? The dog does not waddle, fly or quack so the logic to control the dog must be completely different…. unless….. what if we redefine our duck interface as move(speed) talk(howLoud). Moving below a certain speed for the duck would be waddling and above the threshold flying. Talking for the duck is quacking .The dog moves by walking or running depending on speed, and bark to talk. Now we have a common interface, and ‘polymorphism’ as we can morph both duck and dog to the one interface.
Polymorphism described a common interface for Duck and Dog which we could call ‘Animal’. With Inheritance, a base class of ‘Animal’ is declared, then Duck and Dog classes would inherit from the base class. A class that inherits is also called a ‘subclass’ as it can be said that ‘Duck’ is a subclass of ‘Animal’. Animal would have methods for move(speed) and talk(howLoud) as well defining the property ‘size’. The two methods would not need an implementation as the idea is that ‘Animal’ as a class is designed only for subclassing, called an ‘abstract class’, and the methods for Duck and those for Dog will likely have little in common, so an implementation in the base class would not be very useful.
Duck could also be subclassed to ‘SpottledWhistlingDuck’ and other species of Duck. The SpottedWhistling could use common code for move (both fly and waddle), but may need a very different ‘talk’.
What has Changed with OOP.
So how has OOP changed over time? One of the most significant ways is ‘composition over inheritance‘, which basically means that most often, the role of inheritance should actually be best achieved by ‘composition’, which was not one of the original four principles. Multiple inheritance is now particularly frowned upon, and where possible, composition is considered the preferred way to code, not even single inheritance. There are many articles on composition over inheritance online, and here is one specific to python, and the author astutely uses ‘ducks’ in his examples. Further reading encouraged if this is new to you.
OOP in Python.
Python allows almost everything you could want in OOP, but there are many things you have to do yourself without actual language support.
- All data in python is an object
- inheritance: single and multiple inheritance support
- visibility modifiers: unusual, but effective
- abstract classes: no Python language support, and no support while coding but these can be implemented to work at runtime, but it is tedious
from abc import ABC, abstractmethod
- this works at run time only, errors are detected only when and if an object is instantiated in testing
- interfaces: no real support, but the result can be achieved, although all checking will be at run time, and requires additional unit tests to be safe code
- delegates: again, no real language support, but that doesn’t stop python having delegates, it just makes the code a little less clean and means there is no IDE tool support when coding
OOP in Kotlin
Kotlin not only allows almost everything you could want in OOP, but gives language level support, delivering compile time checks that eliminate errors possible with python, enables tool support when coding, and provides a guide.
- All data in Kotlin is an object
- inheritance: single inheritance only, multiple inheritance by interface(as generally recommended by coding guides)
- visibility modifiers: full extensive language support
- abstract classes: fully supported at language level
- interfaces: fully supported at language level
- delegates: fully supported at language level
While almost everything can be done with Python, visibility control, abstract classes, interfaces and delegates, which are all key OOP tools, are supported at language level with Kotlin. As the examples on the Interfaces and Delegation page show, such techniques as dependency injection and ‘composition over inheritance’ are far easier to code and master in Kotlin that in Python. Perhaps learn, and perfect, such techniques in Kotlin, then backport to the knowledge to your Python code.