Object Oriented Programming

Classes

🏆 Can implement 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.

Class-Level Members

🏆 Can implement 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

🏆 Can implement 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;
    ...
}

Draw a class diagram for the code below. Also draw an object diagram that will represent the object structure after running Main#main().

  • Make the multiplicities as strict as possible without contradicting the code.
  • You may omit the Main class from both diagrams.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Item item1 = new Item();
        Item item2 = new Item();
        Box box1 = new Box(item1);
        Box box2 = new Box(item2);
        item2.setMainCard(new Card());
        item2.addPreviousBox(box1);
        item2.addPreviousBox(box2);
    }
}

class Box{
    private Item item;
    Box (Item item){
        this.item = item;
    }
}

class Item{
    private List<Box> previousBoxes = new ArrayList<>();
    private Card mainCard = null;
    private Card subCard = null;

    void setMainCard(Card card){
        this.mainCard = card;
    }
    
    void setSubCard(Card card){
        this.subCard = card;
    }

    void addPreviousBox(Box previousBox){
        previousBoxes.add(previousBox);
    }
}

class Card{
    
}

Suppose we wrote a program to follow the class structure given in this class diagram:

Draw object diagrams to represent the object structures after each of these steps below. Assume that we are trying to minimize the number of total objects.

i.e. apply step 1 → [diagram 1] → apply step 2 on diagarm 1 → [diagarm 2] and so on.

  1. There are no persons.

  2. Alfred is the Guardian of Bruce.

  3. Bruce's contact number is the same as Alfred's.

  4. Alfred is also the guardian of another person. That person lists Alfreds home address as his home address as well as office address.

  5. Alfred has a an office address at Wayne Industries building which is different from his home address (i.e. Bat Cave).

After step 2, the diagram should be like this:

Implement the class structure given below:

Dependencies

🏆 Can implement 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

🏆 Can implement 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

🏆 Can implement 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

🏆 Can implement 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.

Which one of these is the suitable as an Association Class?

  • a
  • b
  • c
  • d

(a)(b)(c)(d)

Explanation: Mileage is a property of the car, and not specifically about the association between the Driver and the Car. If Mileage was defined as the total number of miles that car was driven by that driver, then it would be suitable as an association class.

Inheritance

🏆 Can implement basic 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

🏆 Can implement operation 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"
    }
}

Which of these methods override another method?

  • a
  • b
  • c
  • d
  • e

d

Explanation: Method overriding requires a method in a child class to use the same method name and same parameter sequence used by one of its ancestors

Overloading

🏆 Can implement 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

🏆 Can implement 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.

  1. Staff interface inherits (note the solid lines) the interfaces TaxPayer and Citizen.
  2. TA class implements both Student interface and the Staff interface.
  3. Because of point 1 above, TA class has to implement all methods in the interfaces TaxPayer and Citizen.
  4. Because of points 1,2,3, a TAis 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

🏆 Can implement 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'.

Choose the correct statements about abstract classes and concrete classes.

  • a. A concrete class can contain an abstract method.
  • b. An abstract class can contain concrete methods.
  • c. An abstract class need not contain any concrete methods.
  • d. An abstract class cannot be instantiated.

(b)(c)(d)

Explanation: A concrete class cannot contain even a single abstract method.

Polymorphism

🏆 Can implement polymorphism

We can use inheritance to achieve polymorphism.

📦 Continuing with the example given in [ 🎓 OOP → Polymorphism → Introduction ], given below is the minimum code for 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 both Admin and Academic objects but treats them as Staff objects
  • Only one loop
  • Outcome of the s.adjustSalary(byPercent) method call depends on whether s is an Academic or Admin 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);
        }
    }
}