Key features of Object-oriented programming (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.
dim polo as new car 'instantiation 1 dim escort as new car 'instantiation 2
The code above creates an object called polo and escort, both of class type car (which we declared earlier). We can now use all the public attributes and methods:
polo.refuel(100) 'assuming fuel starts at 0 polo.drive() escort.refuel(50) 'assuming fuel starts at 0 escort.drive() for x = 1 to 20 escort.drive() polo.drive() next polo.refuel(10) console.writeline("polo: " & polo.getFuel()) console.writeline("escort: " & escort.getFuel())
This would output the following:
dim beetle as new car 'instantiation
In Procedure-oriented programming, data available to one method is generally available to all methods. This can be manually restricted, e.g. using private/protected/etc, see Encapsulation [link needed here], but this is optional. Usually the only limit is that the data provided to each Procedure must match specific A-level Computing/AQA/Paper 1/Fundamentals of programming/Data types. When defining the Procedure, we choose which datatypes it will accept (i.e. we restrict the Parameters).
EXAMPLE: ---datatypes: 2 procedures eg int int and string int
When someone tries to use the Procedure, the computer looks at the data provided, and compares the datatypes to ....( so long as the types of data they provide exactly match the types we choose, the Procedure will execute.)
In Object-oriented programming, all data is encapsulated by default [for multiple reasons - link needed]. Unlike Procedure-oriented programming, where any Procedure can access any data anywhere in the program, Methods can only directly access data in their own object. To access the data within a different object, we must pass the whole object to any Methods (or Procedures) that need to act on that data.
This creates a problem: the datatype of the parameter will now be the type of the object's class, and each class is a unique datatype. For a simple case, this works fine, but for larger problems it prevents us re-using Methods. 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.
For example, if we have a Pet class with a Year_of_Birth variable, and an Owner class that also has a Year_of_Birth variable, and we have a method that calculates current Age from a Year_of_Birth ... it will either work on Pet classes, or on Owner classes, but it cannot work on both.
To solve this, OOP languages have an essential feature called Polymorphism. There are many kinds of Polymorphism, but most of the time we only use two of them. The shared concept is that one thing can pretend to be multiple things.
The simplest form of Polymorphism is w:Ad hoc polymorphism when the programmer writes multiple different versions of the Procedure: e.g. one that accepts Objects of type A, and one that accepts Objects of type B. Both versions have the same name, and the OOP language knows to treat them as if they are the same, but to intelligently use one or the other depending on how the Procedure is invoked at runtime.
The more powerful form, used heavily in OOP, is w:Subtyping. With subtype polymorphism, the programmer links different datatypes to each other, promising the computer that - in some way - those datatypes can be used interchangeably. The mechanism for this is Inheritance (see below).
Consider our car example again. When we created the
public sub refuel(byVal x as integer) as integer console.writeline("pumping gas!") fuel = fuel + x end sub
Well this just won't do. We are creating an electric car and we don't want to say that we are pumping gas; what would our sandal-socked yoghurt eating friends say! So for the
class electricCar inherits car private numBatteries as integer public sub setnumBatteries(byVal n as integer) numBatteries = n end sub public function getnumBatteries() as integer 'interface return numBatteries end sub '###### overrides morphs the inherited version, replacing it public overrides sub refuel(byVal x as integer) console.writeline("pure renewable energy!") fuel = fuel + x end sub '###### end class
class limo inherits car public overrides sub setSpeed(byVal s as integer) if s > 100 then maxSpeed = 100 else maxSpeed = s end if end sub end class
class musclecar inherits car private leatherSeating as boolean public overrides sub drive() fuel = fuel - 30 console.writeline("vroom vroom!") end sub public sub setLeather(s as boolean) leatherSeating = s end sub public function getLeather() return leatherSeating end function end class
Polymorphism allows you to inherit attributes from a parent class, but redefine some of the methods or attributes
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 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 vertices, 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.
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 ellipse.
Subtype refers to a hierarchy of types, where both ellipse 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 .
However, this comes at a price - changes in the superclass may produce unwanted side effects in some subclasses.
class limo inherits car 'declares what attributes and methods you are inheriting private numSeats as integer 'must be private private colourSeats as integer 'must be private public sub setnumSeats(byVal s as integer) 'interface set numSeats = s end sub public function getnumSeats() 'interface get return numSeats end function public sub setcolourSeats(byVal c as string) 'interface set colourSeats = c end sub public function getcolourSeats() 'interface get return colourSeats end function end class
Creating new classes from parent classes is very quick and easy. It allows for a modular approach to creating classes, where you might never use the base class at all.
Building on the car example above, what would happen if we wanted to declare an electric car? Well we'd probably want to store some information on the number of batteries that it has:
class electricCar private maxSpeed as integer private fuel as integer 'fixed! private numBatteries as integer 'added public sub setnumBatteries(byVal n as integer) numBatteries = n end sub public function getnumBatteries() return numBatteries end sub public sub setSpeed(byVal s as integer) maxSpeed = s end sub public function getSpeed() as integer return maxSpeed end function public sub refuel(byVal x as integer) as integer '..... 'HOLD ON! end class
This seems like a very long and tedious task rewriting all the same code again. You're right! It would be far better if we only had to declare all the new stuff we wanted to add. OOP allows for inheritance, where a new class can inherit the attributes and methods of a parent class:
class electricCar inherits car 'declares what attributes and methods you are inheriting private numBatteries as integer 'added public sub setnumBatteries(byVal n as integer) 'interface numBatteries = n end sub public function getnumBatteries() 'interface return numBatteries end sub end class
This means that everything that car declared is now accessible from electricCar, as well as the new numBatteries attribute and methods. Let's instantiate this example and see what's possible
dim gwiz as new electricCar gwiz.setnumBatteries(6) 'from electricCar gwiz.setSpeed(60) 'from car gwiz.drive() 'from car
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.
Technically, Overloading is a kind of ad-hoc polymorphism, but it is so widely used that it has its own name.