Lecture 1 Object-Oriented Design and Implementation
Lecture 1 Object-Oriented Design and Implementation
Objectives
The objective of this lecture is to introduce object-oriented design and
implementation strategies. After this lecture, student will
■ learn implementation strategies that are transforming design to code for object
oriented principles (e.g., abstraction, encapsulation, decomposition,
generalization, different types of inheritance, association, aggregation, and
composition dependencies);
■ explain different types of associations and learn how to create class definitions
from UML class diagram to the Java code;
■ lean how to apply design guidelines for modularity, separation of concerns,
information hiding, and conceptual integrity to create a flexible, reusable,
maintainable design
■ learn the implementation tradeoff between cohesion and coupling.
■ understand the SOLID principles that are the fundamental principles for object-
oriented programming to be followed for adaptable changes to the class design
and maintaintablity
Reference Reading
[1] Rajib Mall, ―Fundamental of Software Engineering‖, 4th Edition, 2014
• Chapter 8: Object Oriented Software Development
[2] Brahma Dathan and Sarnath Ramnath, ―Object-Oriented Analysis, Design and
Implementation‖ , An Integrated Approach, Second Edition, 2015, UTiCS,
Springer.
• Chapter 7: Design and Implementation
[3] https://www.visual-paradigm.com/guide/uml-unified-modeling-language/
[4] https://www.tutorialspoint.com/object_oriented_analysis_design/ooad_object_ori
ented_paradigm.htm
[5] https://www3.ntu.edu.sg/home/ehchua/programming/java/J3a_OOPBasics.html
[6] https://www.w3resource.com/java-exercises/index-interface.php
-1-
1. Introduction
In this lecture, the object oriented design model and the implementation strategies are
discussed with practical example using Java technology. Students are expected to have
basic Java programming knowledge. We have discussed the UML diagrams that
represent different views for the software system in previous lecture. This lecture
provides the implementation strategies for UML design models, transforming design to
code.
The object design models come between the system design and implementation.
After the designing stage, the implementation process gets start. Classes and their
relationships which are developed during the object design; such designs are transformed
into a particular programming language at the implementation stage. Generally, the task
of converting an object design into source code is a straightforward process. But the
feature such as the association of class diagrams in the design model is not possible to
directly convert into programming language structures. Actually, most programming
languages do not offer any paradigms to implement associations directly. This lecture,
explores some of the significant features and talks about the several strategies that should
be adopted for their implementation.
This lecture will explain concepts related to the implementation strategy, such as
mapping design to code and illustrate how to transform class definition from class
diagram and strategies for implementing unidirectional or bidirectional associations and
how to create method from collaboration diagram, constraints and their implementation,
statecharts and their implementation.
Firstly, let‘s recall our previous discussion on the object oriented concepts and design
with UML diagram and then explain the implementation strategies for those uml design.
Basic concepts of the object oriented paradigm are the foundation elements to
visualize a software application in the object model. The basic concepts and
terminologies in object oriented systems are:
• Abstraction
o Classes
o Objects
• Encapsulation
• Inheritance
• Polymorphism
We discuss briefly about them with object oriented programming concept with java
language.
-2-
2.1 Abstraction
Abstraction is a higher-level concept or a way of thinking when you start designing
your application from the business requirement.
Abstraction means, simply, to filter out an object‟s properties and operations.
For a business requirement, after you‘ve made your decisions about what to include and
what to exclude, is an abstraction of a system that you are going to build.
Abstraction is a process of identifying essential entities (classes) and their
characteristics (class members) and leaving irrelevant information from the
business requirement to prepare a higher-level application design.
Abstraction process includes finding nouns from the business requirement (the noun
is the person, place, thing, or process) and identifying potential classes and their attributes
and operations from the nouns.
2.2 Encapsulation
Encapsulation is a technique to implement the abstraction in code. Create classes
and their attributes and operations with appropriate access modifiers to show
functionalities and hide details and complexity.
Encapsulation hides the data and implementation details show only the required
members within a class, thus hiding complexity from other code. No other code needs
to know about implementation detail and also can‘t modify the code of the class‘s data
and methods. Encapsulation is also called information hiding. In the following section,
we discuss encapsulation with example class implementation.
-3-
that can be created from it. Creation of an object as a member of a class is called
instantiation. Thus, object is an instance of a class. The constituents of a class are −
• A set of attributes for the objects. Attributes are referred as class data.
• A set of operations that describe the behavior of the objects of the class.
Operations are also referred as functions or methods.
Attributes or data fields represent an employee’s name, hourly pay rate, and weekly
pay amount and operations or methods include two set methods that accept values from
the outside world, three get methods that send data to the outside world and one work
method that performs work within the class.
(1) The SET Methods: In the Employee class, the setLastName() and setHourlyWage()
methods are known as set methods because their purpose is to set the values of data
fields within the class. Each accepts data from the outside and assigns it to a field
within the class.
(a) Employee class diagram in general form (b) Employee class diagram with with public
and private access specifiers
Figure 1.2: Employee class diagram
(2) The GET Methods: In the Employee class, three of the methods: getLastName(),
getHourlyWage(), and getWeeklyPay() methods are called get methods. The purpose
of a get method is to return a value to the world outside the class. The
getLastName() method returns ―string‖ type value of employee name, the
getHourlyWage() method returns ―number‖ type value of hourly wage, and the
-4-
getWeeklyPay() method returns ―number‖ type value of the employee‘s weekly pay
amount.
(3) The WORK method: In the Employee class, calculateWeeklyPay() method is a work
method within the class. It computes the weeklyPay value by multiplying
hourlyWage by the work_week_hours. No values need to be passed into this method,
and no value is returned from it because this method does not communicate with the
outside world. Instead, this method is called only from within another method in the
same class.
(4) The Access Specifier: An access specifier (or access modifier) is the defining the
type of access (public or private) to the attribute or method of a class from the
outside classes.
In object-oriented principles, encapsulation is the process of combining all of an
object‘s attributes and methods into a single package and information hiding is the
concept that other classes should not alter an object‘s attributes—only the methods of
an object‘s own class should have that privilege. Outside classes should only be
allowed to make a request that an attribute be altered; then it is up to the class‘s
methods to determine whether the request is appropriate. Detailed workings of
objects create within object-oriented programs can be hidden from outside programs
and modules if you want them to be. Object-oriented programming defines these
encapsulation and information hiding principles using the access specifier.
To prevent changing the class‘s data fields from outside programs or methods, the
object-oriented programmers usually specify that their attributes or data fields will
have private access—that is, the data cannot be accessed by any method that is not
part of the class. The public access—allows other programs and methods may use
the methods to access to the class‘s inside private data.
In UML class diagram, visibility symbols are used to define the access specifier.
The UML defines four levels of visibility, namely public, protected, private and
package. Visible symbols:
Figure 1.2 (a) is general Employee class and figure 1.2(b) with access specifier
information.
Advantages of Encapsulation:
-5-
• Restrict unauthorized access of data by allowing authorization before data access.
• Allow validation before setting data.
• Only the author of the class needs to understand the implementation, not others.
• Makes applications easy to maintain.
// default constructor
public Employee( ){
this.hourlyWage = 10.00;
calculateWeeklyPay();
}
// constructor for initial values
public Employee(double rate, String name){
this.lastName = name;
setHourlyWage (rate);
calculateWeeklyPay();
}
-6-
if (wage < MINWAGE){
this.hourlyWage = MINWAGE;
}
else if (wage > MAXWAGE) {
this.hourlyWage = MAXWAGE;
}
else {
this.hourlyWage = wage;
}
calculateWeeklyPay();
}
Mapping for Methods and Attributes Signature to the Code from the Class Diagram
• The Employee class diagram shown in figure 1.1(b) with public and private access
specifiers.
• Attributes of the class are represented by fields in Java, with UML data types being
translated into the appropriate Java equivalents where necessary.
• Operations of the class are defined as methods in Java, with appropriate
implementations.
• A default constructor is one that requires no arguments. In OO languages, a default
constructor is created automatically by the compiler for every class you write. Any
constructor must have the same name as the class it constructs, and constructor
methods cannot have a return type.
-7-
• A constructor is defined to initialize the values of the attributes. Note that
constructors are often omitted from class diagrams, but are required in
implementations of classes.
• The class diagram does not tell you what takes place inside the method.
• When you create the Employee class, you include method implementation details.
• For example, in the setHourlyWage() method, the wage passed to the method is tested
against minimum and maximum values, and is assigned to the class field hourlyWage
only if it falls within the prescribed limits. If the wage is too low, the MINWAGE value
is substituted, and if the wage is too high, the MAXWAGE value is substituted.
• The purpose of the set method is to set the values of data fields within the class.
• The purpose of a get method is to return a value to the world outside the class.
A TestEmployee class shown below is the test driver class that creates or
instantiation an Employee object and sets the hourly wage value. The test driver class
displays the weeklyPay value. Then a new value is assigned to hourlyWage and
weeklyPay is displayed again. When Employee objects eventually are created, each
-8-
object will have its own lastName, hourlyWage and weeklyPay fields and have access
to methods to get and set it. Now, compile and run this program to check how the object
creation works. The execution result of the TestEmployee.java is shown in figure 1.4. As
you can see from the output in Figure 1.4, the weeklyPay value has been recalculated
even though it was never set directly by the client program.
//declaration
Employee myGardener, myMaid;
//setting values
myGardener.setLastName("Greene");
myGardener.setHourlyWage(LOW);
-9-
Figure 1.4: Execution of the TestEmployee class
• to provide initial values, for example, to set all numeric fields to zero by default.
• to hold some predefined default values, or
• to perform additional tasks when creating an instance of a class
For the Employee class, in implemented java code, the default constructor is
Employee( ), that establishes one Employee object with the identifier provided. For
example, if you want every Employee object to have a starting hourly wage of $10.00 as
well as the correct weekly pay for that wage, then you could write the constructor for the
Employee class as follows:
// default constructor
public Employee( ){
this.hourlyWage = 10.00;
calculateWeeklyPay();
- 10 -
}
Any Employee object instantiated will have an hourlyWage field value equal to 10.00,
a weeklyPay field equal to $400.00, and a lastName field equal to the default value for
strings. The another constructor with the parameter list is to set the predefined values
specified by the user.
In the test driver class, there are two employee objects: myGardener and myMaid.
myGardener object use default constructor and myMaid object uses parameter constructor.
In a class, there can be two types of methods: static methods and non-static methods.
- 11 -
Student.displayStudentMotto();
- 12 -
Q4. Assume you have created a class named MyClass, and that a working program
contains the following statement:
output MyClass.number
Which of the following do you know?
(a) number is a numeric field (b) number is a static field
(c) number is an instance variable (d) all of the above
Q5. Assume you have created an object named myObject and that a working program
contains the following statement:
output myObject.getSize()
Which of the following do you know?
(a) size is a private numeric field (b) size is a static field
(c) size is a public instance variable (d) all of the above
Q6. When you instantiate an object, the automatically created method that is called is a(n)
---------.
(a) creator (b) initiator (c) constructor (d) architect
- 13 -
Q10. Complete the following tasks:
(a) Write a java class named GirlScout with fields that hold a name, troop number, and
dues owed. Include get and set methods for each field. Include a static method that
displays the Girl Scout motto (―To obey the Girl Scout law‖). Include three
overloaded constructors as follows:
• A default constructor that sets the name to ―XXX‖ and the numeric fields to 0.
• A constructor that allows you to pass values for all three fields
• A constructor that allows you to pass a name and troop number but sets dues owed
to 0.
Create the class diagram and write the pseudocode that defines the class.
- 14 -
3. Relationship Between Classes
- 15 -
Figure 1.5: Inheritance relationship between classes
A class that is used as a basis for inheritance, like Employee class, is called a base
class. When you create a class that inherits from a base class (such as
CommissionEmployee, HourlyEmployee class), it is a derived class or extended class. A
CommissionEmployee ―is an‖ Employee—not always the other way around—so
Employee is the base class and CommissionEmployee is derived class.
You can use the terms superclass and subclass as synonyms for base class and
derived class. Thus, CommissionEmployee can be called a subclass of the Employee
superclass. You can also use the terms parent class and child class. A
CommissionEmployee is a child to the Employee parent.
A derived class can be further extended. In other words, a subclass can have a child
of its own. The entire list of parent classes from which a child class is derived
constitutes the ancestors of the subclass. A child inherits all the members of all its
ancestors.
- 16 -
this.empNum = number; public void setCommissionRate (float
} rate)
public String getEmpNum( ){ this.commissionRate = rate;
return this.empNum; }
}
public void setWeeklySalary(float salary){ public float getCommissionRate( ) {
this.weeklySalary = salary; return this.commissionRate;
} }
public float getWeeklySalary( ){ }
return this.weeklySalary;
}
}
public class HourlyEmployee extends Employee {
private float hoursWorked;
private float hourlyRate;
public void setHoursWorked(float hours){
this.hoursWorked = hours;
setWeeklySalary(hoursWorked * hourlyRate); // call base class public set method
}
public float getHoursWorked( ){
return this.hoursWorked;
}
public void setHourlyRate(float rate){
this.hourlyRate = rate;
setWeeklySalary(hoursWorked * hourlyRate); // call base class public set method
}
public float getHourlyRate( ){
return this.hourlyRate;
}
}
In the above example, the Employee class is called the base class or the parent class,
and the CommissionEmployee class is called the derived class or the child class.
The CommissionEmployee class and HourlyEmployee class inherit from the Employee
class and so these two classes automatically acquire all the public members of the
Employee class. It means even if the CommissionEmployee class does not include
empNum, weeklySalary properties and set/get…() methods, an object of the
CommissionEmployee class will have all the properties and methods of the Employee class
along with its own members (commissionRate). See the executed results of following test
driver TestInheritedMembers class, the CommissionEmployee object: salesperson can use
empNum and weeklySalary data fields by using public set methods:
salesperson.setEmpNum("222") and salesperson.setWeeklySalary(300.00) methods. The
- 17 -
HourlyEmployee class uses the base class public method, setWeeklySalary() inside its
class to set the weeklysalary value.
Example: Inherited Members
manager.setEmpNum("111");
manager.setWeeklySalary(700.00);
Note that Java does not allow a class to inherit multiple classes. A class can only achieve
multiple inheritances through interfaces. The following is the executed results of the test
driver class, TestInheritedMembers.class.
Output:
Manager: 111 has weekly salary:
Salesperson: 222 has weekly salary: with commission rate: 0.12
Part Time worker: 333 has weekly salary: with hourly rate: 20.12
- 18 -
3.1.3 Role of Access Modifiers in Inheritance
Access modifiers play an important role in inheritance. Access modifiers of each
member in the base class impact their accessibility in the derived class.
}
//Test driver class
}
//Test driver class
- 19 -
(3) Protected Members
The protected members of the base class can be accessible from the derived class
but cannot be a part of the derived class object.
Let consider access modifier with HourlyEmployee class given below. In the
HourlyEmployee class, it set weeklySalary value by calling the public base class method
setWeeklySalary() because weeklySalary remains private in Employee class. If weeklySalary
is defined as protected access modifier in the base Employee class, the derived class can
access the data fields inside the derived class but not accessible outside classes.
- 20 -
this.weeklySalary = salary; }
} public void setHourlyRate(float rate){
public float getWeeklySalary( ){ this.hourlyRate = rate;
return this.weeklySalary;
weeklySalary = hoursWorked *
}
} hourlyRate;
}
public float getHourlyRate( ){
return this.hourlyRate;
}
}
//Test driver class
Employee Constructor
Commission Employee Constructor
- 21 -
If there are multiple levels of inheritance then the constructor of the first base class
will be called and then the second base class and so on. For example consider the
following hierarchy of inheritance.
class Base {
public Base() {
System.out.println("Base");
}
}
-----------------------------------------------------------------
class Derived extends Base {
public Derived() {
System.out.println("Derived");
}
}
-----------------------------------------------------------------
class DeriDerived extends Derived {
public DeriDerived() {
System.out.println("DeriDerived");
}
}
-------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
Derived b = new DeriDerived();
}
}
Output:
Base
Derived
DeriDerived
Explanation:
Whenever a class gets instantiated, the constructor of its base classes (the constructor of the root
of the hierarchy gets executed first) gets invoked before the constructor of the instantiated class.
- 22 -
super.getArea() to invoke the superclass' version within the subclass definition. Similarly,
if your subclass hides one of the superclass' variable, you can use super.variableName to
refer to the hidden variable within the subclass definition. For example, the following
calls the base class's parameterized constructor using the super key.
//constructors
public Employee ( ) {
System.out.println("Employee no-arg Constructor ");
}
//another constructor with parameter
public Employee (String empno) {
this.empNum = empno;
System.out.println(“Employee: “ + empNum);
}
//another constructor with two parameters
public Employee (String empno, float wsalary) {
this.empNum = empno;
this.weeklySalary = wsalary;
System.out.println(“Employee: “ + empNum + “ with weeklySalary: “ + weeklySalary);
}
…
}
//constructor
public HourlyEmployee() {
super ( ); //call constructor of base class
}
public HourlyEmployee(String empno) {
super(empno); // call Parameterized constructor of base class
}
public HourlyEmployee(String empno, float salary){
super(empno, salary); // call two parameterized constructor of base class
}
}
class TestEmployee {
public static void main(String[] args){
- 23 -
HourlyEmployee emp = new HourlyEmployee();
HourlyEmployee emp1 = new HourlyEmployee(“Emp 11”);
HourlyEmployee emp2 = new HourlyEmployee(“Emp 22”, 2300);
…
}
}
Output:
Employee no-arg Constructor
Employee: Emp 11
Employee: Emp 22 with weeklySalary: 2300
Note that:
• super(args), if it is used, must be the first statement in the subclass' constructor.
If it is not used in the constructor, Java compiler automatically insert a super()
statement to invoke the no-arg constructor of its immediate superclass. This
follows the fact that the parent must be born before the child can be born. You need
to properly construct the superclasses before you can construct the subclass.
• If no constructor is defined in a class, Java compiler automatically create a no-
argument (no-arg) constructor, that simply issues a super() call, as follows:
• The default no-arg constructor will not be automatically generated, if one (or more)
constructor was defined. In other words, you need to define no-arg constructor
explicitly if other constructors were defined.
• If the immediate superclass does not have the default constructor (it defines some
constructors but does not define a no-arg constructor), you will get a compilation
error in doing a super() call. Note that Java compiler inserts a super() as the first
statement in a constructor if there is no super(args).
- 24 -
Instance variable Instance Type Instance Members of
Base type Base type Base type
Base type Derived type Base type
Derived type Derived type Base and derived type
The following program demonstrates supported members based on the variable type:
//Instance variable = base type, Instance type = base type, Instance members of = base type
Employee emp1 = new Employee ();
emp1.empNum = “emp-1”; //valid
emp1.weeklySalary = 700.00; //valid
emp1. hoursWorked; // not supported
emp1. commissionRate; // not supported
//Instance variable = base type, Instance type = derived type, Instance members of = base type
Employee emp2 = new CommissionEmployee();
emp2. empNum = “emp-2”; //valid
emp2. weeklySalary = 650.00; //valid
- 25 -
emp2. commissionRate = 0.12; // not supported
In the above example, the type of emp2 is Employee, so it will only expose public
properties of the Employee type even if an object type is the CommissionEmployee.
However, the type of hemp is HourlyEmployee and so it exposes all the public properties
of both classes. Note that the base type object cannot be assigned to the derived type
variable.
For example,
Employee emp1 = new HourlyEmployee(12, 12.00);
// Compiler checks to ensure that Right-value is a subclass of Left-value.
Employee emp2 = new String(); // Compilation error: incompatible types
You can revert a substituted instance back to a subclass reference. This is called
"downcasting". For example,
- 26 -
Employee emp1 = new HourlyEmployee(12, 12.00); // upcast is safe
HourlyEmployee hemp1 = (HourlyEmployee) emp1; // downcast needs the casting operator
Downcasting requires explicit type casting operator in the form of prefix operator
(new-type). Downcasting is not always safe, and throws a runtime ClassCastException
if the instance to be downcasted does not belong to the correct subclass. A subclass object
can be substituted for its superclass, but the reverse is not true.
public class A {
public A() { // Constructor
System.out.println("Constructed an instance of A");
}
@Override
public String toString() {
return "This is A";
}
}
----------------------------------------------------------------------
public class B extends A {
public B() { // Constructor
super();
System.out.println("Constructed an instance of B");
}
@Override
public String toString() {
return "This is B";
}
}
----------------------------------------------------------------------
public class C extends B {
public C() { // Constructor
super();
System.out.println("Constructed an instance of C");
- 27 -
}
@Override
public String toString() {
return "This is C";
}
}
Constructed an instance of A
Constructed an instance of B
This is B
..runtime error: java.lang.ClassCastException: class B cannot be cast to class C
- 28 -
(1) Single Inheritance
- 29 -
3.1.9 Summary of Inheritance
• A class can inherit a single class only (i.e., single inheritance). It cannot inherit
from multiple classes.
• A class can inherit (implement) one or more interfaces.
• Constructors or destructors cannot be inherited.
Suppose that we are required to model students and teachers in our application. We
can define a superclass called Person to store common properties such as name and
address, and subclasses Student and Teacher for their specific properties. For students, we
need to maintain the courses taken and their respective grades; add a course with grade,
print all courses taken and the average grade. Assume that a student takes no more than
30 courses for the entire program. For teachers, we need to maintain the courses taught
currently, and able to add or remove a course taught. Assume that a teacher teaches not
more than 5 courses concurrently.
- 30 -
The Superclass Person.java
/**
* The superclass Person has name and address.
*/
public class Person {
// private instance variables
private String name, address;
/** Constructs a Person instance with the given name and address */
public Person(String name, String address) {
this.name = name;
this.address = address;
}
/**
* The Student class, subclass of Person.
*/
public class Student extends Person {
// private instance variables
private int numCourses; // number of courses taken so far
private String[] courses; // course codes
private int[] grades; // grade for the corresponding course codes
private static final int MAX_COURSES = 30; // maximum number of courses
- 31 -
/** Constructs a Student instance with the given name and address */
public Student (String name, String address) {
super(name, address);
this.numCourses = 0;
this.courses = new String[MAX_COURSES];
this.grades = new int[MAX_COURSES];
}
/**
* The Teacher class, subclass of Person.
*/
public class Teacher extends Person {
// private instance variables
- 32 -
private int numCourses; // number of courses taught currently
private String[] courses; // course codes
private static final int MAX_COURSES = 5; // maximum courses
/** Constructs a Teacher instance with the given name and address */
public Teacher (String name, String address) {
super(name, address);
this.numCourses = 0;
this.courses = new String[MAX_COURSES];
}
/** Adds a course. Returns false if the course has already existed */
public boolean addCourse (String course) {
// Check if the course already in the course list
for (int i = 0; i < numCourses; i++) {
if (courses[i].equals(course)) return false;
}
courses[numCourses] = course;
numCourses++;
return true;
}
/** Removes a course. Returns false if the course cannot be found in the course list */
public boolean removeCourse(String course) {
boolean found = false;
// Look for the course index
int courseIndex = -1; // need to initialize
for (int i = 0; i < numCourses; i++) {
if (courses[i].equals(course)) {
courseIndex = i;
found = true;
break;
}
}
if (found) {
// Remove the course and re-arrange for courses array
for (int i = courseIndex; i < numCourses-1; i++) {
courses[i] = courses[i+1];
}
numCourses--;
return true;
} else {
- 33 -
return false;
}
}
}
/**
* A test driver for Person and its subclasses.
*/
public class TestPerson {
public static void main(String[] args) {
/* Test Student class */
Student s1 = new Student("Tan Ah Teck", "1 Happy Ave");
s1.addCourseGrade("IM101", 97);
s1.addCourseGrade("IM102", 68);
s1.printGrades();
System.out.println("Average is " + s1.getAverageGrade());
- 34 -
IM101 cannot be added
IM101 removed
IM102 removed
IM101 cannot be removed
Q1. Advantages of creating a class that inherits from another include all of the following
except:
(a) You save time because subclasses are created automatically from those that come
built-in as part of a programming language.
(b) You save time because you need not re-create the fields and methods in the original
class.
(c) You reduce the chance of errors because the original class‘s methods have already
been used and tested.
(d) You make it easier for anyone who has used the original class to understand the new
class.
- 35 -
Q6. Which of the following is true?
(a) A class‘s data members are usually public. (b) A class‘s methods are usually public.
(c) Both of the above (d) none of the above
Q7. Which of the upcasting is valid for the UML class diagram?
Q8. Which of the downcasting is invalid for the UML class diagram?
- 36 -
(c) Write a test driver class that instantiates an object of each type and demonstrates all
the methods.
(a) Write a java class named Circle that holds a radius and color. Include methods that
are shown in figure.
(b) Write a java class named Cylinder that is a child class of Circle. Include a new data
field height of the cylinder. Show how the subclass Cylinder invokes the superclass'
constructors (via super() and super(radius)) and inherits the variables and methods
from the superclass Circle.
(c) Write a test driver class that instantiates an object of Cylinder using default
constructor and another cylinder object with defined height and radius. Then display
each cylinder object‘s radius, height, color and volume.
- 37 -
3.2 Polymorphism
Polymorphism is a Greek word that means multiple forms or shapes. You can use
polymorphism if you want to have multiple forms of one or more methods of a class
with the same name. Polymorphism can be achieved in two ways:
Figure 1.7: The Circle class with method overloading in constructor methods
- 38 -
Example: Method Overloading
public class Circle {
// private instance variables
private double radius;
private String color;
// Constructors
public Circle() {
this.radius = 1.0;
this.color = "red";
System.out.println("Construced a Circle with Circle()"); // for debugging
}
public Circle(double radius) {
this.radius = radius;
this.color = "red";
System.out.println("Construced a Circle with Circle(radius)"); // for debugging
}
public Circle(double radius, String color) {
this.radius = radius;
this.color = color;
System.out.println("Construced a Circle with Circle(radius, color)"); // for debugging
}
- 39 -
}
}
- 40 -
to provide a different implementation than the base class. This is called method
overriding that also known as runtime polymorphism.
In polymorphism, a subclass inherits all the member variables and methods from its
superclasses (the immediate parent and all its ancestors). It can use the inherited methods
and variables as they are. It may also override an inherited method by providing its
own version, or hide an inherited variable by defining a variable of the same name.
In the programming language, the compiler cannot know at compile time precisely
which piece of codes is going to be executed at run-time (e.g., getArea() has different
implementation for Rectangle and Triangle). To support the runtime polymorphism,
object-oriented language like Java uses a mechanism called dynamic binding (or late-
binding or run-time binding). When a method is invoked, the code to be executed is
only determined at run-time. During the compilation, the compiler checks whether the
method exists and performs type check on the arguments and return type, but does not
know which piece of codes to execute at run-time. When a message is sent to an object to
invoke a method, the object figures out which piece of codes to execute at run-time.
Suppose that Shape class uses many kinds of shapes, such as triangle, rectangle,
circle and so on. We should design a superclass called Shape, which defines the public
interfaces (or behaviors) of all the shapes. For example, we would like all the shapes to
have a method called getArea(), which returns the area of that particular shape. The Shape
class can be written using the abstract class as follow.
- 41 -
abstract public double getArea();
abstract public double getPerimeter();
abstract public void draw();
}
Implementation of these methods is NOT possible in the Shape class, as the actual
shape is not yet known. (How to compute the area if the shape is not known?)
Implementation of these abstract methods will be provided later once the actual shape is
known. These abstract methods cannot be invoked because they have no implementation.
A class containing one or more abstract methods is called an abstract class. An
abstract class must be declared with a class-modifier abstract. An abstract class
CANNOT be instantiated, as its definition is not complete.
Let see our Shape class as an abstract class, containing an abstract method
getArea() as follows:
/**
* This abstract superclass Shape contains an abstract method
* getArea(), to be implemented by its subclasses.
*/
- 42 -
abstract public class Shape {
To use an abstract class, you have to derive a subclass from the abstract class. In
the derived subclass, you have to override the abstract methods and provide
implementation to all the abstract methods. The subclass derived is now complete, and
can be instantiated. (If a subclass does not provide implementation to all the abstract
methods of the superclass, the subclass remains abstract.) We derive subclasses, such as
Triangle and Rectangle, from the superclass abstract Shape. The subclasses override the
getArea() method inherited from the superclass, and provide the proper implementations
for getArea() as follows:
/**
* The Rectangle class, subclass of Shape
*/
public class Rectangle extends Shape {
/** Constructs a Rectangle instance with the given color, length and width */
public Rectangle(String color, int length, int width) {
super(color);
- 43 -
this.length = length;
this.width = width;
}
/** Override the inherited getArea() to provide the proper implementation for rectangle */
/**subclasses must implement an abstract method called getArea() */
@Override
public double getArea() {
return length*width;
}
}
/**
* The Triangle class, subclass of Shape
*/
public class Triangle extends Shape {
// Private member variables
private int base, height;
/** Constructs a Triangle instance with the given color, base and height */
public Triangle(String color, int base, int height) {
super(color);
this.base = base;
this.height = height;
}
/** Returns a self-descriptive string */
@Override
public String toString() {
return "Triangle[base=" + base + ",height=" + height + "," + super.toString() + "]";
}
/** Override the inherited getArea() to provide the proper implementation for triangle */
@Override
public double getArea() {
return 0.5*base*height;
}
}
- 44 -
Example: A Test Driver (TestShape.java)
In our application, create instances of the subclasses such as Triangle and Rectangle,
and upcast them and assigned them to instances of superclass Shape (so as to program
and operate at the interface level), but you cannot create instance of Shape, which does
not have implementation code. This is because the Shape class is meant to provide a
common interface to all its subclasses, which are supposed to provide the actual
implementation.
/**
* A test driver for Shape and its subclasses
*/
public class TestShape {
public static void main(String[] args) {
The beauty of this code is that all the references are from the superclass (i.e.,
programming at the interface level). You could instantiate different subclass instance,
and the code still works. You could extend your program easily by adding in more
subclasses, such as Circle, Square, etc, with ease.
- 45 -
contract, or understanding, or naming convention) to all its subclasses. For example, in
the abstract class Shape, you can define abstract methods such as getArea() and draw().
No implementation is possible because the actual shape is not known. However, by
specifying the signature of the abstract methods, all the subclasses are forced to use
these methods' signature. The subclasses could provide the proper implementations.
Coupled with polymorphism, you can upcast subclass instances to Shape, and
program at the Shape level, i,e., program at the interface. The separation of interface and
implementation enables better software design, and ease in expansion. For example,
Shape defines a method called getArea(), which all the subclasses must provide the
correct implementation. You can ask for a getArea() from any subclasses of Shape, the
correct area will be computed. Furthermore, you application can be extended easily to
accommodate new shapes (such as Circle or Square) by deriving more subclasses.
Rule of Thumb: Program at the interface, not at the implementation. (That is, make
references at the superclass; substitute with subclass instances; and invoke methods
defined in the superclass only.)
Substitutability
A subclass possesses all the attributes and operations of its superclass (because a
subclass inherited all attributes and operations from its superclass). This means that a
subclass object can do whatever its superclass can do. As a result, we can substitute a
subclass instance when a superclass instance is expected, and everything shall work fine.
This is called substitutability.
Notes:
An abstract method cannot be declared final, as final method cannot be
overridden. An abstract method, on the other hand, must be overridden in a
descendant before it can be used.
An abstract method cannot be private (which generates a compilation error). This is
because private methods are not visible to the subclass and thus cannot be
overridden.
- 46 -
(JDK 8 introduces default and static methods in the interface. JDK 9 introduces private
methods in the interface. These will not be covered in this article.)
Similar to an abstract superclass, an interface cannot be instantiated. You have to
create a "subclass" that implements an interface, and provide the actual
implementation of all the abstract methods. Unlike a normal class, where you use the
keyword "extends" to derive a subclass. For interface, we use the keyword
"implements" to derive a subclass.
An interface is a contract for what the classes can do. It, however, does not specify
how the classes should do it. An interface provides a form, a protocol, a standard, a
contract, a specification, a set of rules, an interface, for all objects that implement it. It is
a specification and rules that any object implementing it agrees to follow.
In Java, abstract class and interface are used to separate the public interface of a
class from its implementation so as to allow the programmer to program at the interface
instead of the various implementation.
Interface Naming Convention: Use an adjective (typically ends with "able") consisting
of one or more words. Each word shall be initial capitalized (camel-case). For example,
Serializable, Extenalizable, Movable, Clonable, Runnable, etc.
// constants
static final ...;
All methods in an interface shall be public and abstract (default). You cannot use other
access modifier such as private, protected and default, or modifiers such as static, final.
UML Notation: The UML notation uses a solid-line arrow linking the subclass to a
concrete or abstract superclass, and dashed-line arrow to an interface as illustrated.
Abstract class and abstract method are shown in italics.
- 47 -
Figure 1.9: Normal class, Abstract class, abstract method, interface syntax
/**
* The interface Shape specifies the behaviors
* of this implementations subclasses.
*/
- 48 -
double getArea(); //public abstract double getArea( )
/**
* The subclass Rectangle needs to implement all the abstract methods in Shape
*/
public class Rectangle implements Shape {
// using keyword "implements" instead of "extends"
/** Constructs a Rectangle instance with the given length and width */
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
/**
* The subclass Triangle need to implement all the abstract methods in Shape
*/
public class Triangle implements Shape {
// Private member variables
private int base, height;
/** Constructs a Triangle instance with the given base and height */
public Triangle(int base, int height) {
this.base = base;
this.height = height;
}
- 49 -
// Need to implement all the abstract methods defined in the interface
/** Returns the area of this triangle */
@Override
public double getArea() {
return 0.5 * base * height;
}
}
- 50 -
Figure 1.11: Multiple Inheritances of a class in Java
A subclass, however, can implement more than one interface. This is permitted in
Java as an interface merely defines the abstract methods without the actual
implementations and less likely leads to inheriting conflicting properties from multiple
interfaces. In other words, Java indirectly supports multiple inheritances via
implementing multiple interfaces. Any class that implements multiple interfaces
must provide an implementation for every method defined in each of the interfaces
it implements.
For example,
- 51 -
Figure 1.12: Multiple inheritance of a Circle class
- 52 -
}
/** Returns a self-descriptive string */
@Override
public String toString() {
return "Shape[color=" + color + "]";
}
/** All Shape's concrete subclasses must implement a method called getArea() */
abstract public double getArea();
abstract public double getPerimeter();
/**
* The Movable interface defines a list of public abstract methods
* to be implemented by its subclasses
*/
public interface Movable {
// use keyword "interface" (instead of "class") to define an interface
// An interface defines a list of public abstract methods to be implemented by subclasses
- 53 -
public class Circle extends Shape implements Movable, Resizable {
// extends one superclass but implements multiple interfaces
// private instance variables
private double radius;
private int x, y, xSpeed, ySpeed;
// Constructors
public Circle(double radius) {
this.radius = radius;
super("red");
x = 10; y = 5; xSpeed = 0.5; ySpeed = 0.3;
System.out.println("Construced a Circle with Circle(radius)"); // for debugging
}
/** All Shape's concrete subclasses must implement a method called getArea() */
/** Returns the area of this Circle */
@Override
public double getArea() {
return radius * radius * Math.PI;
}
/** All Shape's concrete subclasses must implement a method called getPeremeter() */
/** Returns the perimeter of this Circle */
@Override
public double getPerimeter() {
return 2 * radius * Math.PI;
}
- 54 -
y - = ySpeed;
}
@Override
public void moveDown(){
y + = ySpeed;
}
@Override
public void moveLeft(){
x - = xSpeed;
}
@Override
public void moveRight(){
x + = xSpeed;
}
- 55 -
contain any implementation, but merely defines the behaviors. As an example, Java's
thread can be built using interface Runnable or superclass Thread.
- 56 -
In this exercise, Shape shall be defined as an abstract class, which contains:
Two protected instance variables color(String) and filled(boolean). The protected
variables can be accessed by its subclasses and classes in the same package. They are
denoted with a '#' sign in the class diagram.
Getter and setter for all the instance variables, and toString().
Two abstract methods getArea() and getPerimeter() (shown in italics in the class
diagram).
The subclasses Circle and Rectangle shall override the abstract methods getArea() and
getPerimeter() and provide the proper implementation. They also override the toString().
// Constructors (overloaded)
/** Constructs a Shape instance with default value for color and filled */
public Shape ( ) { // 1st (default) constructor
this.color = "red";
this.filled = true;
}
/** Constructs a Shape instance with the given color and filled*/
public Shape (String color, Boolean filled) { // 2nd constructor
this.color = color;
this.filled = filled;
}
- 57 -
}
public void setFilled(Boolean filled) {
this.filled = filled;
}
/** All Shape's concrete subclasses must implement a method called getArea() */
abstract public double getArea();
abstract public double getPerimeter();
// Constructors overloaded
public Circle() {
super(); //setting default color=red and filled = true
this.radius = 1.0;
System.out.println("Construced a Circle with Circle()"); // for debugging
}
public Circle(double radius) {
super(); //setting default color=red and filled = true
this.radius = radius;
System.out.println("Construced a Circle with Circle(radius)"); // for debugging
}
public Circle(double radius, String color, Boolean filled) {
super(color, filled); //setting given color and filled value
this.radius = radius;
System.out.println("Construced a Circle with Circle(radius, color)"); // for debugging
}
- 58 -
return this.radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
/** The subclasses must implement the abstract methods: getArea() and getPerimeter() */
/** Returns the area of this Circle */
@Override
public double getArea() {
return this.radius * this.radius * Math.PI;
}
/** Constructs overload – Default constructor - a Rectangle with default length and width */
public Rectangle( ) {
super( );
this.length = 1.0;
this.width = 1.0;
}
/** Constructs a Rectangle instance with the given length and width */
public Rectangle(double width, double length) {
super( );
this.length = length;
- 59 -
this.width = width;
}
/** Constructs a Rectangle instance with the given length, width, color and filled */
public Rectangle(double width, double length, String color, Boolean filled) {
super(color, filled);
this.length = length;
this.width = width;
}
/** The subclasses must implement the abstract methods: getArea() and getPerimeter() */
/** Returns the area of the Rectangle */
@Override
public double getArea() {
return this.length*this.width;
}
/** Returns the perimeter of the Rectangle */
@Override
public double getPerimeter(){
return 2 * ( this.length + this.width);
}
}
- 60 -
The subclass of Rectangle: Square
/**
* The Square class, subclass of Rectangle
*/
public class Square extends Rectangle { // Save as "Square.java"
Write a test class to test the following statements involving polymorphism and
explain the outputs. Some statements may trigger compilation errors. Explain the
errors, if any. A test driver class is as follows:
- 61 -
public class TestShape {
public static void main(String[] args) {
- 62 -
System.out.println(r2);
System.out.println(r2.getArea());
System.out.println(r2.getColor());
System.out.println(r2.getSide());
System.out.println(r2.getLength());
- 63 -
Q6. Can interfaces in Java be used to achieve polymorphism?
a) Yes b) No c) Only if they are abstract d) Only if they extend a class
Q7. How does polymorphism benefit code reusability?
a) By using the same method for different purposes
b) By copying methods from one class to another
c) By allowing methods to run faster
d) By reducing the amount of memory used by the program
- 64 -
Q9. Concepts: Overloading + Overriding + Polymorphism
Question: What is the output of the below code?
package com.markbdsouza.polymorphism;
class Car {
public int getSpeed() { return 100; }
}
class Audi extends Car {
// overridden method as it already exists in the Car class
public int getSpeed() { return 500; }
}
public class PolyTest1 {
public void drive(Car car) {
System.out.println("Using a Car and driving at " + car.getSpeed());
}
// overloaded version of drive method
public void drive(Audi audi) {
System.out.println("Using an Audi and driving at " + audi.getSpeed());
}
public static void main(String[] args) {
// Create 3 objects
Car car = new Car();
Audi audi = new Audi();
Car audiCar = new Audi();
Q10. Write a Java program to create an interface Flyable with a method called fly_obj().
Create three classes Spacecraft, Airplane, and Helicopter that implement the Flyable
interface. Implement the fly_obj() method for each of the three classes. Then write a test
driver class that create object for each class and call the fly_obj() method that will display
the type of aircraft flying, e.g. ―Airplane is flying‖ for Airplane object. Use the following
UML diagram to implement it.
- 65 -
Figure Q10: Polymorphism and Interface
- 66 -
3.3 Composition Relationship Between Classes
• B is a permanent part of A
• A is made up of Bs
• A is a permanent collection of Bs
For example, consider ―a Book is written by one Author‖, where the Book class has a
composite relationship with the Author class who write the book. The Author object
cannot exist without the Book object.
Figure 1.14: The composition relationship between Book and Author classes
- 67 -
Let's design a Book class, for the case that a book is written by one (and exactly one)
author. The composition relation between Book class and the Author class shown in
Figure 1.14. UML notation for composition relationship is represented as a filled
diamond drawn at the composite-end.
In this book and author example, as soon as a book object is created, the author
object is also created and as soon as the book object is destroyed, the author object
for that book is also destroyed. That is, the life of the component (author object) is
the same as the aggregate (book object).
/**
* The Book class models a book with one (and only one) author.
*/
public class Book {
// The private instance variables
private String name;
private Author author;
private double price;
private int qty;
- 68 -
this.qty = qty;
}
- 69 -
Public getters/setters: getName(), getEmail(), setEmail(), and getGender().
(There are no setters for name and gender, as these properties are not designed to be
changed.)
A toString() method that returns "name (gender) at email", e.g., "Tan Ah Teck (m) at
ahTeck@somewhere.com".
/**
* The Author class model a book's author.
*/
public class Author {
// The private instance variables
private String name;
private String email;
private char gender; // 'm' or 'f'
- 70 -
return name + " (" + gender + ") at " + email;
}
}
/**
* A test driver program for the Book class.
*/
public class TestBook {
public static void main(String[] args) {
// We need an Author instance to create a Book instance
Author smith = new Author("Smith Will", "smith@somewhere.com", 'm');
System.out.println(smith); // Author's toString()
// Smith Will (m) at smith@somewhere.com
- 71 -
3.3.3 Summary of Composition Relationship:
A class (parent) contains a reference to another class (child).
The child class doesn't exist without the parent class.
Deleting the parent class will also delete the child class.
A class can also include a reference of the id property of another class instead of
an instance of another class.
Figure 1.15: The composition relationship between Book and Author class
for a book can be written by one or more author
- 72 -
The toString() method shall return
"Book[name=?,authors={Author[name=?,email=?,gender=?],......},price=?,qty=?]".
To store one or more Authors for a Book, use Java Generic type Array List:
import java.util.List;
import java.util.ArrayList;
/**
* The Book class models a book with one or more authors.
*/
import java.util.List;
import java.util.ArrayList;
/** Constructs a Book instance with the given author with default price */
public Book(String name, List<Author> authors, double price) {
this.name = name;
this.authorLst = authors;
this.price = price;
this.qty = 0;
}
- 73 -
// Getters and Setters
/** Returns the name of this book */
public String getName() {
return name;
}
- 74 -
The Author class
/**
* The Author class model a book's author.
*/
public class Author {
// The private instance variables
private String name;
private String email;
private char gender; // 'm' or 'f'
// The public getters and setters for the private instance variables.
// No setter for name and gender as they are not designed to be changed.
/** Returns the name */
public String getName() {
return name;
}
/** Returns the gender */
public char getGender() {
return gender;
}
/** Returns the email */
public String getEmail() {
return email;
}
/** Sets the email */
public void setEmail(String email) {
this.email = email;
}
/**
* A test driver program for the Book class.
*/
public class TestBook {
public static void main(String[] args) {
- 75 -
//Create Author instances for book
List<Author> authors = new ArrayList<Author> ();
Q1. Consider a Unicycle class and a Tire class. A Unicycle requires a Tire. In other
words:
(a) The Tire HAS-A Unicycle (b) The Tire voids the Unicycle
(c) The Unicycle polymorphs the Tire (d) The Unicycle HAS-A Tire
Q2. Examine the following code. There are two classes: Page and Book. Which best
describes their relationship?
(a) Book HAS-A Page (b) Page HAS-A Book
(b) Page inherits from Book (c) This code is invalid
Q3. Which one of the following relationships describes the OO design concept of
―composition‖?
(a) is-a (b) is-a-kind-of (c) has-a (d) composed-as
- 76 -
Q4. Composition in java is implemented by ______________.
(a) a class contains instances of other classes to use their abilities
(b) a class references to objects to use link.
(c) a class inherits traits from a parent class
(d) a class defines the attributes of other classes
Q6. Consider the following java classes: Room and House classes. Is it the composition
implementation in java? Explain Why? Draw a UML diagram for these two classes
relationship.
class Room {
class House {
private List<Room> rooms;
public House() {
this.rooms = new ArrayList<>();
rooms.add(new Room("Living Room"));
rooms.add(new Room("Bedroom"));
rooms.add(new Room("Kitchen"));
}
- 77 -
Q7. Let's consider a real-world example: a Car class that is made up of Engine and Tyre
classes. In this example, the Car class contains an Engine object and an array of
Tyre objects. If the Engine object or Tyre object gets destroyed then Car object
cannot be completed, i.e., Car can not exist without these components. The
relationship between the Car and its components is composition.
When you create a new Car instance, it immediately produces an instance of Engine
and four cases of Tyre. This structure ensures the Car class has all its necessary
components.This composition relationship emphasizes the concept that a car has an
engine and has tires, emphasizing the importance of the link.
(a) Draw UML composition diagram of Car class made up of Engine and Tyre classes.
(b) Implement composition relationship of Car class made up of Engine and Tyre classes
in java.
- 78 -
3.4 Aggregation Relationship between Classes
Aggregation is another category of "has a" relationship where a class can contain
other classes as properties but those classes can exist independently.
Composition and Aggregation both are "has a" relationship but in the composition
relationship, related classes don't exist independently whereas, in the aggregation,
related classes exist independently.
For example, the Student has enrolled in number of courses and Student and Course
have ―has a‖ relationship. A Student class contains the Course class instance as a property
to form the composition relationship. However, even if the Student object is deleted, the
Course object will still exist. That is, the related classes exist independently and both
the classes can exist independently and so it is called an aggregation relationship.
Let's design for a case that a Student has enrolled in number of courses. The
aggregation relationship between a Student class and the Course class is shown in Figure
1.16. UML notation for aggregation relationship is represented as an unfilled diamond
drawn at the aggregate-end.
- 79 -
In this example aggregation relationship, the Student object and the Course object
will exist independently. The Student class can also contain CourseId property instead of
Course instance.
/**
* The Student class models a student has enrolled in number of courses.
*/
import java.util.List;
import java.util.ArrayList;
- 80 -
public void setStudentId (int Id) {
this.studentId = Id;
}
/** Returns the name of this student */
public String getName() {
return this.stdname;
}
public void setName(String name) {
this.stdname=name;
}
/** Returns the address of this student */
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
/** Return the enrolled course List of this student*/
public List<int> getEnrollCourses() {
return this.enrollCourses; // return an Array List of enrolled Course instances
}
public void setEnrollCourse(int enrollCourse) {
this.enrollCourses.add(enrollCourse);
}
/** Returns a self-descriptive String */
public String toString() {
String str = "Student[ Id= '" + studentId + "' , " + stdname + " , " + address +
" , Enrolled Course ID = { ";
for (int cid : enrollCourses) {
str = str + cid + " , ";
}
str = str + " } ]" ;
return str; // Student.toString()
}
/**
* The Course class models course information.
*/
- 81 -
private int courseId;
private String coursename;
private String topic;
private String startDate;
private String endDate;
- 82 -
}
/** Returns a self-descriptive String */
public String toString() {
String str = "Course[ CId= '" + courseId + " , " + coursename + " , " +
topic + " , " + startDate + " , " + endDate + " ]" ;
return str; // course.toString()
}
}
/**
* The Course Catalog class models list of courses information.
*/
import java.util.List;
import java.util.ArrayList;
- 83 -
The Test driver Class
/**
* A test driver program for the Book class.
*/
public class TestCourse {
public static void main(String[] args) {
- 84 -
s3.setEnrollCourse(1201);
s3.setEnrollCourse(1212);
s3.setEnrollCourse(1223);
s3. setEnrollCourse(1214);
- 85 -
(b) cannot exist independently without the larger one
(c) can only exist with the main one
(d) none of the above
Q4. Consider the following classes. A Unicycle class and a Tire class. A Unicycle
requires a Tire. In other words:
Q5. Examine the following code. There are two classes: Page and Book. Which best
describes their relationship?
Q6. Consider the following java classes: Department and University classes. Is it the
aggregation implementation in java? Explain Why? Draw a UML diagram for the
relationship between Department and University classes.
class Department {
private String name;
class University {
private String name;
private List<Department> departments;
- 86 -
}
Q7. Let‘s understand the aggregation in Java with the example of a Country class and a
Sportsperson class.
• Country class is defined with a name and other attributes like size, population,
capital, etc, and a list of all the Sportspersons that come from it.
• A Sportsperson class is defined with a name and other attributes like age, height,
weight, etc.
A Country object has-a list of Sportsperson objects that are related to it. Note that a
sportsperson object can exist with its own attributes and methods, alone without the
association with the country object. Similarly, a country object can exist independently
without any association to a sportsperson object. In, other words both Country and
Sportsperson classes are independent although there is an association between them.
Hence Aggregation is also known as a weak association.
(a) Draw UML Aggregation diagram of Country class and a Sportsperson class.
(b) Implement Aggregation relationship between Country class and a Sportsperson class
for the case of a Country object has-a list of Sportsperson objects that are related to it
in java.
- 87 -
3.5 Associations Relationship Between Classes
Figure 1.18 shows the facts that a student takes many modules, an instance of
Student, Jack, links with three instances of Module: CS101, CS102, M011. Link defines
the relationship between two or more objects and a link is considered as an instance of an
association. This means an association is a group of links that relates objects from the
same classes. Figure 1.18(a) and (b) show the state of the object diagrams that are
legitimate instances of association in the corresponding class diagram given in Figure
1.17.
- 88 -
(a) A student may takes zero or more modules (b) A module may be taken zero or more students
Figure 1.18: The link between the instances of the association class
The multiplicity specifies how many objects an instance of the class at the other
end of the association can be linked to. Figure 1.19 shows the ‗Takes‘ association with
role names and multiplicity annotations (‗*‘) is added at both end. This means that
diagram specifies that a student takes more than zero or more modules and that a module
can be taken by zero or more students.
The multiplicity is shown on each side of the association relation. It indicates how
many instances of one class are associated with the other (or) specifies how many objects
of the opposite class an object can be associated with. It is noted as an individual number
or a range of the minimum and maximum values (syntax: number or min.. max), e.g. 1..5.
Multiplicity Notation
zero 0 or 0..0
zero or one 0..1
exactly one 1
one or more 1..*
- 89 -
3.5.4 Navigability in Relations
The navigability in association relation is used to record the fact that certain links in
the system could only be traversed, or messages sent, in one direction. This design
decision can be shown on a class diagram by writing a navigation arrow on the
association to show the required direction of traversal. It has been decided that an
association only needs to be supported in one direction. It is like one way
communication. An association with no arrowheads, such as the one in Figure 1.19, is
normally assumed to be navigable in both directions.
For example, Figure 1.20 shows an association between Account and DebitCard class
that is modeling the fact that every account can have a debit card issued for use with the
account. It shows an association that is only to be implemented uni-directionally. This
means that an Account object holds a reference to at most one DebitCard object and
association would correspond to the requirement that only one card was ever issued for
a particular account.
In other words, if the design decision was decided that there was no need for
DebitCard object to explicitly store a reference to the related Account object because
an Account object holds only one DebitCard object that is issued for, then, the association
might be defined to be navigable in only one direction, as shown in Figure 1.20.
a pointer or
reference instance variable or
as a method argument.
- 90 -
Implementation strategies for mapping associations‘ relation between classes to the
code can be done in following ways:
1. Unidirectional Association
• Unidirectional Optional Associations
• Unidirectional One-to-one associations / exactly one associations
• Unidirectional One-to-many associations
2. Bidirectional Association
• Bidirectional one-to-one and optional association
• Bidirectional, one-to-many association
• Bidirectional qualified association
3. Association Classes
4. Qualified Association
5. Constraints
Let‘s study each implementation strategies for class association with examples below.
An object might store another object in a field. This design decision decided that an
association between classes only needs to be supported in one direction. The following
sections discuss the cases where the multiplicity of the directed association is
• ‗optional‟,
• ‗exactly one‟ and
• ‗many‟.
- 91 -
Figure 1.21: An optional association
This uni-directional optional association can be implemented using a simple
reference variable, as shown below code. In unidirectional association, you can traverse
from an occurrence of one class to an occurrence of the associated class (as indicated by
the direction of the arrow) but not in a reverse direction. That means Account class holds
a link to the DebitCard Class but not in a reverse direction.
This allows an Account object to hold a reference to at most one DebitCard object.
Cases where an account is not linked to a card are modeled by allowing the reference
variable to hold a null reference. This implementation therefore provides exactly the
multiplicity of zero or one requirements specified by the association. The code design
for this condition is as follow:
- 92 -
theCard = null; public float getAmtLimit() {
} return this.amountLimit;
…. }
private String ownerName; …
private String accNo; public String getCardNo(){
private float balAmt; return this.cardno;
Account(String name, String accno, float }
bal){ public String getIssueDate(){
this.ownerName = name; return this.issueDate;
this.accNo = accno; }
this.balAmt = bal; …
this.theCard = null; }
}
… getter() & setter() for each attribute
}
In the above example, only the Account class holds a reference variable of
DebitCard class and also uses the DebitCard class as a parameter of methods in
setCard() method. This allows when an Account object is created, it holds a reference
to at most one DebitCard object. There is no Account class reference is stored in the
DebitCard class. So the system could only be traversed, or messages sent, in one
direction from Account object to the DebitCard object. The implementation design
for the account class, debit card class and test driver class for unidirectional optional
association is as follow:
/**
* The Account class holds a reference variable (at most one) the DebitCard object of DebitCard
* class.
*/
- 93 -
}
public String getOwnerName(){
return ownerName;
}
public String getAccNo(){
return accNo;
}
public float getBalAmt(){
return balAmt;
}
public DebitCard getCard() {
return theCard;
}
public void setCard (DebitCard card) {
// an account object to hold a reference to at most one debit card object
theCard = card;
}
public void removeCard() {
// multiplicity of zero or one variable to hold a null reference.
theCard = null;
}
public String toString(){
String str = "Account[ accNo + " , " + ownerName + " , " + balAmt + " , " +
" , DebitCard { " + theCard.getCardNo() + " , " +
theCard.getIssueDate() +
" , " + theCard.getAmtLimit() + "} ] " ;
return str;
}
}
/**
* The DebitCard class holds no Account class reference. The messages sent, in one direction
* from Account object to the DebitCard object.
*/
- 94 -
this. cardno = cardno;
this.issueDate = issueDate;
}
public void setAmtLimit(float amt){
this.amountLimit = amt;
}
public float getAmtLimit() {
return this.amountLimit;
}
public String getCardNo(){
return this.cardno;
}
public String getIssueDate(){
return this.issueDate;
}
public String toString(){
return "DebitCard [ " + cardno + " , " + issueDate + " , " + amountLimit + " ] " ;
}
}
- 95 -
acc1.setCard(DbCard3); // debit card 3 is also issued for acc1.
}
}
- 96 -
theCard = dcard;
}
public DebitCard getCard() {
return theCard ;
}
public void setCard(DebitCard card) {
if (theCard != null) { // Immutable associations
// throw ImmutableAssociationError
}
theCard = card ;
}
….
}
- 97 -
Figure 1.22: A one-to-one association
- 98 -
This code implements the association between account and guarantor objects as
an immutable association. If the association was mutable, so that the guarantor of an
account could be changed, a suitable function could be added to this class provided that,
like the constructor, it checked that the new guarantor reference was non-null.
/**
* The Account class holds a reference variable (exactly one) the Guarantor object.
*/
public class Account{
}
public Guarantor getGuarantor() {
return theGuarantor ;
}
public void setGuarantor(Guarantor g) {
if (g == null) {
// throw NullLinkError
}
theGuarantor = g ;
}
public String getOwnerName(){
return ownerName;
}
public void setOwnerName(String name){
this.ownerName = name;
}
public String getAccNo(){
return accNo;
}
public void setAccNo(String acc){
this. accNo = acc;
- 99 -
}
public float getBalAmt(){
return balAmt;
}
public void setBalAmt(float bal){
this.balAmt = bal;
}
public String toString(){
String str = "Account[ accNo + " , " + ownerName + " , " + balAmt + " , " +
theGuarantor.toString() + " ] " ;
return str;
}
}
/**
* The Guarantor class holds no Account class reference. The messages sent, in one direction
* from Account object to the Guarantor object.
*/
- 100 -
The Test Driver Class
- 101 -
Figure 1.23: An association with multiplicity ‗many‘
The simplest and most reliable way to implement such an association is to make use
of a suitable container class from a class library. For simple implementations in Java,
the Vector class is a natural choice for this purpose. A skeleton implementation of the
manager class using vectors is given below. The class declares a vector of accounts as a
private data member, and the functions to add and remove accounts from this collection
simply call the corresponding functions defined in the interface of the vector class.
/**
* The Manager class holds numbers of Account objects. The messages sent, in one direction
* from Manager object to the Account objects.
*/
public class Manager {
- 102 -
public String getEmail(){
return this.email;
}
public void addAccount(Account acc) {
theAccounts.addElement(acc) ;
}
public void removeAccount(Account acc) {
theAccounts.removeElement(acc) ;
}
Part of the semantics of a class diagram such as Figure 1.23 is that there can be at
most one link between a manager and any particular account. In this implementation,
however, there is no reason why many pointers to the same account object could not be
stored in the vector held by the manager. This is a further example of the inability of
programming languages to capture in declarations every constraint expressible in UML‘s
class diagrams. A correct implementation of the addAccount function should check
that the account being added is not already linked to the manager object.
/**
* The Account class holds no reference Manager object. The message sent only
* from manager object to account objects.
*/
public class Account {
- 103 -
Account(String name, String accno, float bal){
this.ownerName = name;
this.accNo = accno;
this.balAmt = bal;
}
public String getOwnerName(){
return this.name;
}
public String getAccNo(){
return this.accNo;
}
public float getBalAmt(){
return this.balAmt;
}
public void toString(){
System.out.prinln("Account [ " + this.accNo + " , " + this.name + " , " + this.balAmt
+ " ] " );
}
}
- 104 -
3.5.7 Bidirectional Association
Consider the association between accounts and guarantors, the required property can
be stated informally as ‗the guarantor of an account must guarantee that same
account‟. Figure 1.24(b) violates this: the top account object holds a reference to a
guarantor object, which in turn holds a reference to a completely different account. These
two references cannot be understood as being an implementation of a single link. It
should be clear from this example that referential integrity cannot be ensured by
simply giving appropriate definitions of data members in the relevant classes.
- 105 -
Figure 1.25: A bidirectional association
- 106 -
This implementation certainly provides the data members necessary to store the
bidirectional links, but the methods maintain the two directions of the link independently.
For example, to create a link between a new debit card and an account two separate
operations are required, first to create the card and second to link it to the account.
The link from card to account is created when the card itself is created. Code to
implement this might be as follows.
- 107 -
public void removeCard() {
theCard = null; }
}
/**
* The Account class holds a reference variable (at most one) the DebitCard object of DebitCard
* class. This association is a combination of a mutable and optional association
* in the left-to-right direction with an immutable association in the DebitCard.
*/
- 108 -
theCard.setCardNo(cno);
theCard.setIssueDate(idate);
theCard.setAmtLimit(10000.00); // default amount is set
}
public void removeCard() {
// multiplicity of zero or one variable to hold a null reference.
theCard = null;
}
public String toString(){
return "Account[ " + accNo + " , " + ownerName + " , " + balAmt + " ] " ;
}
}
/**
* The DebitCard class holds exactly one Account object reference.
* The DebitCard object is created from the Account object.
* This association is a combination of a mutable and optional association in the left-to-right
* direction with an immutable association in the DebitCard class.
*/
public class DebitCard {
public DebitCard(Account a) {
this.theAccount = a;
}
public Account getAccount() {
return this. theAccount;
}
public void setCardNo(String cno){
this.cardno = cno;
}
public String getCardNo(){
return this.cardno;
}
public void setAmtLimit(float amt){
this.amountLimit = amt;
}
public float getAmtLimit() {
return this.amountLimit;
- 109 -
}
public String getIssueDate(){
return this.issueDate;
}
public void setIssueDate(String idate){
this.issueDate = idate;
}
public String toString(){
return "DebitCard [ " + cardno + " , " + issueDate + " , " + amountLimit +
theAccount.toString() + " ] " ;
}
}
- 110 -
and it will be reasonable for the card class to provide an operation to change the account
that the card is associated with.
The implementation sketched below follows the strategy given above of allocating
the responsibility of manipulating references exclusively to the account class. As
explained above, this makes consistency easier to guarantee, as there is only one place
where changes are being made to links. This means that the card class must call
functions in the account class to update references, as shown in the implementation of
the changeAccount operation given below.
/**
* The Account class holds a reference variable (at most one) the DebitCard object .
* This association is mutable in both directions. Both Account and DebitCard can change.
*/
- 111 -
this.ownerName = name;
this.accNo = accno;
this.balAmt = bal;
this.theCard = null;
}
public String getOwnerName(){
return ownerName;
}
public String getAccNo(){
return accNo;
}
public float getBalAmt(){
return balAmt;
}
public DebitCard getCard() {
return theCard;
}
public void addCard(DebitCard dcard) {
// an account object to hold a reference to at most one debit card object
theCard = dcard;
}
public void removeCard() {
// multiplicity of zero or one variable to hold a null reference.
theCard = null;
}
public String toString(){
return "Account[ " + accNo + " , " + ownerName + " , " + balAmt + " ] " ;
}
}
/**
* The DebitCard class holds exactly one Account object reference.
* This association is mutable in both directions. Debit Card can change the Account.
*/
public class DebitCard {
- 112 -
this.theAccount = acc;
}
public Account getAccount() {
return this. theAccount;
}
public void changeAccount(Account newacc) {
if (newacc.getCard() != null) {
// throw AccountAlreadyHasACard
}
theAccount.removeCard() ;
newacc.addCard(this) ;
}
public void setCardNo(String cno){
this.cardno = cno;
}
public String getCardNo(){
return this.cardno;
}
public void setAmtLimit(int amt){
this.amountLimit = amt;
}
public float getAmtLimit() {
return this.amountLimit;
}
public String getIssueDate(){
return this.issueDate;
}
public void setIssueDate(String idate){
this.issueDate = idate;
}
public String toString(){
return "DebitCard [ " + cardno + " , " + issueDate + " , " + amountLimit + " , " +
theAccount.toString() + " ] " ;
}
}
- 113 -
Account acc3 = new Account("Mary Brown", "Acc003", 50000);
- 114 -
Figure 1.26: An immutable one-to-one association
- 115 -
The declarations in bidirectional implementation introduce a certain condition. When
an account is needed to create, a guarantor must create first. The guarantor will
create an account that guarantee to that account. This could be achieved as shown in the
following code.
In order to create the required link, one of the objects must be created using a default
constructor and the link subsequently established with a suitable ‗set‘ operation. It will
then be necessary to check explicitly that the constraints on the association are
maintained at all times.
/**
* The Account class holds a reference variable (exactly one) the Guarantor object.
* Without the guarantor, no account can be created. Immutable Association in Both Directions.
*/
public class Account{
}
public Guarantor getGuarantor() {
return theGuarantor ;
}
public String getOwnerName(){
return ownerName;
}
public void setOwnerName(String name){
this.ownerName = name;
- 116 -
}
public String getAccNo(){
return accNo;
}
public void setAccNo(String acc){
this. accNo = acc;
}
public float getBalAmt(){
return balAmt;
}
public void setBalAmt(float bal){
this.balAmt = bal;
}
public String toString(){
String str = "Account[ accNo + " , " + ownerName + " , " + balAmt + " , " +
theGuarantor.toString() + " ] " ;
return str;
}
}
/**
* The Guarantor class holds an Account reference.
* Each guarantor can only guarantee one account. Immutable Association in Both Directions.
* The responsibility for maintaining the association to the guarantor class to create a new
* account that it grantee for. Without the guarantor, no account can be created.
*/
public class Guarantor {
- 117 -
}
public String getEmail(){
return this.email;
}
public String toString(){
return " Guarantor [ " + name + " , " + email + " ] " ;
}
}
//First create guarantor objects that will automically create each account object
Guarantor g1 = new Guarantor("Jame Hanlen", "jhanlen@gmail.com");
Guarantor g2 = new Guarantor("Horgan Will", "mwill@gmail.com");
- 118 -
(2) Bidirectional, One-to-Many Association
The bidirectional implementation of one-to-many associations raises no significantly
different problems from those discussed above. For example, Figure 1.27 shows an
association specifying that customers can hold many accounts, each of which is held
by a single customer.
As before, the customer class could contain a data member to store a collection of
pointers to accounts, and additionally each account should store a single pointer to a
customer. It would seem most sensible to give the customer class the responsibility of
maintaining the links of this association, though in practice this decision would only be
made in the light of the total processing requirements of the system.
- 119 -
public void removeAccount(Account acc) { return this.theOwner ;
theAccounts.removeElement(acc); }
} }
}
The implementation sketched below follows the strategy allocating the responsibility
of manipulating references exclusively to the customer class.
/**
* The Account class holds a reference variable (exactly one) the Customer object.
* An immutable association with multiplicity 'one' Customer object and a mutable association
* with multiplicity 'many' Account objects.
*/
public class Account{
public Account(Customer c) {
if (c == null) {
// throw NullCustomerError
}
theOwner = c ;
}
public Customer getOwner() {
return this.theOwner ;
}
public String getAccNo(){
return this.accNo;
}
public void setAccNo(String acc){
this.accNo = acc;
}
public float getBalAmt(){
return balAmt;
}
public void setBalAmt(float bal){
this.balAmt = bal;
}
public String toString(){
String str = "Account[ accNo + " , " + balAmt + " , Owner { " +
- 120 -
theOwner.getName() + " , " + theOwner.getEmail() + " }] " ;
return str;
}
}
/**
* The Customer class holds multiple Account objects of its own.
* The customer class takes the responsibility of maintaining the links of this association,
*/
public class Customer {
- 121 -
}
System.out.println(c1.toString());
System.out.println(c2.toString());
//Customer[ Jame Hanlen, jhanlen@gmail.com, Accounts{ [Acc001, 10000.00],
// [Acc002, 30000.00]} ]
//Customer[Horgan Will, mwill@gmail.com, Accounts{ [ Acc003, 50000.00] } ]
System.out.println(acc1.toString());
System.out.println(acc2.toString());
System.out.println(acc3.toString());
}
}
- 122 -
(3) Bidirectional, Many-to-Many Association
The most general case of many-to-many associations does not raise any new issues of
principle. For example, consider the relationship in Figure 1.28, which allows a number
of signatories (participants), people who are authorized to sign cheques, to be defined
for each account. At the same time, a person can be a signatory for any number of
accounts. This association may need to be traversed in both directions, and is
mutable at both ends.
- 123 -
Figure 1.29: Reifying a many-to-many association
- 124 -
private Account theAccount ;
Figure 1.30: Reifying a many-to-many association to one-to-many association with a new class
/**
* The Account class holds a number of signatories (participants) objects.
* A mutable association at both ends and traverse in both directions
* The association with multiplicity 'one-to-many' Account object and Customer objects.
* The account class is responsible for maintaining links between customers and accounts.
- 125 -
*/
public class Account {
if (newCustomer) {
Signatory s = new Signatory( c, this ) ;
theSignatories.addElement(s) ;
c.addSignatory(s) ;
}
}
public String getAccNo(){
return this.accNo;
}
public void setAccNo(String acc){
this.accNo = acc;
}
public float getBalAmt(){
return balAmt;
}
public void setBalAmt(float bal){
this.balAmt = bal;
}
- 126 -
public String toString(){
String str = "Account[ accNo + " , " + balAmt + " , Signatories { " ;
for(Signatory s : theSignatories){
str = str + s.getCustomer().getName() + " , " ;
}
str = str + " }] " ;
return str;
}
}
/**
* The Customer class holds multiple Signatories objects
* who are authorized to sign cheques defined for each account
*/
- 127 -
}
/**
* The Signatory class holds a Customer object and an Account object.
* The Customer who is authorized to sign cheques defined for each account
*/
//assign the Customer who is authorized to sign cheques defined for each account
- 128 -
// the Account class takes the responsibility of maintaining the links of this association
acc1.addSignatory(c1);
acc1.addSignatory(c2);
acc2.addSignatory(c1);
acc3.addSignatory(c1);
System.out.println(c1.toString());
System.out.println(c2.toString());
//Customer[ Jame Hanlen, jhanlen@gmail.com, Signatories{ Acc001, Acc002,Acc003}]
//Customer[Morgan Will, mwill@gmail.com, Signatories{ Acc001 } ]
System.out.println(acc1.toString());
System.out.println(acc2.toString());
System.out.println(acc3.toString());
Some association links can be described with the attributes. Consider, for
example, the association between students and the modules, the class diagram shown in
figure 1.31(a) models that a student takes a module and system needs to record all the
marks for modules that students took. Here the ‗mark‘ attribute only makes sense if
student takes the module and student may take many modules and it is necessary to
record more than one mark for each student. So it‘s not simply an attribute of either class
of Student and Module. Therefore „mark‟ attribute is associated with the link between
two objects rather than with either of the individual objects.
Association class is shown as an association and a class icon, linked by a dashed line.
Figure 1.31(b) shows the association class enabling a single mark to be recorded every
time a student takes a module. This replaces the association ―Takes‖ defined in figure
1.31(a).
- 129 -
(b) An association class to store ‗marks‘ attributes for both class association
Figure 1.31: Association class example
A further consideration in the implementation of this association is that the two class
diagrams in Figure 1.31 and Figure 1.32 in fact have slightly different meanings. Figure
1.31 states that a student can only take a module once, as only one link is permitted
between any given pair of module and student objects. With Figure 1.32 on the other
hand, there is nothing to prevent a student being linked to the same module many times,
through the mediation of different registration instances. An implementation of Figure
1.32 should bear this in mind and check that the appropriate constraints are satisfied.
A common strategy in this case is to transform the association class into a simple
class linked to the two original classes with two new associations, as shown in Figure
1.32. In this diagram, the fact that many students can take a module is modeled by
stating that the module can be linked to many objects of the registration class, each of
which is further linked to a unique student, namely the student for whom the registration
applies.
A possible outline implementation for this operation is shown below. The
implementation of the registration class is very simple. It must store a reference to the
linked student object and the mark gained by that student. As this class is manipulated
exclusively by the module class, we do not bother to provide an operational interface for
- 130 -
it. The relevant parts of the definition of the module class are sketched out below. The
very simple implementation given in this example is given below.
For the case of the association class in Figure 1.31, the natural approach seems to be to
allow the registration class to maintain the links between registrations, students and
modules. For example, these links will be created when a student registers for a module,
and so will be created when a registration object is created. The following code defines
the classes with methods permitting a class list to be printed off for a module, and a list of
modules taken for a student, thus demonstrating that the links can be traversed in both
directions.
Figure 1.33: The association class – Registartion maintain the links between student and
module class
- 131 -
public class Student {
public Student(String n) {
name = n ;
}
public String getName() {
return name ;
}
public void addRegistration(Registration r) {
registrations.add(r) ;
}
public void ModuleList() {
Enumeration enum = registrations.elements() ;
System.out.println("Student " + name + " takes modulelist [ ") ;
while (enum.hasMoreElements()) {
Registration r = (Registration) enum.nextElement() ;
System.out.print(r.getModule().getCode() + " , ") ;
}
System.out.println(" ] ");
}
}
public Module(String c) {
mcode = c ;
}
public String getCode() {
return mcode ;
}
public void addRegistration(Registration r) {
registrations.add(r) ;
}
public void classList() {
Enumeration enum = registrations.elements() ;
System.out.print("Module – " + mcode + " , Student List [" );
while (enum.hasMoreElements()) {
- 132 -
Registration r = (Registration) enum.nextElement() ;
System.out.print(r.getStudent().getName() + " , " ) ;
}
System.out.println(" ] ") ;
}
}
/* The registration class maintain the links between registrations, students and modules. */
public class Registration {
- 133 -
Student s1 = new Student(“Jamy”);
Student s2 = new Student(“Halen”);
Student s3 = new Student(“Morgan”);
Student s4 = new Student(“Will”);
- 134 -
3.5.9 Qualified Association
There is one subtle point about qualified associations: the change in multiplicity. For
example, as shown in Figure 1.34(a) vs. (b), qualification reduces the multiplicity at
the target end of the association, usually down from many to one, because it implies the
selection of usually one instance from a larger set.
For simplicity, we assume that the association is to be given a unidirectional
implementation. For example, to retrieve information about accounts given only an
account number, if the bank simply held a pointer to each of its accounts, this operation
could be implemented by searching through all the pointers until the matching account
was found. This could be very slow, however, and a better approach might be to
maintain a lookup table mapping account numbers to accounts, as illustrated in Figure
1.35. As account numbers can be ordered, for example, this gives the possibility of using
much more efficient search techniques. This kind of structure is relatively easy to
implement, the major issue being to decide how to implement the lookup table. In Java,
an obvious and straightforward choice is to use a utility class such as
java.util.HashTable.
- 135 -
Figure 1.35: How qualifiers can be implemented using a lookup table mapping account
numbers to accounts
Qualifiers, then, can still be handled using the simple model that implements
individual links by references. The implementation given above treats a qualified
association in much the same way as an association with multiplicity ‗many‟. The most
significant difference is in the data structure used to hold the multiple pointers at
one end of the association.
In the case of the qualified association between bank and accounts, shown in Figure
1.34(b), it is likely that the Bank class would have the responsibility for maintaining the
links between objects. The code below contains a method in the Bank class to create an
account; the bank field in the new account is thereby filled by the bank creating it.
- 136 -
The Account Class
/* The Bank class qualified associations with the Account class in multiplicity ‘many’.
* The Bank class maintains a lookup table mapping account numbers to accounts.
*/
public class Bank
{
private String name;
private Hashtable theAccounts ;
- 137 -
public void createAccount(int n) {
addAccount(new Account(n, this)) ;
}
public void addAccount(Account a) {
theAccounts.put(new Integer(a.getNumber()), a);
}
public void removeAccount(int accno) {
theAccounts.remove(new Integer(accno));
}
public Account lookupAccount(int accno) {
return (Account) theAccounts.get(new Integer(accno));
}
}
- 138 -
3.5.10 Difference between Association, Aggregation, Composition in Java
- 139 -
Composition - I own an object and I am responsible for its lifetime. When
Foo dies, so does Bar.
Aggregation - I have an object which I've borrowed from someone else. When
Foo dies, Bar may live on.
By carefully understanding the diagram above and the examples provided above we can
conclude that
• association is the weakest relationship between classes,
• aggregation is somewhat stronger relationship than association and
• composition is the strongest relationship
Association examples:
• Two separate classes Bank and Employee are associated through their Objects. Bank
can have many employees, So, it is a one-to-many relationship.
• Similarly, every city exists in exactly one state, but a state can have many cities,
which is a ―many-to-one‖ relationship.
• Lastly, the association between a teacher and a student, multiple students can be
associated with a single teacher and a single student can also be associated with
multiple teachers but both can be created or deleted independently. This is a ―many-
to-many‖ relationship.
- 140 -
// Class 1 : Bank class
class Bank {
// Attributes of bank
private String bankName;
private Set<Employee> employees;
// Attributes of employee
private String name;
- 141 -
// Class 3 : Association between both the classes in main method
class AssociationExample {
Example, There is an Institute which has no. of departments like CSE, EE. Every
department has no. of students. So, we make an Institute class that has a reference to
Object or no. of Objects (i.e. List of Objects) of the Department class. That means
Institute class is associated with Department class through its Object(s). And Department
- 142 -
class has also a reference to Object or Objects (i.e. List of Objects) of the Student class
means it is associated with the Student class through its Object(s).
// Attributes of Student
private String studentName;
private int studentId;
- 143 -
}
public List<Student> getStudents() {
return students;
}
// Attributes of Institute
private String instituteName;
private List<Department> departments;
- 144 -
// Class 4 : main class
class AggregationExample {
// main driver method
public static void main(String[] args) {
// Creating independent Student objects
Student s1 = new Student("Parul", 1);
Student s2 = new Student("Sachin", 2);
Student s3 = new Student("Priya", 1);
Student s4 = new Student("Rahul", 2);
- 145 -
Composition is a restricted form of Aggregation in which two entities are highly
dependent on each other. It represents part-of relationship. In composition, both
entities are dependent on each other. When there is a composition between two entities,
the composed object cannot exist without the other entity.
It simply means that one of the objects is a logically larger structure, which
contains the other object. It means that if we destroy the owner object, its members
also will be destroyed with it. For example, if the building is destroyed the room is
destroyed. But, note that it doesn‘t mean, that the containing object can‘t exist without
any of its parts. For example, if we tear down all the rooms inside a building, the building
will still exist.
Example, a company can have no. of departments. All the departments are part-of the
Company. So, if the Company gets destroyed then all the Departments within that
particular Company will be destroyed, i.e. Departments can not exist independently
without the Company. That‘s why it is composition. Department is Part-of Company.
// Attributes of Department
public String departmentName;
// Constructor of Department class
public Department(String departmentName) {
this.departmentName = departmentName;
}
public String getDepartmentName() {
return departmentName;
}
}
- 146 -
// Constructor of Company class
public Company(String companyName) {
this.companyName = companyName;
this.departments = new ArrayList<Department>();
}
techCompany.addDepartment(new Department("Engineering"));
techCompany.addDepartment(new Department("Operations"));
techCompany.addDepartment(new Department("Human Resources"));
techCompany.addDepartment(new Department("Finance"));
int d = techCompany.getTotalDepartments();
System.out.println("Total Departments: " + d);
- 147 -
Below are the primary differences between the forms of Association, Composition
and Aggregation in java:
Aggregation Composition
Weak Association Strong Association
One class is dependent on Another
Classes (Dependency) in relation can Independent class. The Dependent class
exist independently cannot exist independently in the event of
the non-existence of an independent class.
One class has-a relationship with Once class belongs-to (or) part-of
another class another class
Helps with code reusability. Since Code is not that reusable as the
classes exist independently, associations association is dependent. Such
can be reassigned or new associations Associations once established will create
created without any modifications to the a dependency, and these associations
existing class. cannot be reassigned or new associations
Note: Code reuse is best achieved by like aggregation, etc cannot be created
aggregation. without changing the existing class.
Understanding how the different forms of association work give us the ability to
write code that is closely relevant and practical in the real world.
Q1. What do you understand by the term ‗Association‘ in UML? Discuss the types of
associations. How does Association differ from Link?
Q2. The association: "Each department offers one or more courses" will have the
following multiplicity at the course (child) end of the association.
(a) 1..1
(b) 0..*
(c) 0..1
(d) 1..*
Q3. For the association "Each automobile may be owned by at most one driver", the
multiplicity at the driver (parent) end of the relationship will be:
(a) 0..1
(b) 1..1
(c) 0..*
- 148 -
(d) 1..*
Q4. Consider the statement ―room has chair‖ Which of the following types of association
exist between room and chair?
(a) inheritance
(b) composition
(c) There is no association
(d) Aggregation
Q5. Consider the one-to-one unidirectional association exists in two classes: Clock
Implementation and Display, as shown in figure.
Q6. Consider the one-to-many unidirectional association exists in two classes: Account
and Manager, as shown in figure.
Q7. Consider the following example; two classes: Person and Account have a One-to-
Many bi-directional association relationship between them, which means that one Person
has many Accounts as shown in figure.
- 149 -
The correct implementation of the given figure is:
(a) (b)
public class Persons { public class Account {
private Account accounts; private Person theOwner;
…….. ……..
……… ………
} }
Q8. Below are few real-world examples to understand the associations between them.
Define type of association whether the aggregation or the composition.
• Association between Mobile store and mobiles
• Association between Car and Engine as mandatary part
• Association between a Bank and Employees
• Association between Library and Books
• Association between Institute, student and department
• Association between Building and rooms
• Association between Cities and State
• Association between Band and Musician
Q9. Let‘s take two classes, Professor class, and Department class. Below are the types of
relationships/associations that can be possible between them. Define the forms of
association whether one-to-one, one-to-many or many-to-many.
• One professor can only be assigned to work in one department.
• One professor can be assigned to work in multiple departments.
• Multiple professors can be assigned to work in one department.
• Multiple professors can be assigned to work in multiple departments.
Q10. Explain the unidirectional relationship? Describe how the one-to-one associations
can be implemented.
Q11.What is an optional association? Explain the implementation of optional association
with an example.
Q12. Explain one-to-one uidirectional association with an example in favour of
implementation.
Q13. What is bi-directional relationship? How the bi-directional Implementations are
made.
- 150 -
Q14. What is the difference between unidirectional and bidirectional association? Explain
an approach to implementing bidirectional association.
Q15. How is a one-to-one bi-directional association different from one-to-many bi-
directional association? Explain how one-to-one bi-directional association is
implemented with the help of an example.
Q16. Consider the association relationship between Employee and Company class. How
can we reduce the multiplicy using the qualified association?
(a) Draw UML diagram for reducing the multiplicy using the qualified association.
(b) Design the qualified association implementation using java code.
Q17. The many-to-many association between Employee and Project class that employees
are assigned in one or more project and each project has zero or more employees
assigned. The many-to-many association is replaced by a reified class – Assignment that
maintains the role and hours of work in assigned project.
(a) Draw UML diagram with a reifed class (or) associate class
(b) Design the associate class implementation using java code.
Q16. Aggregation and Composition relationships are two forms of association. Consider
the Car and Engine classes where two forms of association relationships as below:
• In the case of composition, the Car owns the Engine object (i.e. Engine cannot exist
without the Car object).
• In the case of aggregation, the the Engine object is independent of the Car object (i.e.
Engine can exist without the Car object).
(a) Draw UML diagrams for both forms of association between Car and Enginee
class.
(b) Design two forms of association implementation using java code.
- 151 -
4. Implementing Constraints
For example, a bank is introducing a new type of savings account, which pays a
preferential rate of interest. However, the balance of the account must remain within the
range £0 to £250,000 and a deposit or withdrawal that would take the balance outside this
range will be rejected. A class representing savings accounts is shown in Figure 1.35(a),
with the constraint represented informally in a note and if the context is a class, the
constraint can be placed inside the class icon shown in Figure 1.35(b), the account class
with a constraint added to specify that an account‘s balance must fall within the specified
limits. Figure 1.35(c) is stereotyped constraints with pre and post conditions in a note.
- 152 -
(c) Class specification using stereotyped constraints
Figure 1.35: A constrained savings account
- 153 -
}
}
//Post Condition Constraints
public void withdraw(double amt)
throws InvariantFailed, PreconditionUnsatisfied
{
if (amt > balance) {
throw PreconditionUnsatisfied ;
}
balance -= amt ;
if (!invariant()) {
throw InvariantFailed ;
}
}
}
public PreconditionUnsatisfied()
{ super(); }
public PreconditionUnsatisfied(String s)
{ super(s); }
public InvariantFailed()
{ super(); }
public InvariantFailed(String s)
{ super(s); }
- 154 -
public static void main(String[] args) {
//Create Bank object
SavingsAccount acc = new SavingaAccount(“Acc001” );
Figure Q2: Bank account constraints - snon empty owner and positive balance.
Q3. Draw a UML constraint diagram for Employee who could be as a boss or the worker.
Worker‘s salary cannot exceed the salary of boss.
- 155 -
5. Implementing Statecharts Diagram
A state chart diagram describes how the state of an object changes in its life time.
State chart diagrams are good at describing how the behavior of an object changes
across several usecase executions. However, if we are interested in modeling some
behavior that involves several objects collaborating with each other, state chart diagram
is not appropriate. We have already seen that such behavior is better modeled using
sequence or collaboration diagrams. The state chart was proposed by David Harel [1990].
Following are the main purposes of using Statechart diagrams −
• To model the dynamic aspect of a system.
• To model the life time of a reactive system.
• To describe different states of an object during its life time.
• Define a state machine to model the states of an object.
• Actions are associated with transitions and are considered to be processes that
occur quickly and are not interruptible.
• Activities are associated with states and can take longer. An activity can be
interrupted by an event.
- 156 -
Figure 1.36: State chart diagram for a bank saving account object.
- 157 -
In above Figure 1.36, the statechart models two states of a bank account, recording
whether it is in credit or overdrawn. The most important aspects of any implementation
of the behaviour specified in a statechart are to record the current state of an object
and to ensure that its response to a message is correctly determined by its current
state.
The approach for implementing statecharts is using the switch statement. The states
of statecharts diagrams are denoted as objects or scalar variables. Events and actions
are indicated as methods. The most basic technique to record the current state is to
enumerate the different states as constants and to store the current state in a suitable
datamember. The following code shows how this could be done, and also shows how
the constructor sets the initial state of the object to ‗InCredit’, as specified in Figure
1.36.
public class Account
{
private final int InCredit = 0 ; // state variable
private final int Overdrawn = 1 ; // state variable
private final int Suspended = 2 ; // state variable
private int state ;
public Account() {
state = InCredit ; //initial state of an account object
}
}
• Each case represents all the transitions leading from the specified state that are
labelled with the appropriate message.
• It should check any applicable guard conditions, perform any actions and if
necessary change the state of the object by assigning a new value to the data
member recording the state.
• If an operation is not applicable in a given state, that case can simply be left empty.
- 158 -
balance -= amt ;
break ;
case Overdrawn:
case Suspended:
break ;
}
}
(2) Composite states are essentially a device for simplifying the structure of a statechart
and do not need to be represented as separate states. Their major role is to denote by a
single transition a set of equivalent transitions from each of their substates. The
‗suspend‟ transition in Figure 13.14 provides an example of this. This transition can be
implemented simply by grouping together the cases that refer to the substates, as
shown in the following outline implementation of the „suspend‟ operation in the
account class.
(3) History states are not additional states in a statechart, but rather a device for
recording the most recent substate in a composite state. The history state in Figure
13.14 can therefore be represented by a variable storing a state, and this stored state
can be used when an account is unsuspended, as shown below.
Two bits of housekeeping need to be performed on the history state. The first time
the composite state is entered, the history state is not active and the account enters the
- 159 -
‗InCredit‘ state, as specified by the initial state in the composite state. In general, this
behaviour can be simulated by initializing the history state variable to the specified
initial state.
In addition, the history state variable needs to be set whenever a transition leaves
the composite state. In the current example, this would be in the suspend method, just
before the state is set to Suspended.
This general approach to the implementation of statecharts is simple and generally
applicable, but it also has a few disadvantages. First, it does not provide much
flexibility in the case where a newstate is added to the statechart. In principle, the
implementation of every member function of the class would have to be updated in such
a case, even if they were quite unaffected by the change. Second, the strategy assumes
that most functions have some effect in the majority of the states of the object. If this is
not the case, the switch statements will be full of „empty cases‟ and the
implementation will contain a lot of redundant code.
public SavingsAccount() {
state = InCredit ; // define the initial state of an account object
accno = ano;
balance = 0 ;
}
- 160 -
}
If ( - amt <= balance ){
state = InCredit ;
}
balance + = amt;
break ;
case Suspended:
suspend();
break ;
- 161 -
case Suspended:
//Last state to be reinstated when unsuspend is triggered
state = historyState ;
break ;
// other cases
}
}
}
Q2. Consider the following statechart diagram shown in figure for the video player
application with two states: Stop and Play. In the Stop state, when a user presses a play
button, then playButton event happens, and the device executes the Playstart action and
goes to ‗Play‘ state. In the Play state, when the user presses a stop button, the stopButton
event generates, and the device executes the Playstop action and goes to the Stop state.
Design implementation for the statechart as shown in figure.
- 162 -
6. Implementing the Interaction Diagrams
The interaction diagrams in UML are the sequence and collaboration diagram. They
depict the relationships between the objects in a system and objects work together to
perform a particular task. It defines a functionality of the system by demonstrating a set
of chronological interactions between objects. It is basically used to demonstrate how
objects interrelate to perform the behaviour of a specific use case.
In an application system, there are one or more collaboration diagrams to find all the
eventuality of a complex behaviour. The collaboration diagram contains the elements
such as actors, objects and their communication links, and messages.
• Each actor performs a significant role as it invokes the interaction and has a
name.
• The link behaves as a connector between the objects and actors. It depicts a
relationship between the objects through which the messages are sent.
• The messages are exchanged between objects in an order which is represented
by sequence numbers. The messages are sent from the sender to the recipient, and
the direction must be traversable in that particular direction.
- 163 -
Figure 1.37: Collaboration Diagram showing If-else condition
if (a>b) {
ObjectB.Message1();
}
else {
ObjectC.Message2();
}
Let us consider a collaboration diagram for ATM transactions. For this example, first,
create a Use Case diagram and then draw a collaboration diagram. For building a Use
Case diagram, use the below flow of controls:
Customer can insert ATM card into the card reader of the ATM console.
The card reader reads and verifies the card.
ATM console displays a message and prompts the customer to enter PIN.
Customer enters PIN.
ATM console verifies PIN with bank database and confirms the PIN is valid.
If PIN is invalid, ATM console notifies the customer with a message and ejects the
card.
If PIN is valid, ATM console represents a prompt with options such as deposit,
withdraw and transfer money.
Customer selects withdraw option.
ATM console presents prompt to the customer for an accurate amount to be withdrawn.
The customer enters the amount.
ATM console verifies with the bank database whether the customer account has
sufficient funds. Otherwise, it shows an insufficient funds message.
ATM console deducts money from customer‘s account, puts the requested amount in
cash dispenser, and prints a receipt.
ATM console rejects the card and becomes ready for another transaction.
- 164 -
Using the above flow of messages, construct the collaboration diagram similar to
figure 1.38 where messages/method calls are flowing between objects with a sequence
number. This sequence number specifies the ordering of the messages. The messages
appear with pointing arrows from the sender object to the receiver object.
When creating a collaboration diagram for the real business application, we can
create methods as per need. The classes and their methods are created in the
Collaboration Diagram given in figure 1.38, which can be summarized in the tabular
form:
Now you can transform methods defined in the collaboration diagram into executable
code. There are number of methods in the figure-1.38, but the pseudocode given below is
written for verifyPIN() method only. This method verifies the customer's Personal
Identification Number (PIN) using data retrieved from the ATM card's magnetic strip.
- 165 -
This method asks the customers to enter their PIN using the keypad of ATM console. If
the PIN does not equal to the PIN stored on the card, then the ATM system allows a
limited number of repeats. If it is still not matched, then the system invokes ejectCard()
method and the card is seized as a security precaution. If the customer enters the correct
PIN then the system invokes promptOptions() method where it provides options such as
deposit money, withdraw cash and transfer money. You may try to write code for other
methods as well.
method verifyPIN
constants no_of_maxpins is 2
variable pin, pin_count is number
set pin_count to zero
read pin from ATMcard
loop until pin_count is equal to no_of_maxpins
input pin from customer
if entered pin matches with cardpin_database then
call promptOptions method
endif
add 1 to pin_count
endloop
if pin_count is equal to no_of_maxpins then
seize customer's card
else
call ejectCard method
endif
endmethod// verifyPIN
pin_count= 0;
cardpin = ATMcard.readpin(); // pin stored in card
while (pin_count == no_of_maxpins ){
//pin entered by customer
System.out.print(“Enter pin: ” ); input(enteredpin);
if (enteredpin == cardpin) then
promptOptions();
}
- 166 -
pin_count++;
}//endloop
if (pin_count == no_of_maxpins) {
seizecustomercard();
else
ejectCard();
} //endif
}// endmethod of verifyPIN
- 167 -
Figure 1.39: Collaboration Diagram for Programme Management System
Consider the stock control program that stores details about orders which consist
simply of a number of order lines, each of which specifies a certain number of parts of a
particular type. Figure 1.40 shows a class diagram summarizing the necessary facts about
orders. The relationship between orders and order lines is one of composition, because
assume that when an order is destroyed all lines on that order are also destroyed.
- 168 -
Figure 1.40: Orders in the stock control system
Let us assume that to create an order line, a client object must send a message to an
order specifying the part and the quantity to be ordered. A new order line object will then
be created and added to the order. A sequence diagram illustrating this interaction is
shown in Figure 1.41.
The new order line object is created in response to a message labelled ‗add‘ sent from
the client to the order. The parameters of this message supply the number of parts to be
added and the catalogue entry object corresponding to the required parts. In response to
this message, an order object creates a new order line object and the constructor call
creating this object is shown as the next message in the interaction. The diagram shows
that during the construction of order lines the cost of the relevant parts is retrieved from
the catalogue entry object that was passed as a parameter. This value will be used in the
initialization of the order line‘s ‗cost‘ attribute. At the end of this activation, control
returns to the order and the order line‘s lifeline continues as normal.
Collaboration diagrams cannot explicitly show the time at which a new object is
created. In order to distinguish elements that are created during the course of an
interaction from those that existed at the start, classifier and association roles
- 169 -
corresponding to new objects and links are annotated with the property ‗new‘, shown in
Figure 1.42.
The classes and their methods are created in the Sequence Diagram given in figure
1.41, which can be summarized in the tabular form:
Now transform interaction process defined in the Sequence Diagram into executable
code. However only involved methods in the Sequence Diagram are defined as follows:
- 170 -
lineItem. getCost());
}
}
public class OrderLine {
- 171 -
order1.add(3, screw);
order1.add(4, bolt);
order1.hammer(1, hammer);
System.out.println(order1.toString());
Example 2:
Consider the book issued by the librarian when customer requests to issue a book. The
librarian first check the book is available. If yes, validate the member and checked the
number of books issued by the member. If book can be issued, create issued transaction
for the book and add member and book details. Then update the book status and member
issued books status. The Collaboration diagram and Sequence diagram are shown below.
- 172 -
Figure 1.44: Sequence diagram for issuing Book
The classes and their methods are created in the Sequence Diagram given in figure
1.44, which can be summarized in the tabular form:
Now transform interaction process defined in the Sequence Diagram into executable
code. However only involved methods in the Sequence Diagram are defined as follows:
- 173 -
BookCatalogue catalog = new BookCatalogue();
MemberList memberList = new MemberList();
//update
book.updateBookStatus(false);
member.updateMemberRecord(bookID);
return true;
}
…. // other methods
}
public class Book {
- 174 -
private String bookAuthor;
private String bookPublisher;
private boolean bookAvail = true;
…. // other methods
}
public class MemberRecord {
… //other methods
//Check the member ID is valid member or not
public boolean validateMember(int mID){
if (memberID == mID)
return true;
else
return false;
}
//Check member borrowed books exceed the limit
public boolean checkBookIssued( ){
if (borrowedBooks.count() < bookLimit)
return true;
else
return false;
}
//Record the borrowed book ID
public void updateMemberRecord(int bookID){
borrowedBooks.add(bookID);
}
- 175 -
}
public class Transaction {
- 176 -
Q4. How does the sending object obtain data from the object that owns the parameter
values?
(a) By adding an interface to the sending object and adding it to the interaction
sequence
(b) By adding an interface to the owning object and adding it to the interaction
sequence
(c) By reassigning the attributes to the object that receives the parameters
(d) By reassigning the attributes to the object that sends the parameters
Q5. What are the types of UML interaction diagrams and how do they differ?
Q6. UML sequence diagrams typically show some sequence of method invocations that
achieve some specific purpose. Figure Q1 shows a sequence diagram for calculating the
total of a sale transaction. It starts with a call to the method calcTotal() of the Sale class.
Design the relevant source code from the diagram shown.
Q7. UML collaboration diagram describes the same interactions as the sequence
diagrams of sequence diagram. Figure Q2 shows a collaboration diagram for students to
register in a course. List the methods from the diagram and design the relevant source
code for each methods.
- 177 -
7. SOLID: Single, Open-Closed, Liskove Substitution and Dependency Inversion
Principles
The SOLID principles are the fundamental principles that suggest for the object-
oriented programming that -
SOLID are principles, not patterns. SOLID is an acronym for the five design
principles introduced by Robert C. Martin. SOLID stands for:
“Each software module should have one and only one reason to change.”
In other words, a class should have only one responsibility and therefore it should have
only one reason to change its code. If a class has more than one responsibility, then there
will be more than one reason to change the class (code).
Let's check how many responsibilities the following Student class has:
- 178 -
Example: A Class with Multiple Responsibilities
Console.WriteLine("End Save()");
}
public void Delete() {
Console.WriteLine("Starting Delete()");
Console.WriteLine("End Delete()");
}
public IList<Course> Subscribe(Course cs) {
Console.WriteLine("Starting Subscribe()");
- 179 -
The above Student class has the following responsibilities:
If anything in the above responsibility changes, then we will have to modify the
Student class. For example,
• if you need to add a new property then we need to change the Student class.
• Or, if you need a change in the database, maybe moving from a local server to a
cloud, then you need to change the code of the Student class.
• Or, if you need to change the business rules (validation) before deleting a
student or subscribing to a course, or change the logging medium from console
to file, then in all these cases you need to change the code of the Student class.
Thus, you have many reasons to change the code because it has many responsibilities.
SRP tells us to have only one reason to change a class. Let's change the Student class
considering SRP where we will keep only one responsibility for the Student class and
abstract away (delegate) other responsibilities to other classes.
Start with each responsibility mentioned above and decide whether we should
delegate it to other classes or not.
1. The Student class should contain all the properties and methods which are
specific to the student. Except for the Subscribe() method, all the properties and
methods are related to the student, so keep all the properties.
2. The Save() and Delete() method is also specific to a student. Although, it uses
Entity Framework to do the CRUD operation which is another reason to change the
Student class. We should move the underlying EF code to another class to do all
DB operations e.g. StudentRepository class should be created for all CRUD
operations for the Student. This way if any changes on the DB side then we may
need to change only the StudentRepository class.
- 180 -
3. The Subscribe() method is more suitable for the Course class because a course
can have different subscription rules based on the course type. So it is idle to move
the Subscribe() method to the Course class.
4. Sending confirmation emails is also a part of the Subscribe() method, so it will
be a part of the Course class now. Although, we will create a separate class
EmailManger for sending emails.
5. Here, all the activities are logged on the console using the hard-coded
Console.WriteLine() method. Any changes in the logging requirement would
cause the Student class to change. For example, if the admin decides to log all
activities in the text file then you need to change the Student class. So, it's better
to create a separate Logger class that is responsible for all the logging activities.
Now, look at the following classes redesigned after applying SRP using the above
considerations for SRP.
_studentRepo.Save(this);
Logger.Log("End Save()");
}
_studentRepo.Delete(this);
Logger.Log("End Delete()");
- 181 -
}
}
Logger.log("Ending Saving()");
}
Logger.Log("Ending Delete()");
}
Logger.Log("Ending SaveCourse()");
}
}
//apply business rules based on the course type live, online, offline, if any
if (this.Type == "online")
{
//subscribe to online course
- 182 -
}
else if (this.Type == "live")
{
//subscribe to offline course
}
// payment processing
PaymentManager.ProcessPayment();
//create CourseRepository class to save student and course into StudentCourse table
Logger.Log("End Subscribe()");
}
}
Now, think about the above classes. Each class has a single responsibility.
• The Student class contains properties and methods specific to the student-related
activities.
• The Course class has course-related responsibilities.
• The StudentRepository has responsibilities for student-related CRUD
operations using Entity Framework.
• The Logger class is responsible for logging activity.
• The EmailManager class has email-related responsibilities.
• The PaymentManager class has payment-related activities.
In this way, we have delegated specific responsibilities to separate classes so that each
class has only one reason to change. These increase cohesion and loose coupling.
- 183 -
7.1.2 Separation of Concerns
The Open/Closed Principle (OCP) is the second principle of SOLID. Dr. Bertrand
Meyer originated this term in his book Object-oriented Software Construction.
Open/Cloced Principle states that:
―Software entities (classes, modules, functions, etc.) should be open for extension,
but closed for modification.‖
Here, the extension means adding new features to the system without modifying
that system. The plugin systems are the main example of OCP where new features are
added using new features without modifying the existing ones.
OCP says the behavior of a method of a class should be changed without
modifying its source code. You should not edit the code of a method (bug fixes are ok)
instead you should use polymorphism or other techniques to change what it does. Adding
new functionality by writing new code.
In Java and C#, Open/Closed principle can be applied using the following approaches:
- 184 -
To demonstrate OCP, let's take an example of the Logger class shown below.
Assume that you are the creator of this class and other programmers want to reuse your
class so that they don't have to spend time to rewriting it (SOLID principles promote
reusability).
Now, some developers want to change debug messages to suit their needs. For
example, they want to start debugging messages with "Dev Debug ->". So, to satisfy
their need, you need to edit the code of the Logger class and either create a new method
for them or modify the existing Debug() method. If you change the existing Debug()
method then the other developers who don't want this change will also be affected.
One way to use OCP and solve this problem is to use class based-inheritance
(polymorphism) and override methods. You can mark all the methods of the Logger
class as virtual so that if somebody wants to change any of the methods then they
can inherit the Logger class into a new class and override it.
- 185 -
Now, a new class can inherit the Logger class and change one or more method
behavior. The developers who wanted to change the debug message will create a new
class, inherit the Logger class and override the Debug() method to display the
message they wanted, as shown below.
They will now use the above class to display debug messages they want without
editing the source code of the original class.
Example:
Output:
Thus, OCP using inheritance makes it "Open for extension and closed for
modification".
Let's take another example. The following is the Course class that we created in the
previous SRP section.
Example:
- 186 -
public void Subscribe(Student std) {
Logger.Log("Starting Subscribe()");
//apply business rules based on the course type live, online, offline, if any
if (this.Type == "online") {
//subscribe to online course
}
else if (this.Type == "offline") {
//subscribe to offline course
}
// payment processing
PaymentManager.ProcessPayment();
//create CourseRepository class to save student and course into StudentCourse table
Logger.Log("End Subscribe()");
}
}
We will have to edit the above Course class whenever there is a requirement of
adding a new type of course. We will have to add one more if condition or switch cases
to process the course type. Also, the above Course class does not follow the Single
Responsibility Principle because if there is any change in the process of subscribing to
courses or need to add new types of courses, then we will have to change the Course
class.
To apply OCP to our Course class, abstract class-based inheritance is more suitable.
We can create an abstract class as a base class and then create a new class for each type
of course and implement the Subscribe() method in each class which will do all the
necessary subscription steps, as shown below.
- 187 -
}
}
As you can see, the Course class is now an abstract class where the Subscribe()
method is an abstract method that needs to be implemented in a class that inherits the
Course class. This way there is a separate Subscribe() function for separate course
types (Separation of concerns). You can create a new class for a new type of course in the
future that inherits the Course class. That way, you don't have to edit the existing
classes.
Liskov Substitution Principle was introduced by Barbara Liskov in 1987. She described
this principle in mathematical terms as below:
Let φ(x) be a property provable about objects x of type T. Then φ(y) should also be true
for objects y of type S where S is a subtype of T.
- 188 -
LSP guides how to use inheritance in object-oriented programming. It is about
subtyping, and how to correctly derive a type from a base type. Robert Martin explains
LSP as below:
“Subtypes must be substitutable for their base type.”
(i.e., A derived class must be correctly substitutable for its base class.)
Let's simplify it further. A derived class must be correctly substitutable for its base
class. When you derived a class from a base class then the derived class should
correctly implement all the methods of the base class. It should not remove some
methods by throwing NotImplementedException.
- 189 -
The above example violates the Liskov Substitution principle because the
MyReadOnlyCollection class implements the IMyCollection interface but it throws
NotImplementedException for two methods Add() and Remove() because the
MyReadOnlyCollection class is for the read-only collection so you cannot add or
remove any item. LSP suggests that the subtype must be substitutable for the base
class or base interface. In the above example, we should create another interface for
read-only collection without Add() and Remove() methods.
Let's understand what is the meaning of "A derived class should correctly implement
methods of a base class"?
Mathematically, a square is the same as a rectangle that has four equal sides. We can use
inheritance "is-a" relationship here. A square is a rectangle. The Square class can
inherit the Rectangle class with equal height and width, as shown below.
@ override
public int getHeight () {
return this._height;
}
public int setHeight(int value) {
this._height = value;
this._width = value;
}
}
@ override
public int getWidth() {
- 190 -
return this._width;
}
public void setWidth (int value) {
this._width = value;
this._height = value;
}
}
}
// Class 3: AreaCalculatior
public class AreaCalculator {
public static int CalculateArea(Rectangle r) {
return r.getHeight() * r.getWidth();
}
}
LSP says that the derived class should correctly implement the base class methods. Here,
the square class is not a subtype of the rectangle class because it has equal sides. So,
- 191 -
only one property is needed instead of two properties, height, and width. It creates
confusion for the users of the class and might give the wrong result.
Interface Segregation Principle (ISP) is the fourth principle of SOLID principles. It can
be used in conjunction with LSP.
Now, who is the client and what and whose methods it is talking about?
Here, a client is a code that calls the methods of a class with an instance of the
interface. For example, a class implements an interface that contains 10 methods. Now,
you create an object of that class with a variable of that interface and call only 5 methods
for the functionality you wanted and never call the other 5 methods. So, this means that
the interface contains more methods that are not used by all client codes. It is called
a fat interface. ISP suggests segregating that interface into two or more interfaces so
that a class can implement the specific interface that it requires.
- 192 -
// Class 2: Derived class
public class StudentRepository implements IStudentRepository
{
public void AddCourse(Course cs) {
//implementation code removed for better clarity
}
- 193 -
The above IStudentRepository interface contains 12 methods for different purposes.
The StudentRepository class implements the IStudentRepository interface.
Now, after some time, you observe that not all instances of the StudentRepository
class call all the methods. Sometimes it calls methods that perform student-related tasks
or sometimes calls course-related methods. Also, the StudentRepository does not
follow the single responsibility principle because you may need to edit its code if student
related as well as course-related business logic changes.
To apply ISP to the above problem, we can split our large interface
IStudentRepository and create another interface ICourseRepository with all
course-related methods, as shown below.
IList<Course> GetAllCourse();
IList<Course> GetAllCourses(Student std);
}
Now, we can create two concrete classes that implement the above two interfaces. This
will automatically support SRP and increase cohesion.
- 194 -
ISP is not specific to interfaces only but it can be used with abstract classes or any class
that provides some services to the client code.
Now, the question is what are high-level and low-level modules and what is an
abstraction?
A high-level module is a module (class) that uses other modules (classes) to perform
a task. A low-level module contains a detailed implementation of some specific task that
can be used by other modules. The high-level modules are generally the core business
logic of an application whereas the low-level modules are input/output, database, file
system, web API, or other external modules that interact with users, hardware, or other
systems.
Abstraction is something that is not concrete. Abstraction should not depend on
detail but details should depend on abstraction. For example, an abstract class or
- 195 -
interface contains methods declarations that need to be implemented in concrete classes.
Those concrete classes depend on the abstract class or interface but not vice-versa.
You can identify a class is depends on another class if it creates an object of another
class. You may require adding the reference of the namespace to compile or run the code.
Let's use the following example to understand the DIP:
//tight coupling
private StudentRepository _stdRepo = new StudentRepository();
public Student() {
- 196 -
The above Student class creates an object of the StudentRepository class for
CRUD operation to a database. Thus, the Student class depends on the
StudentRepository class for CRUD operations. The Student class is the high-
level module and the StudentRepository class is the low-level module.
Here, the problem is that the Student class creates an object of concrete
StudentRepository class using the new keyword and makes both tightly coupled.
This leads to the following problems:
Creating objects using the new keyword at all places is repeated code. The
object creation is not in one place.
o Violation of the Do Not Repeat Yourself (DRY) principle. If there is
some change in the constructor of the StudentRepository class then we
need to make the changes in all the places. If object creation is in one
place then it would be easy to maintain the code.
Creating an object using new also make unit testing impossible. We cannot unit
test the Student class separately.
The StudentRepository class is a concrete class, so any changes in the class will
require changing the Student class too.
DIP says that high-level modules should not depend on the low-level module. Both
should depend on abstraction. Here, abstraction means use of interface or abstract
class. The following is the result of applying the DIP principle to the above example.
- 197 -
}
}
IList<Student> GetAllStudents();
}
The Student class does not create an object of the StudentRepository class using
the new keyword. The constructor requires a parameter of the IStudentRepository
class which will be passed from the calling code. Thus, it also depends on the abstraction
(interface) rather than the low-level concrete class (StudentRepository).
This will create loose coupling and also make each class unit testable. The caller of
the Student class can pass an object of any class that implements the
IStudentRepository interface and by so not tied to the specific concrete class.
- 198 -
//Test Class
public class Program
{
public static void Main(string[] args) {
//for production
Student std1 = new Student(new StudentRepository);
Instead of creating manually, you can use the factory class to create it, so that all the
object creation will be in one place.
//Test Class
public class Program
{
public static void Main(string[] args) {
//for production
Student std1 = new Student(RepositoryFactory.GetStudentRepository());
It is recommended to use Dependency Injection and IoC containers for creating and
passing objects of low-level classes to high-level classes.
- 199 -
Check Your Understanding on SOLID: Single Responsibility Principle
Q1. What are SOLD principles? Are SOLD patterns? What SOLD stands for?
Q2. What are the SOLD principles? Explain each principle guide for the OO
programming.
Q3. What does the single responsibility principle (SRP) state for the object oriented
programming? Explain with example.
Q4. How does the single responsibility principle (SRP) apply in the OO program?Explain
with example.
Q5. What does the Separation of Concerns principle suggest for OO programming?
Q6. How does the SRP and the Separation of Concerns principle effect on cohesion and
coupling of OO program?
Q7. What does the Open/Closed Principle (OCP) state for the object oriented
programming? Explain with example.
Q8. How does the Open/Closed Principle (OCP) apply in the OO program? Explain with
example.
Q9. What does the Liskov Substitution Principle guide for the object oriented
programming? Explain with example.
Q10. What does Interface Segregation Principle (ISP) guide for the object oriented
programming? Explain with example.
Q11. What does Dependency Inversion Principle guide for the object oriented
programming? Explain with example.
- 200 -
SUMMARY
The more that you read, the more things you will know.
The more that you learn, the more places you will go.
Dr. Sesus
- 201 -