Object Oriented Programming
Introduction
Introduction
Object-Oriented Programming (OOP) is a programming paradigm. A programming paradigm guides programmers to analyze programming problems, and structure programming solutions, in a specific way.
Programming languages have traditionally divided the world into two parts—data and operations on data. Data is static and immutable, except as the operations may change it. The procedures and functions that operate on data have no lasting state of their own; they’re useful only in their ability to affect data.
This division is, of course, grounded in the way computers work, so it’s not one that you can easily ignore or push aside. Like the equally pervasive distinctions between matter and energy and between nouns and verbs, it forms the background against which we work. At some point, all programmers—even object-oriented programmers—must lay out the data structures that their programs will use and define the functions that will act on the data.
With a procedural programming language like C, that’s about all there is to it. The language may offer various kinds of support for organizing data and functions, but it won’t divide the world any differently. Functions and data structures are the basic elements of design.
Object-oriented programming doesn’t so much dispute this view of the world as restructure it at a higher level. It groups operations and data into modular units called objects and lets you combine objects into structured networks to form a complete program. In an object-oriented programming language, objects and object interactions are the basic elements of design.
Some other examples of programming paradigms are:
Paradigm | Programming Languages |
---|---|
Procedural Programming paradigm | C |
Functional Programming paradigm | F#, Haskel, Scala |
Logic Programming paradigm | Prolog |
Some programming languages support multiple paradigms.
📦 Java is primarily an OOP language but it supports limited forms of functional programming and it can be used to (although not recommended) write procedural code. e.g. se-edu/addressbook-level1
📦 JavaScript and Python support functional, procedural, and OOP programming.
Objects
Basic
Every object has both state (data) and behavior (operations on data). In that, they’re not much different from ordinary physical objects. It’s easy to see how a mechanical device, such as a pocket watch or a piano, embodies both state and behavior. But almost anything that’s designed to do a job does, too. Even simple things with no moving parts such as an ordinary bottle combine state (how full the bottle is, whether or not it’s open, how warm its contents are) with behavior (the ability to dispense its contents at various flow rates, to be opened or closed, to withstand high or low temperatures).
It’s this resemblance to real things that gives objects much of their power and appeal. They can not only model components of real systems, but equally as well fulfill assigned roles as components in software systems.
Object Oriented Programming (OOP) views the world as a network of interacting objects.
📦 A real world scenario viewed as a network of interacting objects:
You are asked to find out the average age of a group of people Adam, Beth, Charlie, and Daisy. You take a piece of paper and pen, go to each person, ask for their age, and note it down. After collecting the age of all four, you enter it into
a calculator to find the total. And then, use the same calculator to divide the total by four, to get the average age. This can be viewed as the objects You
, Pen
, Paper
, Calculator
, Adam
,
Beth
, Charlie
, and Daisy
interacting to accomplish to achieve the end result of calculating the average age of the four persons. These objects can be considered as connected in a certain network of certain
structure.
OOP solutions try to create a similar object network inside the computer’s memory – a sort of a virtual simulation of the corresponding real world scenario – so that a similar result can be achieved programmatically.
OOP does not demand that the virtual world object network follow the real world exactly.
📦 Our previous example can be tweaked a bit as follows:
- Use an object called
Main
to represent your role in the scenario. - As there is no physical writing involved, we can replace the
Pen
andPaper
with an object calledAgeList
that is able to keep a list of ages.
Every object has both state (data) and behavior (operations on data).
Object | Real World? | Virtual World? | Example of State (i.e. Data) | Examples of Behavior (i.e. Operations) |
---|---|---|---|---|
Adam | x | x | Name, Date of Birth | Calculate age based on birthday |
Pen | x | Ink color, Amount of ink remaining | Write | |
AgeList | x | Recorded ages | Give the number of entries, Accept an entry to record | |
Calculator | x | x | Numbers already entered | Calculate the sum, divide |
You/Main | x | x | Average age, Sum of ages | Use other objects to calculate |
Every object has an interface and an implementation.
Every real world object has an interface that other objects can interact with and an implementation that supports the interface but may not be accessible to the other object.
📦 The interface and implementation of some real-world objects in our example:
- Calculator: the buttons and the display are part of the interface; circuits are part of the implementation.
- Adam: In the context of our 'calculate average age' example, the interface of Adam consists of requests that adam will respond to, e.g. "Give age to the nearest year, as at Jan 1st of this year" "State your name"; the implementation includes the mental calculation Adam uses to calculate the age which is not visible to other objects.
Similarly, every object in the virtual world has an interface and an implementation.
📦 The interface and implementation of some virtual-world objects in our example:
Adam
: the interface might have a methodgetAge(Date asAt)
; the implementation of that method is not visible to other objects.
Objects interact by sending messages.
Both real world and virtual world object interactions can be viewed as objects sending message to each other. The message can result in the sender object receiving a response and/or the receiving object’s state being changed. Furthermore, the result can vary based on which object received the message, even if the message is identical (see rows 1 and 2 in the example below).
Examples:
World | Sender | Receiver | Message | Response | State Change |
---|---|---|---|---|---|
Real | You | Adam | "What is your name?" | "Adam" | - |
Real | as above | Beth | as above | "Beth" | - |
Real | You | Pen | Put nib on paper and apply pressure | Makes a mark on your paper | Ink level goes down |
Virtual | Main | Calculator (current total is 50) | add(int i): int i = 23 | 73 | total = total + 23 |
Objects as Abstractions
The concept of Objects in OOP is an abstraction mechanism because it allows us to abstract away the lower level details and work with bigger granularity entities i.e. ignore details data formats and the method implementation details and work at the level of objects.
📦 We can deal with a Person
object that represents the person Adam and query the object for Adam's age instead of dealing with details such as Adam’s date of birth (DoB), in what format the DoB is stored, the algorithm used to
calculate the age from the DoB, etc.
Encapsulation Of Objects
Encapsulation protects an implementation from unintended actions and from inadvertent access.
-- Object-Oriented Programming with Objective-C, Apple
An object is an encapsulation of some data and related behavior in two aspects:
1. The packaging aspect: An object packages data and related behavior together into one self-contained unit.
2. The information hiding aspect: The data in an object is hidden from the outside world and are only accessible using the object's interface.
Classes
Basic
Writing an OOP program is essentially writing instructions that the computer uses to,
- create the virtual world of object network, and
- provide it the inputs to produce the outcome we want.
A class contains instructions for creating a specific kind of objects. It turns out sometimes multiple objects have the same behavior because they are of the same kind. Instructions for creating a one kind (or ‘class’) of objects can be done in one go and use that same instructions to instantiate (i.e. create) objects of that kind. We call such instructions a Class.
📦 Classes and objects in an example scenario
When writing an OOP program to calculate the average age of Adam, Beth, Charlie, and Daisy, instructions for creating objects Adam
, Beth
, Charlie
, and Daisy
will be very similar because they
are all of the same kind : they all represent ‘persons’ with the same interface, the same kind of data (i.e. name
, DoB
, etc.), and the same kind of behavior (i.e. getAge(Date)
, getName()
,
etc.). Therefore, we can have a class called Person
containing instructions on how to create Person
objects and use that class to instantiate objects Adam
, Beth
, Charlie
, and
Daisy
. Similarly, we need classes AgeList
, Calculator
, and Main
classes to instantiate one each of AgeList
, Calculator
, and Main
objects.
Person
class and some example instances of the Person
class.
Class Level Members
While all objects of a class has the same attributes, each object has its own copy of the attribute value.
Example:
All Person
objects have the Name
attribute but the value of that attribute varies between Person
objects.
However, some attributes are not suitable to be maintained by individual objects. Instead, they should be maintained centrally, shared by all objects of the class. They are like ‘global variables’ but attached to a specific class. Such variables whose value is shared by all instances of a class are called class level attributes.
Example:
The attribute totalPersons
should be maintained centrally and shared by all Person
objects rather than copied at each Person
object.
Similarly, when a normal method is being called, a message is being sent to the receiving object and the result may depend on the receiving object.
Example:
Sending the getName()
message to Adam
object results in the response "Adam"
while sending the same message to the Beth
object gets the response "Beth"
.
However, there can be methods related to a specific class but not suitable for sending message to a specific object of that class. Such methods that are called using the class instead of a specific instance are called class-level methods.
Example:
The method getTotalPersons()
is not suitable to send to a specific Person
object because a specific object of the Person
class should not know about the total number of Person
objects.
Class-level attributes and methods are collectively called class-level members (also called static members sometimes because some programming languages use the keyword static
to identify
class-level members). They are to be accessed using the class name rather than an instance of the class.
📦 A Student
class with a class-level attribute and a method:
Enumerations
An Enumeration is a fixed set of values that can be considered as a data type.
An enumeration is often useful when using a regular data type such as int
or String
would allow invalid values to be assigned to a variable. e.g. if a variable can only take values 0
and 1
, declaring it as an int
would allow invalid values such as 2
to be assigned to it. But if you define an enumeration called Binary
that has values 0
and 1
only, a variable of type Binary
will never be assigned an invalid value such as 2
because the compiler is able to catch the error.
📦 Priority
can be considered an enumeration because it has only three values.
Priority
: HIGH
, MEDIUM
, LOW
Associations
Basic
Connections between objects are called associations. Objects in an OO solution need to be connected to each other to form a network so that they can interact with each other. Such connections are called associations.
📦 An object diagram example showing associations among objects. For example, there is an association between the AgeList
object and the Main
object.
Object structures can change over time.
📦 In this example, the object structure has changed (from left to right) due to a new Person
object being added and connected to the Main
object.
→
Associations can be reflected among classes too.
📦 An example class diagram showing associations between classes.
An association in a class diagram can use association labels and association roles to show additional info.
Navigability
When two classes are linked by an association, it does not necessarily mean both classes know about each other. The concept of which class in the association knows about the other class is called navigability.
Multiplicity
Multiplicity is the aspect of an OOP solution that dictates how many objects take part in each association.
📦 This class diagram does not tell us multiplicities. For exaple, how many Calculator
objects can be associated with one Main
object? How many Main
objects can be associated with one Calculator
object? and so on.
Dependencies
Dependencies are objects that are not directly linked in the object network can still interact with each other. These are a weaker form of associations we call dependencies.
Composition
A composition is an association that represents a strong whole-part relationship. When the whole is destroyed, parts are destroyed too.
📦 A Board
(used for playing board games) consists of Square
objects.
Composition also implies that there cannot be cyclical links.
📦 In this ‘sub-folder’ association, a Folder
cannot be a sub-folder of itself. If the diamond is removed, it is no longer a composition relationship and technically, allows a folder to be sub-folder of itself.
Aggregation
Aggregation represents a container-contained relationship. It is a weaker relationship than composition.
📦 Club
acts as a container for Person
objects. Person
objects can survive without a Club
object.
Aggregation vs Composition
The distinction between composition (◆) and aggregation (◇) is rather blurred. Martin Fowler’s famous book UML Distilled advocates omitting the aggregation symbol altogether because using it adds more confusion than clarity.
Association Classes
An association class represents additional information about an association. It is a normal class but plays a special role from a design point of view.
📦 A Man
class and a Woman
class is linked with a ‘married to’ association and there is a need to store the date of marriage. However, that data is related to the association rather than specifically owned by either
the Man
object or the Woman
object. In such situations, an additional association class can be introduced, e.g. a Marriage
class, to store such information.
Inheritance
What
The OOP concept Inheritance allows you to define a new class based on an existing class. For example, you can use inheritance to define an EvaluationReport
class based on an existing Report
class
so that the EvaluationReport
class does not have to duplicate code that is already implemented in the Report
class.
📦 Example: The EvaluationReport
inherits the wordCount
attribute and the print()
method from the base classReport
.
- Other names for Base class: Parent class, Super class
- Other names for Derived class: Child class, Sub class, Extended class
A super class is said to be more general than the sub class. Conversely, a sub class is said to be more specialized than the super class.
Applying inheritance on a group of similar classes can result in the common parts among classes being extracted into more general classes.
📦 Man
and Woman
behaves the same way for the 'owes money' association. However, the two classes cannot be simply replaced with a more general class Person
because of the need to distinguish between Man
and Woman
for the ‘marriage’ association. A solution is to add the Person
class as a super class and let Man
and Woman
inherit from Person
.
Inheritance implies the derived class can be considered as a sub-type of the base class (and the base class is a super-type of the derived class), resulting in an is a relationship.
Inheritance does not necessarily mean a sub-type relationship exists. However, the two often go hand-in-hand. For simplicity, at this point let us assume inheritance implies a sub-type relationship.
📦 In this class diagrams of a Snakes and Ladders board game,
SnakeHeadSquare
is aSquare
(aSnakeHeadSquare
is a square in which the head of a snake appears)SnakeTailSquare
is aSquare
Inheritance relationships through a chain of classes can result in inheritance hierarchies (aka inheritance trees).
📦 Two inheritance hierarchies/trees are given below. Note that Parrot
is aBird
as well as it is anAnimal
.
Multiple Inheritance is when a class inherits directly from multiple classes. Multiple inheritance is allowed among C++ classes but not among Java classes.
📦 The TA
class inherits from the Staff
class and the Student
.
Overriding
Method overriding is when a sub-class changes the behavior inherited from the parent class by re-implementing the method. Overridden methods have the same name, same type signature, and same return type.
📦 In the diagram below,
Report#print()
method is overridden byEvaluationReport#print()
method.Report#write(String)
method is overridden byEvaluationReport#write(String)
method.Report#read():String
method is NOT overridden byEvaluationReport#read(int):String
method. Reason: the two methods have different signatures;EvaluationReport#read(int):String
overloads (rather than overrides) theReport#read():String
method.
Design → Object Oriented Programming →
Overloading
Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.
Type Signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.
Method | Type Signature |
---|---|
int add(int X, int Y) |
(int, int) |
void add(int A, int B) |
(int, int) |
void m(int X, double Y) |
(int, double) |
void m(double X, int Y) |
(double, int) |
📦 In the class below, the calculate
method is overloaded because the two methods have the same name but different type signatures (String)
and (int[])
class RatingCalculator{
void calculate(String matric) { ... }
void calculate(int[] averages) { ... }
}
Overloading
Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.
Type Signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.
Method | Type Signature |
---|---|
int add(int X, int Y) |
(int, int) |
void add(int A, int B) |
(int, int) |
void m(int X, double Y) |
(int, double) |
void m(double X, int Y) |
(double, int) |
📦 In the class below, the calculate
method is overloaded because the two methods have the same name but different type signatures (String)
and (int[])
class RatingCalculator{
void calculate(String matric) { ... }
void calculate(int[] averages) { ... }
}
Interfaces
An interface is a behavior specification i.e. a collection of
There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts. --Oracle Docs on Java
📦 SalariedStaff
is an interface that contains two methods setSalary(int)
and getSalary()
. AcademicStaff
implements the SalariedStaff
interface. That means an AcademicStaff
object is able to support the behaviors setSalary(int)
and getSalary()
. That's why the same two methods also appear under the methods implemented by the AcademicStaff
class (in blue)
A class implementing an interface results in an is-a relationship, just like in class inheritance.
📦 In the example above, AcademicStaff
is aSalariedStaff
. An AcademicStaff
object can be used anywhere a SalariedStaff
object is expected e.g. SalariedStaff ss = new AcademicStaff()
.
Similarly, AdminStaff
is aSalariedStaff
too.
Abstract Classes
Abstract Class:
An abstract class is a class that is declared abstract
—it may or may not include abstract methods. Abstract classes cannot be instantiated, but they can be subclassed. -- Oracle's Java Documentation
An abstract method is simply the method interface without the implementation.
📦 The Account
class has an abstract method (addInterest()
).
Dynamic and Static Binding
Dynamic Binding (
Implementation → Object Oriented Programming →
Overriding
To override a method inherited from an ancestor class, simply re-implement the method in the target class.
📦 A simple example where the Report#print()
method is overridden by EvaluationReport#print()
method:
class Report{
void print(){
System.out.println("Printing report");
}
}
class EvaluationReport extends Report{
@Override // this annotation is optional
void print(){
System.out.println("Printing evaluation report");
}
}
class ReportMain{
public static void main(String[] args){
Report report = new Report();
report.print(); // prints "Printing report"
EvaluationReport evaluationReport = new EvaluationReport();
evaluationReport.print(); // prints "Printing evaluation report"
}
}
- Java - Overriding -- a tutorial by tutorialspoint.com
Which of these methods override another method?
- a
- b
- c
- d
- e
d
Explanation: Method overriding requires a method in a child class to use the same method name and same parameter sequence used by one of its ancestors
📦 Consider the code below. The declared type of s
is Staff
and it appears as if the adjustSalary(int)
operation of the Staff
class is invoked.
void adjustSalary(int byPercent) {
for (Staff s: staff) {
s.adjustSalary(byPercent);
}
}
However, at runtime the adjustSalary(int)
operation of the actual object will be called (i.e. adjustSalary(int)
operation of Admin
or Academic
). If the actual object does not override that
operation, the operation defined in the immediate superclass (in this case, Staff
class) will be called.
Static binding (aka early binding): When a method call is resolved at compile time.
In contrast,
Implementation → Object Oriented Programming →
Overloading
An operation can be overloaded inside the same class or in sub/super classes.
📦 The constructor of the Account
class below is overloaded because there are two constructors with different signatures: ()
and (String, String, double)
. Furthermore, the save
method in the Account
class is overloaded in the child class SavingAccount
.
class Account {
Account () {
...
}
Account (String name, String number, double balance) {
...
}
void save(int amount){
...
}
}
class SavingAccount extends Account{
void save(Double amount){
...
}
}
- Method Overloading in Java a tutorial from javatpoint.com. Also mentions the topic of a related topic type promotion.
📦 Note how the constructor is overloaded in the class below. The method call new Account()
is bound to the first constructor at compile time.
class Account {
Account () {
// Signature: ()
...
}
Account (String name, String number, double balance) {
// Signature: (String, String, double)
...
}
}
📦 Similarly, the calcuateGrade
method is overloaded in the code below and a method call calculateGrade("A1213232")
is bound to the second implementation, at compile time.
void calculateGrade (int[] averages) { ... }
void calculateGrade (String matric) { ... }
Substitutability
Every instance of a subclass is an instance of the superclass, but not vice-versa. As a result, inheritance allows substitutability : the ability to substitute a child class object where a parent class object is expected.
📦 an Academic
is an instance of a Staff
, but a Staff
is not necessarily an instance of an Academic
. i.e. wherever an object of the superclass is expected, it can be substituted by an object
of any of its subclasses.
The following code is valid because an AcademicStaff
object is substitutable as a Staff
object.
Staff staff = new AcademicStaff (); // OK
But the following code is not valid because staff
is declared as a Staff
type and therefore its value may or may not be of type AcademicStaff
, which is the type expected by variable academicStaff
.
Staff staff;
...
AcademicStaff academicStaff = staff; // Not OK
Polymorphism
Introduction
Polymorphism:
The ability of different objects to respond, each in its own way, to identical messages is called polymorphism. -- Object-Oriented Programming with Objective-C, Apple
Take the example of writing a payroll application for a university to facilitate payroll processing of university staff. Suppose an adjustSalary(int)
operation adjusts the salaries of all staff members. This operation will be executed
whenever the university initiates a salary adjustment for its staff. However, the adjustment formula is different for different staff categories, say admin and academic. Here is one possible way of designing the classes in the Payroll
system.
📦 Calling adjustSalary()
method for each Staff type:
Here is the implementation of the adjustSalary(int)
operation from the above design.
class Payroll1 {
ArrayList< Admin > admins;
ArrayList< Academic > academics;
// ...
void adjustSalary(int byPercent) {
for (Admin ad: admins) {
ad.adjustSalary(byPercent);
}
for (Academic ac: academics) {
ac.adjustSalary(byPercent);
}
}
}
Note how processing is similar for the two staff types. It is as if the type of staff members is irrelevant to how they are processed inside this operation! If that is the case, can the staff type be "abstracted away" from this method?
Here is such an implementation of adjustSalary(int)
:
class Payroll2 {
ArrayList< Staff > staff;
// ...
void adjustSalary(int byPercent) {
for (Staff s: staff) {
s.adjustSalary(byPercent);
}
}
}
Notice the following:
- Only one data structure
ArrayList< Staff >
. It contains bothAdmin
andAcademic
objects but treats them asStaff
objects - Only one loop
- Outcome of the
s.adjustSalary(byPercent)
method call depends on whethers
is anAcademic
orAdmin
object
The above code is better in several ways:
- It is shorter.
- It is simpler.
- It is more flexible (this code will remain the same even if more staff types are added).
This does not mean we are getting rid of the Academic
and Admin
classes completely and replacing them with a more general class called Staff
. Rather, this part of the code “treats” both Admin
and Academic
objects as one type called Staff
.
For example, ArrayList
staff contains both Admin
and Academic
objects although it treats all of them as Staff
objects. However, when the adjustSalary(int)
operation of these objects
is called, the resulting salary adjustment will be different for Admin
objects and Academic
objects. Therefore, different types of objects are treated as a single general type, but yet each type of object exhibits a
different kind of behavior. This is called polymorphism (literally, it means “ability to take many forms”). In this example, an object that is perceived as type Staff
can be an Admin
object or an Academic
object.
Mechanism
Three concepts combine to achieve polymorphism: substitutability, operation overriding, and dynamic binding.
- Substitutability: Because of substitutability, you can write code that expects object of a parent class and yet use that code with objects of child classes. That is how polymorphism is able to treat objects of different types as one type.
- Overriding: To get polymorphic behavior from an operation, the operation in the superclass needs to be overridden in each of the subclasses. That is how overriding allows objects of different sub classes to display different behaviors in response to the same method call.
- Dynamic binding: Calls to overridden methods are bound to the implementation of the actual object's class dynamically during the runtime. That is how the polymorphic code can call the method of the parent class and yet execute the implementation of the child class.
Conceptualizing an OOP Solution
Introduction
You can use models to analyze and design a software before you start coding.
Suppose You are planning to implement a simple minesweeper game that has a text based UI and a GUI. Given below is a possible OOP design for the game.
Before jumping into coding, you may want to find out things such as,
- Is this class structure is able to produce the behavior we want?
- What API should each class have?
- Do we need more classes?
To answer those questions, you can analyze the how the objects of these classes will interact with each other to produce the behavior you want.
Basic
As mentioned in [
Design → Object Oriented Programming → Conceptualizing OO Solutions →
Introduction
You can use models to analyze and design a software before you start coding.
Suppose You are planning to implement a simple minesweeper game that has a text based UI and a GUI. Given below is a possible OOP design for the game.
Before jumping into coding, you may want to find out things such as,
- Is this class structure is able to produce the behavior we want?
- What API should each class have?
- Do we need more classes?
To answer those questions, you can analyze the how the objects of these classes will interact with each other to produce the behavior you want.
Let us start by modelling a sample interaction between the person playing the game and the TextUi object.
newgame
and clear x y
represent commands typed by the Player
on the TextUi
.
How does the TextUi
object carry out the requests it has received from player? It would need to interact with other objects of the system. Because the Logic
class is the one that controls the game logic, the TextUi
needs to collaborate with Logic
to fulfill the newgame
request. Let us extend the model to capture that interaction.
W = Width of the minefield; H = Height of the minefield
The above diagram assumes that W
and H
are the only information TextUi
requires to display the minefield to the Player
. Note that there could be other ways of doing this.
The Logic
methods we conceptualized in our modelling so far are:
Now, let us look at what other objects and interactions are needed to support the newGame()
operation. It is likely that a new Minefield
object is created when the newGame()
method is called.
Note that the behavior of the Minefield
constructor has been abstracted away. It can be designed at a later stage.
Given below are the interactions between the player and the Text UI for the whole game.
💡 Note that
📺 Defining the architecture-level APIs for a small Tic-Tac-Toe game:
Intermediate
Continuing with the example in [
Design → Object Oriented Programming → Conceptualizing OO Solutions →
Basic
As mentioned in [
Design → Object Oriented Programming → Conceptualizing OO Solutions →
Introduction
You can use models to analyze and design a software before you start coding.
Suppose You are planning to implement a simple minesweeper game that has a text based UI and a GUI. Given below is a possible OOP design for the game.
Before jumping into coding, you may want to find out things such as,
- Is this class structure is able to produce the behavior we want?
- What API should each class have?
- Do we need more classes?
To answer those questions, you can analyze the how the objects of these classes will interact with each other to produce the behavior you want.
Let us start by modelling a sample interaction between the person playing the game and the TextUi object.
newgame
and clear x y
represent commands typed by the Player
on the TextUi
.
How does the TextUi
object carry out the requests it has received from player? It would need to interact with other objects of the system. Because the Logic
class is the one that controls the game logic, the TextUi
needs to collaborate with Logic
to fulfill the newgame
request. Let us extend the model to capture that interaction.
W = Width of the minefield; H = Height of the minefield
The above diagram assumes that W
and H
are the only information TextUi
requires to display the minefield to the Player
. Note that there could be other ways of doing this.
The Logic
methods we conceptualized in our modelling so far are:
Now, let us look at what other objects and interactions are needed to support the newGame()
operation. It is likely that a new Minefield
object is created when the newGame()
method is called.
Note that the behavior of the Minefield
constructor has been abstracted away. It can be designed at a later stage.
Given below are the interactions between the player and the Text UI for the whole game.
💡 Note that
📺 Defining the architecture-level APIs for a small Tic-Tac-Toe game:
This interaction adds the following methods to the Logic
class
clearCellAt(int x, int y)
markCellAt(int x, int y)
getGameState() :GAME_STATE (GAME_STATE: READY, IN_PLAY, WON, LOST, …)
And it adds the following operation to Logic API:
getAppearanceOfCellAt(int,int):CELL_APPEARANCE (CELL_APPEARANCE: HIDDEN, ZERO, ONE, TWO, THREE, …, MARKED, INCORRECTLY_MARKED, INCORRECTLY_CLEARED)
In the above design, TextUi
does not access Cell
objects directly. Instead, it gets values of type CELL_APPEARANCE
from Logic
to be displayed as a minefield to the player. Alternatively, each
cell or the entire Minefield can be passed directly to TextUi
.
Here is the updated class diagram:
The above is for the case when Actor Player
interacts with the system using a text UI. Additional operations (if any) required for the GUI can be discovered similarly. Suppose Logic
supports a reset()
operation.
We can model it like this:
Our current model assumes that the Minefield
object has enough information (i.e. H, W, and mine locations) to create itself.
An alternative is to have a ConfigGenerator
object that generates a string containing the minefield information as shown below.
In addition, getWidth()
, getHeight()
, markCellAt(x,y)
and clearCellAt(x,y)
can be handled like this.
The updated class diagram:
How is getGameState()
operation supported? Given below are two ways (there could be other ways):
Minefield
class knows the state of the game at any time.Logic
class retrieves it from theMinefield
class as and when required.Logic
class maintains the state of the game at all times.
Here’s the SD for option 1.
Here’s the SD for option 2. Here, assume that the game state is updated after every mark/clear action.
It is now time to explore what happens inside the Minefield
constructor? One way is to design it as follows.
Now let us assume that Minesweeper
supports a ‘timing’ feature.
Updated class diagram:
💡 When designing components, it is not necessary to draw elaborate UML diagrams capturing all details of the design. They can be done as rough sketches. For example, draw sequence diagrams only when you are not sure which operations are required by each class, or when you want to verify that your class structure can indeed support the required operations.
Miscellaneous
Miscellaneous
What’s the difference between a Class, an Abstract Class, and an Interface?
- An interface is a behavior specification with no implementation.
- A class is a behavior specification + implementation.
- An abstract class is a behavior specification + a possibly incomplete implementation.
How does overriding differ from overloading?
Overloading is used to indicate that multiple operations do similar things but take different parameters. Overloaded methods have the same method name but different method signatures and possibly different return types.
Overriding is when a sub-class redefines an operation using the same method name and the same type signature. Overridden methods have the same name, same method signature, and same return type.