Implementing OOP
Classes
Given below is a tutorial you can refer to (from Oracle’s official Java tutorials) to learn how to implement java classes, in the unlikely case you don't know how to do that already.
- Classes, methods, variables – Start from the linked page and follow the next few steps in the tutorial
Class-Level Members
You can refer to Oracle’s official Java tutorial on class-level members to learn how to implement Java class-level members, in the unlikely case you don't know how to do that already.
Associations
We use instance level variables to implement associations.
A normal instance-level variable gives us a 0..1
multiplicity (also called optional associations) because a variable can hold a reference to a single object or null
.
class Logic {
Minefield minefield;
...
}
class Minefield {
...
}
A variable can be used to implement a 1
multiplicity too (also called compulsory associations).
class Logic {
ConfigGenerator cg = new ConfigGenerator();
...
}
Bi-directional associations require matching variables in both classes.
class Foo {
Bar bar;
...
}
class Bar {
Foo foo;
...
}
To implement other multiplicities, choose a suitable data structure such as Arrays, ArrayLists, HashMaps, Sets, etc.
class Minefield {
Cell[][] cell;
...
}
Dependencies
Dependencies result from interactions between objects that do not result in a long-term link between the said objects.
Example:
class TaxProcessor{
double rate;
void addTax(Taxable t){
t.addTax(rate);
}
}
The code above results in this dependency.
The code does not indicate an association between the two classes because the TaxProcessor
object does not keep the Taxable
object (i.e. it’s only a short-term interaction)
Composition
Composition too is implemented using a normal variable. If correctly implemented, the ‘part’ object will be deleted when the ‘whole’ object is deleted. Ideally, the ‘part’ object may not even be visible to clients of the ‘whole’ object.
Example:
class Car {
private Engine engine;
...
}
Aggregation
Implementation is similar to that of composition except the containee object can exist even after the container object is deleted.
📦 Example:
class Car {
Person driver;
...
void drive(Person p) {
driver = p;
}
}
Association Classes
Note that while a special notation is used to indicate an association class, there is no special way to implement an association class.
Example:
At implementation level, an association class is most likely implemented as follows.
Inheritance
To learn how to implement inheritance in Java, you can follow [Oracle’s Java Tutorials: Inheritance]
A very beginner-friendly video about implementing Java inheritance.
Java requires all class to have a parent class. If you do not specify a parent class, Java automatically assigns the Object
class as the parent class.
Overriding
To override a method inherited from an ancestor class, simply re-implement the method in the target class.
📦 A simple example where the Report#print()
method is overridden by EvaluationReport#print()
method:
class Report{
void print(){
System.out.println("Printing report");
}
}
class EvaluationReport extends Report{
@Override // this annotation is optional
void print(){
System.out.println("Printing evaluation report");
}
}
class ReportMain{
public static void main(String[] args){
Report report = new Report();
report.print(); // prints "Printing report"
EvaluationReport evaluationReport = new EvaluationReport();
evaluationReport.print(); // prints "Printing evaluation report"
}
}
Overloading
An operation can be overloaded inside the same class or in sub/super classes.
📦 The constructor of the Account
class below is overloaded because there are two constructors with different signatures: ()
and (String, String, double)
. Furthermore, the save
method
in the Account
class is overloaded in the child class SavingAccount
.
class Account {
Account () {
...
}
Account (String name, String number, double balance) {
...
}
void save(int amount){
...
}
}
class SavingAccount extends Account{
void save(Double amount){
...
}
}
Interfaces
Java allows multiple inheritance among interfaces. A Java class can implement multiple interfaces (and inherit from one class).
📦 The design below is allowed by Java.
Staff
interface inherits (note the solid lines) the interfacesTaxPayer
andCitizen
.TA
class implements bothStudent
interface and theStaff
interface.- Because of point 1 above,
TA
class has to implement all methods in the interfacesTaxPayer
andCitizen
. - Because of points 1,2,3, a
TA
is aStaff
, is aTaxPayer
and is aCitizen
.
Java uses the interface
keyword to declare interfaces and implements
keyword to indicate that a class implements a given interface. Inheritance among interfaces uses the extends
keyword,
just like inheritance among classes.
📦 The code for the example design given in the previous example:
interface TaxPayer {
void payTax();
}
interface Citizen {
void vote();
}
interface Staff extends Citizen, TaxPayer{
void work();
}
interface Student {
void learn();
}
class TA implements Student, Staff{
@Override
public void payTax() {
//...
}
@Override
public void vote() {
//...
}
@Override
public void work() {
//...
}
@Override
public void learn() {
//...
}
}
Abstract Classes
Use the abstract
keyword to identify abstract classes/methods.
📦 Partial code below gives an example of how to declare abstract classes/methods.
abstract class Account {
int number;
abstract void addInterest();
void close(){
//...
}
}
class CurrentAccount extends Account{
@Override
void addInterest() {
//...
}
}
In Java, if a class contains an abstract method, the class itself should be an abstract class i.e. if any methods of the class is 'incomplete', the class itself is 'incomplete'.
Polymorphism
We can use inheritance to achieve polymorphism.
📦 Continuing with the example given in [
Staff
, Admin
, and Academic
classes that achieves the desired polymorphism.
Design → Object Oriented Programming → Polymorphism →
Introduction
Polymorphism:
The ability of different objects to respond, each in its own way, to identical messages is called polymorphism. -- Object-Oriented Programming with Objective-C, Apple
Take the example of writing a payroll application for a university to facilitate payroll processing of university staff. Suppose an adjustSalary(int)
operation adjusts the salaries of all staff members. This operation will
be executed whenever the university initiates a salary adjustment for its staff. However, the adjustment formula is different for different staff categories, say admin and academic. Here is one possible way of designing the classes
in the Payroll
system.
📦 Calling adjustSalary()
method for each Staff type:
Here is the implementation of the adjustSalary(int)
operation from the above design.
class Payroll1 {
ArrayList< Admin > admins;
ArrayList< Academic > academics;
// ...
void adjustSalary(int byPercent) {
for (Admin ad: admins) {
ad.adjustSalary(byPercent);
}
for (Academic ac: academics) {
ac.adjustSalary(byPercent);
}
}
}
Note how processing is similar for the two staff types. It is as if the type of staff members is irrelevant to how they are processed inside this operation! If that is the case, can the staff type be "abstracted away" from
this method? Here is such an implementation of adjustSalary(int)
:
class Payroll2 {
ArrayList< Staff > staff;
// ...
void adjustSalary(int byPercent) {
for (Staff s: staff) {
s.adjustSalary(byPercent);
}
}
}
Notice the following:
- Only one data structure
ArrayList< Staff >
. It contains bothAdmin
andAcademic
objects but treats them asStaff
objects - Only one loop
- Outcome of the
s.adjustSalary(byPercent)
method call depends on whethers
is anAcademic
orAdmin
object
The above code is better in several ways:
- It is shorter.
- It is simpler.
- It is more flexible (this code will remain the same even if more staff types are added).
This does not mean we are getting rid of the Academic
and Admin
classes completely and replacing them with a more general class called Staff
. Rather, this part of the code “treats” both Admin
and Academic
objects as one type called Staff
.
For example, ArrayList
staff contains both Admin
and Academic
objects although it treats all of them as Staff
objects. However, when the adjustSalary(int)
operation of
these objects is called, the resulting salary adjustment will be different for Admin
objects and Academic
objects. Therefore, different types of objects are treated as a single general type, but yet each type
of object exhibits a different kind of behavior. This is called polymorphism (literally, it means “ability to take many forms”). In this example, an object that is perceived as type Staff
can be an Admin
object or an Academic
object.
class Staff {
String name;
double salary;
void adjustSalary(int percent) {
// do nothing
}
}
//------------------------------------------
class Admin extends Staff {
@Override
void adjustSalary(int percent) {
salary = salary * percent;
}
}
//------------------------------------------
class Academic extends Staff {
@Override
void adjustSalary(int percent) {
salary = salary * percent * 2;
}
}
//------------------------------------------
class Payroll {
ArrayList< Staff > staff;
// ...
void adjustSalary(int byPercent) {
for (Staff s: staff) {
s.adjustSalary(byPercent);
}
}
}