D.1 Objects as a programming concept
The topic introduces object-oriented programming, emphasizing the nature of an object, its components, and the difference between an object and instantiation. It covers the construction and interpretation of UML diagrams, the process of decomposition into related objects, and the relationships between objects. The need to reduce dependencies between objects and the construction of related objects for a given problem is discussed. The topic also explains the need for different data types and how data items can be passed as parameters.
Common Core (SL and HL)
D.1 Objects as a programming concept (6 hours)
Key Terms
- Object
- Instantiation
- UML
- Decomposition
- Relationships
- Dependencies
- Data types
- Parameters
D.1.1 Outline the general nature of an object
We can represent real-life objects as having states and behaviours. Identifying these states and behaviours in real-life objects can help us to understand how these objects can be translated into an object-oriented program. The complexity of the object will result in a greater number of states and behaviours. [1]
Key characteristics of an object:
Encapsulation: The bundling of data and methods into a single unit, hiding the internal implementation details and providing a well-defined interface for interaction.
State: The current values of an object's attributes, reflecting its properties or characteristics at a given time.
Behavior: The actions or operations that an object can perform, defined by its methods. These methods can modify the object's state or interact with other objects.
Example:
Consider an object representing a "Car."
Attributes: Make, model, colour, speed, fuel level.
Methods: Accelerate, brake, turn, honk.
The car's attributes define its current state (e.g., red Toyota going 50 mph), while its methods represent the actions it can take.
It is this type of modelling that leads to an object-oriented program. In this section, we will look at how this is done.
An object can be programmed using a combination of data and the operations that can be performed with that data. Each data part of an object is referred to as a data member while the operations can be referred to as methods. The current state of an object is stored in its data members and that state can be changed directly or accessed through the methods. There are several common categories of operations (methods):
- the construction of objects;
- operations that either set (mutator methods) or return (accessor methods) the data members;
- operations unique to the data type;
- operations used internally by the object.
An object can be represented in JAVA by a class which offers a blueprint for us to be able to create as many objects as we require.
Let's consider a student object and how we can create a class to represent it. We can consider a simple version of the student. The student has three data members (states) which are name, age and grade. Several methods allow us to change the data (mutators) and several methods allow us to inspect that data (mutators). This class acts as a template to create several unique students.
public class Student {
String name;
int age;
int grade;
// Getter (or accessor) methods
public String getName () {
return name;
}
public int getAge () {
return age;
}
public int getMark () {
return mark;
}
// Setter (or mutator) methods
public void setName (String newName) {
name = newName;
}
public void setAge (int newAge) {
age = newAge;
}
public void setMark (int newMark) {
mark = newMark;
}
}
D.1.2 Distinguish between an object (definition, template or class) and instantiation
The process of creating an instance of an object from a class is called instantiation.
The class is an abstract representation of an object. It is the blueprint (template) of an object that defines its actions and properties.
In OOP, instantiation is done by using a particular method called a constructor. The constructor is a method with the same name as the class.
Look at the code below to see an example of a constructor. Examples of these can be seen in the Student class above.
public class Student {
String name;
int age;
int grade;
//Constructor
public Student (String newName, int newAge, int newGrade) {
name = newName;
age = newAge;
grade = newGrade;
}
}
There are three steps when creating an object from the class:
- Declaration:
- a variable declaration with a variable name with and object type
- Instantiation
- the “new” keyword is used
- Initialisation
- the “new” keyword calls the constructor to initialise the new object.
A new student can be instantiated with the following code:
Student s1 = new Student(Toni, 17, 75);
The new student is now instantiated in a physical memory location. It is important to note that the variable s1 does not hold the object but only refers to the object in a memory location. This concept of how object references work is covered in more detail in D.4.5.
It is also worth noting that every class has a constructor. Even if one is not explicitly written the JAVA compiler builds a default constructor for the class. For example for a simple class such as:
public class Student {
String name;
int age;
int grade;
}
We can instantiate a student with the following code.
Student s2 = new Student();
This will create a default student. To define the state of that student we could code the following.
s2.name = "Molly";
s2.age = 16;
s2.grade = 86;
D.1.3-1.4 Unified Modelling Language (UML)
In OOP a UML diagram is constructed to show the composition of a class and and its relationship with other classes. A UML diagram is a rectangle divided into three components. The class name, the data and the behaviours or methods.
Class name |
Data |
Actions |
Consider the BankAcount class shown below.
public class BankAccount {
private int accountNumber;
private String accountName;
private double balance;
public void deposit (double d) {
balance = balance + d;
}
public void withdraw (double w) {
balance = balance -w;
}
public double showBalance () {
return balance;
}
}
This can be represented in the following UML diagram.
BankAccount |
---|
-accountNumber: int -accountName: String -balance: double |
+deposit (double): void +withdraw (double): void +showBalance() : double |
UML Variables
The variables are in three parts:
- Visibility
- - Private
- + Public
- # Protected
- Variable name
- Datatype
The "+", "-" and "#" signs denote whether the data or method is private, public or protected. The JAVA keywords are covered in more detail later in D.3.3 to understand their significance here is what they mean.
- Private variables can only be accessed from within the class. It is the most restrictive level of access.
- Public variables can be accessed anywhere it is the least restrictive access.
- Protected variables can be accessed from the same class and the same package.
This can be seen summarised below[2]:
Modifier | Class | Package | Subclass | World |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
no modifier | Y | Y | N | N |
private | Y | N | N | N |
UML methods
The methods are in four parts:
- Visibility
- - Private
- + Public
- # Protected
- Method name
- Method parameters
- Return type
UML general
In general, the UML diagram can be represented as follows:
Class Name |
---|
vis attribute: type |
vis operation (arg list): return type |
Where:
- vis = visibility (+ for public, - for private)
- attribute = data member (aka field)
- operation = method (or constructor)
Note:
- The arg list is a list of parameter types (e.g., int, double, String); parameter names are not included in the UML class diagram
- Methods that don't return a value (i.e. void methods) should give a return type of void
- Static methods and fields are indicated by underlining
- Constant (i.e. final) fields are indicated via naming convention: constants should be in ALL_CAPS
D.1.5 Decomposition
In the context of object-oriented programming (OOP), decomposition refers to the process of breaking down a complex problem or system into smaller, more manageable parts called objects. Each object represents a distinct entity or concept within the system, encapsulating its data (attributes) and behaviours (methods).
Key aspects of decomposition in OOP:
- Identifying Objects: The first step is to analyze the problem domain and identify the key entities or concepts that can be modelled as objects. These objects should be relevant to the problem and have clear responsibilities within the system.
- Defining Attributes and Methods: For each object, determine the relevant data (attributes) that it needs to store and the actions (methods) that it can perform. Attributes represent the object's state, while methods define its behaviour.
- Establishing Relationships: Objects in an OOP system rarely exist in isolation. They interact with each other through relationships. The IB Computer Science syllabus focuses on three primary relationships (which are covered in more detail later):
- Dependency: One object relies on another object to perform a specific task.
- Association/Aggregation: One object is composed of other objects, forming a "has-a" relationship.
- Inheritance: One object (subclass) inherits properties and behaviours from another object (superclass), forming an "is-a" relationship.
Benefits of Decomposition:
- Improved Modularity: By breaking down a system into smaller objects, each with its responsibility, the code becomes more modular and easier to understand, maintain, and extend.
- Code Reusability: Objects can be reused in different parts of the program or even in different programs altogether, saving development time and effort.
- Enhanced Abstraction: Objects provide a level of abstraction, hiding implementation details and allowing developers to focus on the higher-level functionality of the system.
- Easier Testing and Debugging: Since each object has a well-defined interface and responsibility, it is easier to isolate and test individual components, making debugging more manageable.
Example:
Consider a library management system. You can decompose it into objects like:
- Book: Attributes: title, author, ISBN, availability. Methods: check out, return.
- Member: Attributes: name, ID, borrowed books. Methods: borrow book, return book.
- Library: Attributes: a collection of books, members. Methods: add books, remove books, search books, register members, etc.
These objects interact with each other to provide the overall functionality of the library system.
By understanding the principles of decomposition in OOP, you can design well-structured, maintainable, and scalable software systems that effectively model real-world problems.
D.1.6 Relationships between objects
There are three ways we need to consider when describing relationships between classes:
- Inheritance (IS A relationship)
- Association (HAS A relationship)
- Dependency (USES relationship)
Inheritance (IS A relationship)
This type of relationship is an IS A relationship. It can be implemented through a class or interface Inheritance.
Examples of this are:
- A banana IS A fruit
- A car IS A vehicle
It is a unidirectional relationship so for example, a house is a building but a building is not a house.
This relationship uses the JAVA keywords extends or implements. The inheritance relationship is denoted by an arrow with a solid line and a hollow arrow from the subclass to the superclass as illustrated below.
UML - Inheritance
JAVA - Inheritance
Consider the following code to demonstrate an inheritance relationship. The student uses the keyword extends to inherit the parent class. This means that it also inherits the variables (and any methods) from the Person class and adds its own unique variable GPA.
public class Person {
String surname:
String forename;
}
public class Student extends Person {
double GPA;
}
What this means is discussed in more detail in D.2.2. Suffice it to say that in this case the subclass Student literally inherits the characteristics of the parent class Person and can use the data members and methods in that superclass.
Association (HAS A relationship)
Definition: A general relationship between two classes where one class uses or interacts with another. It's a very broad term and can be further classified into aggregation and composition.
Relationship: Often described as a "has-a" or "uses-a" relationship.
Lifetime: The lifetime of the associated objects is independent. One object can exist without the other.
Ownership: There's no strong ownership between the objects.
UML Notation: A simple line connecting the two classes.
Example: A student has a teacher
Aggregation
Definition: A specialized form of association where one class (the whole) is made up of another class (the part), but the part can exist independently.
Relationship: Also described as a "has-a" relationship, but with a weaker sense of ownership compared to composition.
Lifetime: The lifetime of the part object is independent of the whole object. If the whole is destroyed, the part can still exist.
Ownership: There's some ownership, but it's not as strong as in composition.
UML Notation: A hollow diamond shape on the whole class side, connected to the part class with a line.
Example: A Department has Teachers. If the department is closed, the teachers still exist.
Composition
Definition: The strongest form of association. One class (the whole) is composed of another class (the part), and the part cannot exist without the whole.
Relationship: Described as a "part-of" relationship.
Lifetime: The lifetime of the part object is completely dependent on the whole object. If the whole is destroyed, the part is also destroyed.
Ownership: The whole object has exclusive ownership of the part object.
UML Notation: A filled diamond shape on the whole class side, connected to the part class with a line.
Example: A House has Rooms. If the house is demolished, the rooms cease to exist.
Association summary
Aggregation and Composition are subsets of Association meaning they are specific cases of association.
Feature | Association | Aggregation | Composition |
Relationship | Has-a/Uses-a | Has-a | Part-of |
Lifetime | Independent | Independent | Dependent |
Ownership | Weak | Moderate | Strong |
UML notation | Line | Hollow diamond | Filled diamond |
Dependency (USES A relationship)
Definition: A relationship between two classes where one class (the dependent) depends on another class (the provider) for its functionality.
Relationship: The dependent class uses the provider class, often through method parameters, return types, or local variables. It's a "uses-a" relationship, but typically for a shorter duration compared to association.
Lifetime: The dependent object doesn't necessarily control the lifetime of the provider object. The provider object may be created and destroyed independently.
Ownership: There's no ownership involved in a dependency relationship. The dependent class simply relies on the provider class's interface.
UML Notation: A dashed arrow pointing from the dependent class to the provider class.
JAVA - Dependency
Here we can see a simple example of dependency in JAVA. In this case, the gambler depends on the Die to work. If the Die ceases to exist the Gambler can no longer use it.
class Player {
public void TakeTurn(Die die){
die.Roll(); ...
}
}
class Die {
public void Roll() {
... }
}
Summary of UML connectors
D.1.7 Reducing dependencies
Why Reduce Dependencies?
Keeping Your Code Tidy and Flexible
Imagine building a house of cards. Each card leans on others for support. If you pull out one card carelessly, the whole structure might collapse! This is similar to what can happen in software when parts of your code are too tightly connected, or highly dependent on each other.
The Problem with connected code
High dependency creates a web of connections within your code. This can lead to several headaches:
- The Ripple Effect: Changing one part of your code can unexpectedly break other seemingly unrelated parts. This makes updates and bug fixes a nightmare.
- Code Reusability Takes a Hit: When parts are tightly interwoven, it's difficult to reuse them in other projects or even in different parts of the same project. Imagine trying to reuse a room from your house of cards in a different structure!
- Maintenance Mayhem: The more dependencies you have, the harder it is to understand, debug, and maintain your code. It's like trying to untangle a giant ball of yarn.
- Testing Troubles: Testing becomes more complex as you need to consider how changes in one part affect all its dependents.
Example
You have a program that calculates taxes. It depends directly on a specific tax law calculation module. But tax laws change frequently! Every time the law changes, you have to modify your main program. This is a high dependency.
Now, imagine you introduce an interface called "TaxCalculator". Your main program now depends on this interface, not the specific tax law module. Different modules can implement the "TaxCalculator" interface for different tax laws.
When the law changes, you simply plug in a new module. This is low dependency.
Benefits of Reducing Dependencies
- Increased Flexibility: Easily adapt to changes and add new features without causing widespread breakage.
- Improved Code Reusability: Reuse components in different contexts, saving time and effort.
- Simplified Maintenance: Understand, debug, and maintain code more easily.
- Enhanced Testability: Test individual components in isolation, making testing more effective.
In essence, reducing dependencies helps you create code that is:
- Modular: You can easily assemble and rearrange components like building with Lego blocks.
- Robust: Less prone to breaking when changes are made.
- Maintainable: Easier to understand and update over time.
By striving for low dependency, you're building a more stable and adaptable software foundation for the future.
How to Reduce Dependencies
1. Interfaces: Promises, Not Implementations
Think of an interface like a contract. It defines a set of methods a class must provide but doesn't specify how those methods should be implemented. This is powerful because it allows different classes to fulfil the same contract in their way.
How this reduces dependency:
- Abstraction: Instead of depending on a concrete class, your code depends on the interface. This means you can swap out different implementations without affecting other parts of your code.
- Loose Coupling: Classes are less tightly connected, making changing one without impacting others easier.
Example:
Imagine an Authentication interface with a login() method. You could have different classes like GoogleAuth, FacebookAuth, and EmailAuth, each implementing this interface. Your application can then work with the Authentication interface without knowing the specific authentication method being used.
// 1. Interfaces Example: Authentication
interface Authentication {
boolean login(String username, String password);
}
class GoogleAuth implements Authentication {
@Override
public boolean login(String username, String password) {
// Logic to authenticate with Google
return true; // Or false if authentication fails
}
}
class FacebookAuth implements Authentication {
@Override
public boolean login(String username, String password) {
// Logic to authenticate with Facebook
return true; // Or false if authentication fails
}
}
// Usage:
Authentication auth = new GoogleAuth(); // Or new FacebookAuth();
if (auth.login("user", "password")) {
// Authentication successful
}
2. Abstract Classes: A Middle Ground
Abstract classes are a hybrid between interfaces and concrete classes. They can have both abstract methods (no implementation) and concrete methods (with implementation). This allows you to define some common behaviour while still leaving room for specialization.
How this reduces dependency:
- Partial Implementation: Provides a base implementation that subclasses can inherit, reducing code duplication.
- Flexibility: Allows subclasses to customize behaviour while still adhering to a common structure.
Example:
A Shape class could be abstract, with an abstract method calculateArea(). Subclasses like Circle and Rectangle would then provide concrete implementations for calculateArea().
// 2. Abstract Classes Example: Shape
abstract class Shape {
abstract double calculateArea();
}
class Circle extends Shape {
double radius;
@Override
double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double width;
double height;
@Override
double calculateArea() {
return width * height;
}
}
// Usage:
Shape circle = new Circle();
Shape rectangle = new Rectangle();
3. Design Patterns: Tried and True Solutions
Design patterns are reusable solutions to common software design problems. Many patterns specifically address dependency management.
How this reduces dependency:
- Decoupling: Patterns like Strategy, Factory, and Observer help decouple objects and reduce dependencies.
- Flexibility: Make it easier to change and adapt your code without affecting other parts.
Example:
The Strategy pattern encapsulates different algorithms (like sorting algorithms) and allows them to be used interchangeably. This reduces the dependency between the client code and the specific algorithm.
// 3. Design Patterns Example: Strategy (simplified)
interface SortingStrategy {
void sort(int[] array);
}
class BubbleSort implements SortingStrategy {
@Override
public void sort(int[]
array) {
// Bubble sort implementation
}
}
class QuickSort implements SortingStrategy {
@Override
public void sort(int[] array) {
// Quick sort implementation
}
}
class Sorter {
private SortingStrategy strategy;
public Sorter(SortingStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] array) {
strategy.sort(array);
}
}
// Usage:
Sorter bubbleSorter = new Sorter(new BubbleSort());
Sorter quickSorter = new Sorter(new QuickSort());
In Summary
These techniques aim to create more modular, flexible, and maintainable code by:
- Reducing direct dependencies between concrete classes.
- Promoting abstraction and loose coupling.
- Encapsulating variability and allowing for easier changes.
By using these techniques effectively, you can make your code more robust and easier
D.1.8 Constructing related objects
Example 1: Banking System
Java
class Account { // Base class
private String accountNumber;
private double balance;
public Account(String accountNumber) {
this.accountNumber = accountNumber;
this.balance = 0.0;
}
// Methods: deposit, withdraw, getBalance
}
class SavingsAccount extends Account { // Subclass (Inheritance)
private double interestRate;
public SavingsAccount(String accountNumber, double interestRate) {
super(accountNumber);
this.interestRate = interestRate;
}
// Methods: calculateInterest
}
class Customer { // Class with Aggregation
private String name;
private List<Account> accounts;
public Customer(String name) {
this.name = name;
this.accounts = new ArrayList<>();
}
// Methods: addAccount, removeAccount
}
Relationships:
SavingsAccount inherits from Account (inheritance).
Customer has a list of Account objects (aggregation).
Example 2: School Management
class Student {
private String name;
private int grade;
// Constructor and methods
}
class Course {
private String name;
private List<Student> enrolledStudents;
// Constructor and methods
}
class Teacher {
private String name;
private List<Course> coursesTaught;
// Constructor and methods
}
Relationships:
Course has a list of Student objects (aggregation).
Teacher has a list of Course objects (aggregation).
Example 3: Online Store
class Product {
private String name;
private double price;
// Constructor and methods
}
class Order {
private int orderID;
private List<Product> products;
// Constructor and methods
}
class Customer {
private String name;
private List<Order> orders;
// Constructor and methods
}
Relationships:
Order has a list of Product objects (aggregation).
Customer has a list of Order objects (aggregation).
Exam Tips:
- Identify the main entities in the problem.
- Determine the attributes (data) each entity needs.
- Think about the actions (methods) each entity can perform.
- Consider how the entities relate to each other (inheritance, aggregation, dependency).
Remember to explain these relationships clearly in your exam answers, using the correct terminology and providing justification for your design choices.
D.1.9 Data Types
Data types identify the type of data that is being used in a program. JAVA has several primitive data types. The ones required by the IB are detailed below.
Integer
The int data type is a 32-bit signed two's complement integer, which has a minimum value of -231 and a maximum value of 230 (231-1).
Example:
int age = 32;
Real
In JAVA there are two data types to represent a real number:
- float
- The float data type is a single-precision 32-bit IEEE 754 floating point.
- double
- The double data type is a double-precision 64-bit IEEE 754 floating point.
Example
double decNumber = 13.24
String
Strings, which are widely used in Java programming, are a sequence of characters.
In the Java programming language, strings are objects.
Example
String greetings = “Hello World”;
Boolean
The boolean data type has only two possible values: true and false.
Use this data type for simple flags that track true/false conditions.
This data type represents one bit of information, but its "size" isn't something that's precisely defined.
Example
boolean found = false;
D.1.10 Parameters
In programming a parameter is a special type of variable that is passed to a method. In the method below the parameters are u, a and t.
public double calcVelocity (double u, double t, double a){
double v = u + (a*t);
return v;
}
Note that the parameters refer to the variables in the method. Arguments are the actual values passed to the method.
Parameter types
You can use any data type for a parameter including:
- Primitive types such as:
- double
- int
- float
- Reference types such as:
- Arrays
- Objects
Parameter names
Consider the code below.
public class Triangle {
private double base, height;
public void calcArea(double b, double h) {
...
}
}
In this class Triangle the base and height are class fields and b and h are method parameters. When arguments are passed to the method calcArea they refer only to the method parameters and the class fields will not be changed.
References
- ^ https://docs.oracle.com/javase/tutorial/java/concepts/index.html
- ^ https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html
Quiz D.1 Objects as a programming concept
Which of the following is NOT a key characteristic of an object in OOP?
What is the process of creating an object from a class called?
In a UML diagram, what does a minus sign (-) before an attribute or method indicate?
Which type of relationship between objects is best described as an "IS-A" relationship?
What is the purpose of decomposition in OOP?
Which of the following is NOT a benefit of reducing dependencies between objects?
What type of UML relationship is represented by a dashed arrow?
Which data type would be most suitable for storing a student's age?
What is the term for the actual values passed to a method when it is called?
Which of the following is an example of aggregation?