artiface/artiface/ ABriefOverviewOfObjectOrientedProgramming
A Brief Overview of Object Oriented Programming
What is Object Oriented Programming
Object Oriented Programming (OOP) is an approach to programming which revolves around the concept of objects. An object, in this context, contains both data and code. Code outside an object has limited access to the data contained in an object. Usually an object is an instance of a class, in the sense that Podger is a Cat and Rover is a Dog. Then classes usually offer what is called inheritance, in which in our example, Cat’s and Dog’s are both Animal’s. In turn, both Animal’s and Tree’s are Lifeforms, but a Tree is not an Animal. Inheritance then allows for code reuse, for example all Animal’s need to eat, and hence the code for eating can be implemented once in Animal, rather than once for each subclass of Animal.
Many languages, such as C++, offer a degree of support for OOP; some such as Java force the paradigm onto programmers. Others, such as C, do not offer direct support for OOP, but in these cases OOP can be practised to a degree, utilising structures for an object instance’s data, and naming functions around the structures they operate on.
The main features we see in OOP are:
- Encapsulation: objects contain their data, and control access to it;
- Modularity: code for manipulating an object is contained in one place;
- Inheritance: code from a superclass, e.g. Animal, is inherited by all subclasses, such as Cat, meaning that it needs to be written only once, for the Animal class, and then all subclasses such as Cat and Dog get it for free.
- Polymorphism: We can program in a way where the class of an object doesn’t need to be fully known at runtime. For example we know that pet is an Animal, but whether it says meow or woof is determined at runtime. A small number of languages supporting OOP are not built around the concept of classes. The first main example of this was called Self, and in its earlier incarnations, Javascript did not have the concept of classes (and indeed classes in Javascript are more of a form of syntactic sugar added late in the languages life).
In OOP, programs are designed around breaking a problem into small constituent parts, each of which can be modelled as an object, and in which the overall design is of well-defined objects interacting.
Breaking Down Into Objects
Consider an everyday activity with our pets. We stroke them, we feed them, and we may want to take them for a walk if, for example, they are a dog. This latter example could be accomplished using what is known as polymorphism. For each Animal, we could have a means of enquiring as to whether or not the animal requires feeding, or indeed whether or not it is a pet, and whether or not we own that pet. This last case means we need a notion of a pet’s owner, and that owner is a Person. Here we hit the kind of design issues one sees with languages such as Java which have single inheritance, in the sense that any class has at most one, or exactly one direct ancestor. We shall illustrate how things are done in Java. In Java, every class ultimately descends from the Object class. The Object class is a special class provided by the language. If we declare
class Animal {
…
}
Then Animal will be a subclass of Object. If we declare
class Animal extends Creature { ...
Then an instance of Animal inherits all methods and data declared in class Creature. This leads to needing to think in terms of ‘is a’. For example, a PetOwner is a Person, but a Person needn’t be a PetOwner. One is then led to the question of how, given a Person, we can enquire as to whether they are a PetOwner. Then for PetOwner, we may have additional information such as the Pet’s they own. So we arrive at a set of classes like the following:
class Creature {...}
class Animal extends Creature {...}
class Person {...}
class PetOwner extends Person {...}
class Pet extends Animal {...}
class Dog extends Pet {...}
It is here that we see issues we will face with singly inherited languages (though it must be said that multiple inheritance as in, for example, C++, comes with its own issues and difficulties). Consider a Dog: is it a Pet or not? Consider a feral dog: clearly it is not a pet. So if Dog extends Pet, we have the problem that a feral dog does not fit our hierarchy. On the other hand, if Dog is not a Pet, then we have issues as to what pets a PetOwner owns. That needs to be an Animal rather than a Pet, and then do we have properties of Animal for whether or not it is a pet, and if so, who is its owner.
OOP Terminology
In most OOP languages, it is usual to talk of classes, instances of a class, member variables, and methods. In C++, methods are called member functions. In Java and Smalltalk, they are called methods. To use a method we invoke it, in some languages by calling it like a function (e.g. Java, Python), in others by sending messages (e.g. Smalltalk, Objective-C). In addition, some languages or frameworks have interfaces which declare what methods a class which implements that interface must provide.
In what follows we will talk of classes, instances, member variables, methods, interfaces and invocations.
OOP in Popular Languages
Languages such as C++, Java, Python and Ruby have built-in support for class-based object orientation. (Languages such as Javascript and Self support a form of object orientation which is is not class based, though in recent times Javascript has gained the ability to declare classes as one would in e.g. Java or Python.) In Java, we write:
class Animal extends Creature implements MaybePet {
public void stroke() {...}
}
In Python we write:
class Animal(Creature):
def stroke(self):
...
In C++ we write:
class Animal : public Creature {
public:
void stroke() {...}
For the same thing. In the Java case, we have what are known as interfaces, which are class-like entities which declare methods a class which implements that interface must provide. In the case of C++, both classes and interfaces are written in terms of the class facilities provided by the language, and in the case of C++, multiple inheritance is possible.
Other Programming Paradigms, Put Briefly
OOP, whilst widespread and popular, is not the only programming paradigm. Other popular paradigms are procedural and functional programming.
Procedural Programming
There are procedural languages such as C, Fortran or COBOL, which tend to be older. Procedural languages break down computation into procedures which often call other procedures. This allows a degree of factoring a program so as not to repeat code for identical or similar instructions.
At a low level, and in assembly languages, everything is essentially procedural, with only minimal support for procedures: higher level constructs such as loops are turned into arithmetic, comparison and jump instructions (e.g. decrease the value of the ECX register by 1, compare it to zero, and if greater than or equal jump back to the start of the loop). Languages such as C and assembly give programmers a great deal of control over how computations are accomplished, at the expense of requiring the programmer to specify such.
As a result of the low level control of such languages, they find themselves employed in the writing of operating systems. For example the Linux kernel is written in C.
Functional Programming
Functional programming has its roots in the mathematical notion of a function. A function’s inputs uniquely determine its outputs, and a function changes nothing external to itself in the evaluation of itself. Functional programming had its beginnings in languages such as Lisp, and in more recent times finds itself in languages such as Haskell. Now a language which was totally functional in this regard would be incapable of input and output, since for example, a function which gets a character the user pressed would have its output dependent upon what the user presses, rather than upon its inputs.
Such languages express computation in terms of operations such as map, reduce, and filter. The side-effect free nature of this permits much to be done in parallel, and indeed the map-reduce paradigm has in recent times been taken from its functional roots and applied more generally.
A feature of many modern languages, which has its roots in functional programming, is higher order functions, that is, functions may take functions as arguments, and may return functions. For example in Javascript we see things like
let capitalisedWords = words.map(word => word.toUpperCase())
where a function is passed to the map() method as an argument. We may also see, again in Javascript, something known in functional programming circles as currying:
const add = (x,y) => x+y
const addThree = (x) => add(x,3)
Proponents of functional programming will put forward the greater capacity to reason about the behaviour. On the other hand, proponents of object oriented programming will tend to point out that much of the functional style of programming can be applied when coding in object oriented languages, such as minimising the degree to which objects state is mutated.