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
[edit | edit source]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.
To recap:
- 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.
Example: Instantiation 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 as integer = 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:
Code Output
pumping gas! |
Exercise: Instantiating a car
Write your own instantiation for a beetle car:
Answer: dim beetle as new car 'instantiation
What would the following output: dim ka as new car
dim montego as new car
ka.refuel(10) 'assuming fuel starts at 0
montego.refuel(50) 'assuming fuel starts at 0
montego.drive()
montego.drive()
for x = 0 to 10
montego.drive()
next
ka.refuel(10)
console.writeline("ka: " & ka.getFuel())
console.writeline("montego: " & montego.getFuel())
Answer:
Code Output
pumping gas! |
Polymorphism
[edit | edit source]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.
Example: Polymorphism 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.
Ad-hoc Polymorphism
[edit | edit source]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.
Subtype polymorphism
[edit | edit source]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).
Example: Polymorphism 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
|
Exercise: Polymorphism
In polymorphism what is the keyword we use to redefine a subroutine:
Answer: overrides
Write a definition of a limo class that uses polymorphism that makes sure that the maxSpeed never goes beyond 100:
Answer: 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
Write a definition of musclecar that inherits car, but uses 30 units of fuel per drive and displays "vroom vroom!" every time it drives. It should also store details on leatherSeating, allowing you to interface with this.
Answer: 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
Describe polymorphism
Answer: Polymorphism allows you to inherit attributes from a parent class, but redefine some of the methods or attributes |
Inheritance
[edit | edit source]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.
Example: Inheritance 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. |
Example: Inheritance 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.
Exercise: Inheritance
Declare a new class called limo that has attributes numSeats and colourSeats; and the ability to interface with them
Answer: 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
What are the benefits of using inheritance?
Answer: 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. |
Exercise: Inheritance 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 function
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
|
Overriding
[edit | edit source]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.
Overloading
[edit | edit source]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.