Classes and Sub-classes
Birds can be divided into species, they can also be grouped into higher level collections such as raptors (e.g. birds of prey) and passerines (perching birds) based on common evolutionary descent and shared morphology. We are psychologically primed to make groupings of objects in the World based on common features. It helps us the make sense of what we see around us and simplifying what we need to know about each thing we encounter. A starling will have its own peculiarities of form and behaviour, but we also know that it will have wings because all birds have wings (even if some of them are now too small to work for flying).
We begin to see the advantages of the OO approach for modelling the World when, perhaps, we introduce a number of different types of birds into our system each with different behaviours (perhaps by adding a hawk hunting). Our non-OO program will now starts to look more complex, because we have to provide a complete and separate set of programming for each type of bird and use lots of conditional statements to give the right variant of behaviours. The more types of bird we introduce the more complex it becomes.
Object oriented programs rely on the use of classes to specify the behaviour of all instances (objects) of that class. They also provide a mechanism of inheritance. We can define a generic "Bird" class (with associated position and velocity attributes), then also define Starling and Hawk sub-classes. If effect we assert something like "A Starling is-a Bird", "Hawk is-a Bird". They all inherit the Bird attributes (position, velocity) from the Bird super-class and the generic Bird behaviours, but each sub-class can now be given additional attributes and perhaps different variants of specific behaviour. When used wisely and correctly these techniques can lead to a considerable conceptual simplification in the structure of programs. Human readers of the code can see more easily that they are "obviously right" because there is a natural concordance with the way we want to understand the World. Classes have a close connection with the use of abstraction in understanding problems because they can be deployed to make a direct relationship between the abstractions we have recognised in the World and in our design, and the programming code that we actually write. Since it is humans who make the design mistakes this concordance is an important way of avoiding programming errors.
I would like to emphasise that point: the best way of keeping errors out of programs is by making the program as simple as possible and as easy to understand as possible. Make the way it works transparently obvious.
It is possible to make object oriented programs look unnecessarily complex and that leads to lack of understanding by the programmer and a whole new ecosystem of errors that feed on one another. When OO methods are badly used they can give the illusion of a conceptual simplicity which is not actually there. In my own experience, human programmers can cope with two or three levels of inheritance - after that things get difficult because the behaviour of an object that you are actually using may be defined in several different places and it can be hard to keep track of the interactions between the several different levels of definition. Professionally trained software engineers can deploy systematic techniques that help them control complexity and maintain conceptual integrity. For us the best rule is "keep it as simple as possible". Of course, when we are dealing with large systems that do real jobs of work in the World, things get much more complicated. On this scale, keeping things as simple as possible takes a great deal of professional skill, knowledge and experience, and involves correctly deploying a whole range of sophisticated design and programming techniques.