Programming Concepts: Object-oriented programming (OOP)
Where Procedure-oriented programming uses procedures to make code easier to write and understand, Object-oriented programming (OOP) goes a step further and uses objects to make code easier to create and work with.
In OOP, the word "object" has special meaning: objects are defined as a specific way of organizing source code. Briefly, an Object is the combination of:
- Some data (i.e. variables) that are related to each other
- Procedures specifically designed to work with that data
(##Add picture and an example##)
- A-level Computing/AQA/Paper 1/Fundamentals of programming/OOP Examples of key elements
- A-level Computing/AQA/Paper 1/Fundamentals of programming/OOP Examples of key features
- A-level Computing/AQA/Paper 1/Fundamentals of programming/OOP Techniques
If a program only requires a few lines of source code to write down, there is no advantage to using this technique. Procedure-oriented and Object-oriented programming were invented because programs were getting longer and longer, and were difficult to work with. Programmers needed more structure to simplify the programming process.
A program of moderate size and complexity can be simplified using procedures. With especially large or complex programs, procedures are not enough; OOP became popular as a way of handling these very complex programs.
A program may be complex in the raw source code - many lines of code, many procedures. Or it may be complex in the way it's written - many human authors, many interactions between different parts. OOP helps with both.
We will look at the detailed reasons for and against once we've covered the key concepts.
Key elements of OOP
There are four key elements when designing or writing a program using OOP:
In OOP, an Object is compound data: it combines other things together into a single bundle. As noted above, what makes the Object unique is that it combines data (e.g. integers, strings, references) with code (i.e. procedures).
In most languages, Objects do not exist in source code: you cannot directly "write down" an object. Instead you must create an Object piece-by-piece, filling-in the individual bits of data and code.
It is important to remember that not only does the Object contain data, but it is also data itself. Once the program starts running, you can freely change the value of any piece of data inside the Object, and in most languages you can also change the elements (data, code) of the Object itself.
To use any value (the number 7, the word Apple) we need a data-type. Numbers have a data-type e.g. "integer", and text has a data-type e.g. "string".
To define Objects and work with them in source-code, we need a data-type. Classes are the data-type for Objects. Each Class defines the specific set of data and the specific procedures that will make a particular Object.
Classes as templates
Because Objects are complex, with many different variables and procedures inside them, Classes are more complex than normal data-types. Most OOP languages give additional features to Classes to help us use them and create them.
Not only does a Class define a data-type for an Object, but it usually defines default values for new Objects. This allows us to use Classes like a template, or biscuit-cutter: a single Class can stamp-out many identical Objects. The Class can even make small changes to each Object as it is created, as we'll see later.
Since Objects are themselves data, each Object can be separately modified after it's created by simon , even though they start out similar or the same.
Procedures (and Functions) can be created anywhere, and used by any code at any time. In OOP, we want to restrict some Procedures (and Functions) so that they can only act upon the data inside the Object they are attached to. These restricted versions have a special name: Methods.
Every Method is a Procedure, with two special features:
- We can restrict which source-code is allowed to access the Method, based on the Object the Method is attached to, and the Class that created that Object
- Methods have access to a special variable that is the Object and/or Class they are attached to. Depending on language, this variable is usually named "self", "this" or "Me" ("self" is used in Delphi/Pascal and (conventionally) in Python, "this" is from Java and C#, "Me" is from VB.Net).
Data can be created anywhere, and used by any code at any time. In OOP, we want to restrict some data so that it can only be acted-upon by the Methods in the same Object. These restricted variables have a special name: Attributes.
Every Attribute is a Variable, with three special features:
- The Class that creates the Object usually provides a default or starting value for each Attribute
- Attributes can optionally be hidden from other code
- Attributes can optionally be made visible to other code, but made uneditable (only the code (Methods) in the Attribute's Object can edit the Attribute)
NB: Attributes are further split into two types: Fields and Properties. All Fields and all Properties are Attributes, but with extra specialization. See the section on Encapsulation for the precise differences.
Key features of OOP
OOP imposes additional structure on programs. We have introduced two new structural features: Objects, and Classes. We have also seen how ordinary Variables and Procedures are given extra rules, and renamed as Attributes and Methods. OOP languages often have ordinary Variables and Procedures too, but mostly we work with the Object-specific versions; this is what we mean by "Object oriented" - we are actively using Objects in our program design and implementation.
This additional structure allows us to do new things in programming that weren't possible with ordinary Procedure-oriented programming. In later sections, we will see how these can help building larger and more complicated programs, while reducing the number of bugs and making them easier to work with.
Instantiation and Constructors
We have two new structuring concepts: Classes, and Objects. How do we create them?
The Class is a new data-type, so we specify it in source code. Typically, we create a new source file and name it the same as we want to name our Class. So, a "Car" class might be described by a source file called "Car.src" (in Java: "Car.java", in Python: "Car.py", etc).
But how do we create new Objects - how do we use our Class as a template to create many Objects for us?
This process is called Instantiation: when the program is running, we invoke a special Method (i.e. Procedure) within the Class that Instantiaties a new Object. (The object created is referred to as an Instance of the class) These special Methods are the same as ordinary Methods, but in some languages they have different syntax, or additional tags, to make clear they are intended for creating new Objects.
Because these Methods have a special purpose, and to make it easier to talk about them without getting confused with the general Methods on the Object, we have a special name for Methods that can Instantiate new Objects. These Methods are called Constructors.
- A Constructor is a kind of Method. It has the extra feature that it creates (instantiates) new Objects.
- A Method is a kind of Procedure. It has the extra rule that it must be part of a Class and/or Object.
- A Procedure in OOP is the same as a Procedure in non-OOP languages.
With Procedure-oriented programming, we could use any Procedure at any time from any place. In OOP, if a Procedure is a Method, we've restricted when and how it can be used. It has direct access to the Attributes of its own object, but no others. This is useful in that it simplifies the Method, but it can be problematic too.
As a programmer, you quickly meet a practical problem: a lot of the time, you want to implement a similar Method with the data from different Objects.
With Procedures, there is no such problem: if the types of the arguments match the Procedure's required parameters, you can provide any arguments you like. But an instance of Class A cannot be used as an argument to a Procedure that expects an instance of Class B.
This would be disastrous for OOP: we would need to endlessly copy/paste Methods from one Class to another, tweaking the parameter-types, instead of re-using the code. This would be a step backwards in ease of programming and reduction of bugs.
Instead, OOP languages introduce a new feature: Polymorphism( or 'many forms' ; the ability to present the same interface for differing underlying forms.)
Subtype Polymorphism allows methods to be shared between objects of similar type; it occurs when different classes implement methods with the same name, and return the required values, calculated appropriately for the class in question. For example, theShape.area() would invoke a method from the class of which theShape is an instance, which would be different methods when theShape was an instance of a polygon or an elipse. Subtype refers to a heirarchy of types, where both elipse and polygon are subtypes of a supertype Shape. The method used may be selected at run time (in some languages), so that the code which invokes theShape.area() need not know what subtype theShape belongs to, so long as it provides a method area .
In practice, the simple version of Polymorphism above would not save much programmer time, if we end up writing different versions of a similar method for each type.
To further fix this problem, OOP languages use Inheritance (also called Subclassing, if types are implemented as classes).
When one Class Inherits from another Class, it adopts the type of the other Class, and adopts all the Methods and Attributes. The new Class can be treated as if it is the old Class - Procedures don't need to know whether they are seeing a new Class or an old one.
The original Class is usually called a superclass and the new (inheriting) Class is called a subclass.
Crucially, the subclass is allowed to add additional Methods and Attributes on top of the ones it inherited. Because these new features extend the behaviour of the superclass, we usually say that a subclass "extends" the superclass. In some OOP languages, the syntax for making a subclass is to use a keyword "extends", along with the superclass you wish to inherit from.
For example, the Shape class may define variables and methods common to any shape (eg number of verticees, colour, position) which are inherited by subclasses, which then access the same code, where appropriate, while providing subclass specific methods for area - see Overriding below.
However, this comes at a price - changes in the superclass may produce unwanted side effects in some subclasses.
Inheritance (aka Subclassing) and Polymorphism largely solve the issues caused by Methods and Attributes being more restricted than plain Procedures and Variables. But we quickly discover that not only do we want to extend a superclass (i.e. add to its existing Methods/Attributes), but we want to modify its existing behaviour.
This is so useful that most OOP languages support it as core feature, and this is called Overriding.
When a subclass extends a superclass, it can optionally replace any of the superclass's Methods with new, customized versions. This act of replacing is Overriding, and the old version is described as overridden.. The replacement method in the subclass may (but need not) invoke the overridden method of the superclass, as well as carrying out additional operations required by the subclass.
Most OO languages allow redefinition of the standard arithmetic operators (+,-,*,/,...) so they can be applied to members of a class, or to a class and a standard variable type ( real number, integer,..). As with polymorphism, the appropriate method (which must be part of the class definition) may be selected at run time. For example, a class Complex which represents complex numbers, could implement the standard arithmentical operations for its instances. This is described as ad-hoc polymorphism.