Tutorial 16
ð§Đ
Object-Oriented Programming - OOP
9618 A-Level
Note that OOP is an A2 topic - it's not required for AS
Object-Oriented Programming (OOP) a programming paradigm that allows both attributes (data) and methods (behaviour - i.e. functions/procedures (modules)) to be stored as a single object. A class is a template, but that class will be instantiated (created) at runtime and can hence be different (i.e. contain different attribute values) from other instances of the same class
Before we begin, let's list some basic definitions of some concepts this tutorial will cover - these will be restated again with more details and with relevant examples later in this tutorial, but it may be useful to refer to this initial list throughout and at the end as a check for your understanding
- Class: a user-defined template used to model attributes (data) and methods (behaviour - i.e. class procedures/functions) about some aspect of a program (e.g. a game character) - a class in itself isn't executed, specific instances have to be instantiated (created)
- Attribute: a variable contained within a class, much like a record data structure contains attributes/properties/fields too
- Methods: the name for a class module (procedure/function) - this ability to have executable code as part of the class is where classes provide more functionality than a record
- Encapsulation: the concept that both attributes (data) and methods (behaviour) can be contained within a single class object - this makes sense, when you consider that to "encapusulate" means to "fully enclose" something (data and behaviour), as if in a capsule
- Constructor: this is the default method (and specifically, a constructor should be a procedure) that is called automatically when the class is instantiated - constructors must be defined as follows PUBLIC PROCEDURE NEW(...params)
- Instantiation: the act of creating a class instance (object) - e.g. we could describe the code bmw <- NEW Car("bmw", 2005) as "instantiating (creating) an instance of the Car class (and passing "bmw" and 2005 into its constructor)"
- Object: the name for an instance of a class - in the example above, the object would be the bmw - we could then access attributes/methods belonging to that object with dot . syntax, like when accessing record attributes
- Access: both attributes and methods can have an access level of either PRIVATE (accessible only from within the class) or PUBLIC - accessible from both within the class and outside, via the object - e.g. syntax like bmw.driveTo(50, 30). PUBLIC is the default access level, if none is specified (though it's best to)
- Getters: a method to return a (usually private) attribute from a class - getters can also be more advanced - e.g. calculating some new value based on other attributes/random values etc
- Setters: a method to assign to (set) a (presumably private) attribute value from outside the class - this has benefits including allowing validation and not setting the value if the validation fails
- Inheritance: the concept that a child class will have access to the methods and attributes that exist in any parent classes - there can be multiple levels of inheritance (e.g. class C could inherit from class B, which inherits from class A) and is achieved with the INHERITS keyword in pseudocode
- Polymorphism: the word literally means "many forms" or "many shapes" - hence this is when an inheriting class can either use the method defined in the super (parent) class or its overridden version of that method defined in either it or another parent class (if e.g. having 3 levels of inheritance where C inherits from B and B from A as mentioned above - if we try to access a method/attribute that doesn't exist in C, then first B, then A will be checked for a match (i.e. going up the class hierarchy from the leaf to the root class))
- Super: the name for the parent that a particular class inherits from - if a child class has overridden a method, we can still call the super class's method with the super keyword SUPER.MethodName()
Additionally, there is also a video tutorial that covers OOP for A2
1
Constructors & Instantiation
By itself, a class is just a template - we have to create specific instance(s) of classes to actually use them. This process of creating an instance is called instantiation and is achieved by the NEW keyword
A constructor is the default method called when we create an instance of a class - note how we have to declare the constructor inside that class with PUBLIC PROCEDURE NEW - we will look at access later in the tutorial, but briefly, PUBLIC means that attributes/methods can be accessed from both in and outside the class, while PRIVATE attributes or methods can only be accessed from inside the class
In the example below, we create 2 instances of the Person class (bill and ted). Currently, nothing much happens - we are not storing any any data (attributes) and don't have any behaviour (methods) associated with them
2
Attributes
Attributes are data (variables/arrays/records/sets/even other classes etc) that can be stored as part of the class object
A class with just attributes is effectively just a record
This helps with organisation - i.e. data can be grouped together and stored as one object, rather than, for example, many different arrays
Note how rather than using the DECLARE keyword, we instead use the access modifier (PUBLIC or PRIVATE) for declaration
You can see we also pass 2 attribute values as parameters to the constructor - I have prefixed these parameters with a "p" - e.g. "pName", so that the website is able to distinguish the parameter variable pName from the class attribute Name - in other languages, this is achieved with keywords like this or self if referring to the class attribute, rather than a parameter/global/local variable. As you can see in the code, the 2 arguments (parameter values) are assigned to the class attribute
Note that we don't have to assign to a class attribute from a parameter in the constructor - hunger for example is initialised with a default value 50 which we have chosen, while the attribute friend is currently uninitialised and can be set at a later point in the program
3
Methods
A method is a simply a function or procedure encapsulated (contained) within a class - a few things to note:
- Like attributes, methods need to be declared with an access keyword PUBLIC or PRIVATE
- As always, we use CALL to call a procedure, but not a function
- The constructor is a required method and should be a procedure with the identifier (name) NEW
- Methods can be called inside the class directly - e.g. CalculateAge() inside the OutputDetails() method
- Or, if they are public, they can be called from outside the class on the instance object with the dot notation - e.g. napolean.CalculateAge()
- You can also see CALL is used to call the OutputDetails() method on each of the 4 person instances
4
Access
As has been hinted at, there are two access modifiers that Cambridge pseudocode supports
- PUBLIC: attribute or method can be accessed from anywhere - this is the default if no access modifier is specified
- PRIVATE: attribute or method can be accessed only from inside the class
Note: the site also supports accessing private attributes from a subclass for convenience, without needing getters everywhere - most languages don't support this, so in paper 4, you will probably need to use a getter to access a private attribute defined in a class' ancestor (e.g. parent, grandparent class etc)
Below we can see a contrived example with both a public and private attribute and method - this code includes 2 access violations which will result in the following errors. We would have to remove these statements for the code to compile/execute
- Can't access private identifier "y" on class instance test
- Can't access private identifier "Multiply" on class instance test
For public, there is really just one advantage - convenience. You can access attributes/methods from anywhere, without having to create additional getters/setters (see section after this)
In contrast, the benefits of making attributes private include:
- Restricted access: other programmers can't access private attributes/methods from outside the class (e.g. different files) (unless they change them back to public), hence are less likely to modify important internal workings of a class that hence introduce in bugs
- Validation: we can add setters that include validation checks - for example, rather than allowing a programmer to set an email directly, you could have a setter method that first ensures the email is a valid format before assigning it to the class attribute, displaying an error if invalid
- Hides complexity: imagine you have a class to encode videos to a specific format - internally, the class might 100s of complex attributes/methods that would confuse other developers trying to use this class - instead, they could simply be presented with a simple public method that takes in the input filename & type, then the desired output type/filename/quality etc - all the complex encoding attributes/methods are kept private (hence don't show in IDE code suggestions), with the developer instead just seeing this simple public method
A common misconception is that making an attribute or method private means it is more secure - i.e. less easy to view/modify by malicious parties. This is NOT true - e.g. storing passwords or credit card details but making them private still wouldn't be secure, since the data is stored unencrypted in RAM regardless
- To an end user, they would be able to inspect RAM contents (data and code) with memory scanners/debuggers/disassemblers either way, hence can view/modify it regardless of it it's public or private
- For a programmer working on the project, they will have access to the source code, hence could just modify the private attributes/methods to be public, add output statements to display the private attributes etc
5
Getters & Setters
Related to the concept of access, we might not want a user to directly access or modify class attributes - that is where a getter comes in
Here are some use cases of getters:
- Read-only attributes - this can be achieved by making attributes private to disable direct modification, but still allowing a programmer to get the value of that attribute via a public getter - see the dateOfBirth example below, since we don't want to allow the person's date of birth to be changed
- Additional logic - we could have a more complex calculation in a getter - for example, suppose we only want the person's year of birth, rather than the whole date. Rather than copying and pasting the same code every time we want to do that, we can instead create a simple GetYear getter - this is a simple example, but for a more complex example, this elimination of code duplication means there will be less bugs, code can more easily be modified since it only has to be change in one place etc
- Logging - suppose we have personal data like emails - we might want to record each time the field was accessed, the staff who accessed it etc for attribution in the future (e.g. abnormal access amounts/misuse/data leaks etc)
- Debugging - it might be easier to set a breakpoint/print out additional data every time we try to access an attribute in order to help us find & fix bugs
Likewise, a setter is a method that allows us to set (assign) to an attribute - there are again a few use cases:
- Validation - suppose we are creating a game and a person's hunger can be within a range 0-100. Each hour, their hunger could increase by 5, while eating food could reduce the hunger by a given amount - their hunger should always be in the range 0-100 though. A range check could hence be performed inside the setter and the value could be clamped within this range
- Logging - as above, if we have important or personal data, we might want to store every time it changed. Many websites now record the date & time, IP address, browser details and more each time a password is updated, for example - to act as a record in future in case malicious modifications need to be investigated
- Debugging - as above, it might be easier to log e.g. the before/after value when setting, various other important data with the goal of making finding & fixing bugs easier
- Deferred/Lazy Initialisation - the friend instance might not exist at the time that particular person was created, hence we wouldn't be able to assign it in the constructor, but can assign it in later via a setter (though we could also assign to the attribute directly if it's public too)
Note: in some exams, you will be told either in sentence-form or via a class diagram whether to make an attribute public or private as well as what getters & setters to create - if not, it's often preferred/required in the mark scheme that attributes are private.
Tip: since this site is aimed at practice and in paper 3, you will never have to write more than 1 or 2 getters and setters max, the site hence requires you manually write them - however, for many modern IDEs, if you simply declare the attributes, you will then be able to have the IDE automatically generate the getters and setters for you. This is of course very useful if you have large numbers of attributes. In JetBrains IDEs (IntelliJ, Pycharm etc), this can be achieved by right clicking, selecting "generate" from the dropdown box, then choosing getters/setters, the constructor or whatever else you want
6
Inheritance, Super & Polymorphism
Some of the most important aspects of OOP are the following concepts:
- Inheritance: a hierarchical class structure can be created whereby the child class inherits (has access to) the methods and attributes in the parent (super) classes. The INHERITS keyword is used to define this in pseudocode, as seen in the example below. It's important to note that the first statement inside an inheriting class' constructor must be a call to the parent class' constructor with the code CALL SUPER.NEW(...)
- Super: this refers to the parent class that other classes might inherit from. If we specify SUPER, then it means refer to the method or attribute in the parent class, rather than in the current class
- Polymorphism: if you break down the word, "poly" means "many" and "morph" means "shape" or "form" - generally, you can see that means a class can have multiple behaviours, or, more specifically, it is when a child class overrides the behaviour of the super class (yet the programmer has access to both - e.g. in the example below, calling OutputDetails() in either the Teacher or Footballer class will call the method in those classes respectively (which provide more information, related to that particular person type), while calling SUPER.OutputDetails() will call the method in the parent Person class
In the example below, you can see we have the following 3 classes:
Person
- Contains name and role attributes, along with getters, setters and an OutputDetails method that outputs those two attributes that all classes that inherit from Person will also have access to
Teacher
- Takes additional subject arguments in its constructor and calls the parent class' constructor immediately
- Updates the role inside the constructor for all teachers from "npc" to "teacher" - this follows the principle of DRY (don't repeat yourself) - i.e. we only need this one statement, rather than e.g. requiring the programmer to manually pass "teacher" into the constructor for every teacher they create, which is error prone and unnecessarily/tediously duplicates code
- Is polymorphic - OutputDetails is different from the OutputDetails in the super class, in that it also outputs the teacher's subject - the difference between them is demonstrated by calling both methods in the constructor as an example
Footballer
- As above, introduces its own attributes, getters and setters, polymorphic OutputDetails method etc
Hopefully these class examples demonstrate the advantage of OOP for specific situations - imagine extending this for a game for example. All people might have common methods like Eat, Run, Sleep, Talk etc and attributes like Name, Speed, Items, Hunger etc - yet each different character type would also have their own methods and attributes too
It's important to note that can can have more than just 1 level of inheritance - for example, in this contrived example, we can see that C inherits from both B and A (since B inherits from A, therefore since C inherits from B, it also inherits from A too)
When calling the Hello() method in class C, since this method doesn't exist in class C, but does exist in the immediate parent, then both calls will execute the method defined in class B
In contrast, since Goodbye() is only defined in class A, then a call to it from C will bubble all the way up to class A, where a method with this name is found
7
Shapes
Create a program with a Shape class that has private width & height attributes for all shapes - there should be getters for these 2 attributes too
You should then create 3 shapes that inherit from this class, with the following methods:
- Rectangle: GetPerimeter(), GetArea(), IsSquare() //returns Boolean
- Right-angled triangle: GetPerimeter(), GetArea(), IsPythagoreanTriplet() //returns true if INTEGER values of lengths A and B also give an INTEGER value of the hypotenuse C
- Circle: GetCircumference(), GetDiameter(), GetArea(), GetSectorArea(degrees), GetArcLength(degrees)
You might wish to create additional attributes or helper methods too
Test your program by creating instances of each shape, calling the different methods etc
8
Theme Hospital
Our goal is to a make a very simple simulation of the class game Theme Hospital - the exact implementation, class hierarchy, methods, attributes etc will be left to you to figure out - a great way to practice & solidify understanding of OOP is designing the implementation yourself afterall
The features should include
- Multiple types of people - e.g. Doctors and Patients - you can give them a random name from this array of 50 sample names: ["Florence", "Hippolyta", "Louis", "Marie", "James", "Francis", "Edward", "Joseph", "Clara", "Sigmund", "Pox", "Dribble", "Gurney", "Scalpel", "Stitch", "Suture", "Thermy", "Vaccy", "Bloaty", "Wheezy", "Sneezey", "Iggy", "Patch", "Saline", "Hemlock", "Abscess", "Gloop", "Mumpsy", "Banting", "Lister", "Curie", "Salk", "Fleming", "Clive", "Edith", "Matilda", "Oliver", "Hannah", "Theodore", "Josephine", "Albert", "Louisa", "Freya", "Simon", "Esther", "Maxwell", "Helena", "Nigel", "Dorothy", "Rupert"]
- An input prompt asking how many doctors to hire should be displayed at the start of the program
- A small number of diseases - e.g. Coronavirus, Swine Flue and Social Media Addiction. Each disease will take a given number of days to treat - or, if uncured after a certain number of days, the patient will die
- A random number of patients (each with one random disease) should arrive each day (up to a maximum of 10 patients in the hospital - otherwise they will be refused, due to the hospital being full)
- A doctor can treat up to 3 patients each day
Note: since pseudocode only supports fixed-size arrays and there is no concept of null values, you will have to think about how to loop through an array of 10 patients without errors - an approach could be:
- Store a variable containing the number of patients - only loop up to that point, to prevent getting errors if trying to call methods/access attributes on an array index that has no patient
- When patients are cured or die, that hospital bed should now become open for another patient - you could store a flag like InHospital on each patient to represent whether this spot can be taken by a new patient
Relevant messages should be output - e.g. an example of the program could be (note this example doesn't show all days - just some where notable events occurred):
9
Pokemon
For the final challenge, we will implement Pokemon battles - the following is suggested:
- The game will be 2 player - both should be prompted to enter their name
- The Pokemon and Pokemon move files as the bottom of the page will be loaded
- There will be 4 types (normal, fire, water, grass) - both Pokemon and each move will have a given type
- Both players will be able to choose 2 Pokemon (real Pokemon games have 6 - you can choose that if you want - I just chose 2 since the battles are over quicker/it's faster to test)
- Each Pokemon will start with 200 health (hp/hit points)
- Each player will be prompted asking which Pokemon they'd like to send into battle first - show relevant information like the Pokemon type, move details etc
- Player 1 will choose which of the 4 moves they'd like to use - the damage would then be inflicted on player 2's current Pokemon
- The total damage is calculated based on 3 factors: the move's base damage, the move type and the receiving Pokemon's type. For example, a fire move attacking a grass Pokemon will inflict more (e.g. double) damage, while a fire move attacking a water Pokemon will inflict less (e.g. half) damage. The exact modification multipliers are up to you - in the real Pokemon game, there are around 20 types. As a hint, a 2D array could be a good way to store this
- If player 2's Pokemon hasn't fainted, it will then have the oppertunity to attack player 1's Pokemon - these alternate attacks continue until a Pokemon has 0 health
- When a Pokemon has 0 health, the player will be allowed to pick another Pokemon from their pack - this continues until all of a player's Pokemon have fainted, at which point the other player wins
Note: as can be seen in the file at the bottom of the page, the Rest move heals your own Pokemon (assuming it hasn't already fainted), rather than attacking the enemy
The format for the following file is:
- Pokemon name
- Pokemon type
- Move 1
- Move 2
- Move 3
- Move 4
As you can see, data for each Pokemon takes up 6 lines - data for each of the 4 moves are in the file example at the bottom of the page
The format for the following file is:
- Move name
- Move type
- Damage
- Quantity
Note that if a move has negative damage (only "Rest" here), that means that rather than inflicting damage on the opponent, they will instead heal themselves