Object-Oriented Programming (OOP)
Object-oriented programming is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields, and code, in the form of procedures
How to think like a programmer to solve problems:
- Analysis: Understand the problem
- Design: Plan your solution
- Programming: Build the solution
Objects
Objects are instances of a class
- Objects are instantiated from Classes
- Objects are the building blocks of object-oriented programming
- Attributes, properties, characteristics, state, fields, variables
Objects are often referred to as Nouns:
- Things, People, Places, Ideas, Concepts
- Can "The" be placed in front of it: "The mug", "The bank account", "The time"
- Not verbs: "The texting", or "The ringing" as they describe the behaviour of an object, not the object itself
All objects have:
Identity: A unique identifier
- Olivia's coffee mug
Attributes: Properties, characteristics, state, fields, variables
- colour, size, fullness
Behaviours: Operations, methods, functions
fill()
,empty()
,clean()
What is object assignment rule?
- Objects are assigned by reference, not by value
- When you assign an object to a variable, you are assigning a reference to the object, not the object itself
Classes
Class is the code template for creating program Objects
- A Class can be used to create multiple objects
- A Class itself is not an Object
Class Components:
Name (type): What is it? (
RoundCookie
,Car
)Attributes (properties, data): What describes it? (
weight
,colour
,icing
)Behaviour (operations): What can it do? (
decorate()
,consume()
)
Properties
Properties are the characteristics of an object
- Also known as attributes, fields, variables, or data members
Methods
A program procedure that can return a value
- Methods are functions that are defined as part of a class
- They can only access data known to it's object
Fundamental Ideas
There are 4 fundamental ideas in OOP:
Abstraction
Abstraction refers to only showing essential details and keep everything else hidden
Reduce complexity by hiding unnecessary details
Modelling the relevant attributes and interactions of entities as classes to define an abstract representation of a system
Interface and Implementation:
- The interface refers to the way sections of code can communicate with one another. This typically is done through methods that each class can access
- The implementation of these methods, or how these methods are coded, should be hidden
- If classes are entangled, then one change creates a ripple effect that causes many more changes
- Creating an interface through which classes can interact ensures that each piece can be individually developed
- Abstraction allows the program to be worked on incrementally and prevents it from becoming entangled and complex
- Determine specific points of contact that can act as an interface between classes, and only worry about the implementation when coding it
Focus on essential qualities of something rather than one specific example
Understanding the idea
Discard what's unimportant
Example: To send an email using the class EmailService
, we don't need to know how the email is sent. We only need to know the method sendEmail()
to send the email
class EmailService {
public void sendEmail() {
connect();
authenticate();
// Code to send email
disconnect();
}
private void connect() {
// Code to connect to email server
}
private void authenticate() {
// Code to authenticate
}
private void disconnect() {
// Code to disconnect
}
}
Encapsulation
Encapsulation refers to bundling data with methods that can operate on that data within a class. Essentially, it is the idea of hiding data within a class, preventing anything outside that class from directly interacting with it
Hiding the internal state and functionality of an object and only allowing access through a public set of functions
Keeps the programmer in control of access to data
Prevents the program from ending up in any strange or unwanted states
Containing the elements of an object, not just keep them together, but also protect them
We bundle an object's attributes or data along with the methods that operate on that data within the same unit or the same class
Restricting access to some of the object's components
Black Box Testing: Closing off the inner workings of the cookie jar and only revealing specific inputs and outputs. You don't need to know how the 'request cookie' method is implemented under the hood to use it
It's not about being secretive. It's about reducing dependencies between different parts of the application
Changes in one place won't cause a domino effect and require multiple changes elsewhere
Example: Lets say we have a BankAccount
class with a balance
attribute. We don't want to allow the balance to be set to a negative value. We can use encapsulation to ensure that the balance is only updated through a method that checks if the new balance is valid
- This prevents the balance from being set to an invalid value
class BankAccount {
private double balance;
public void Deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public void Withdraw(double amount) {
if (amount > 0 && balance - amount >= 0) {
balance -= amount;
}
}
public double GetBalance() {
return balance;
}
}
Inheritance
Inheritance is the principle that allows classes to derive from other classes
- Ability to create new abstractions based on existing abstractions
- Base new object or class on an existing one
- Inherit the existing attributes and methods
- Great form of code reuse
Inheritance involves creating new classes (subclasses or derived classes) based on existing classes (superclasses or base classes). Subclasses inherit properties and behaviours from their superclasses and can also add new features or override existing ones
- Superclass (Parent/Base class) --> Inherited by --> Subclass (Child/Derived class)
- Inheritance is often described in terms of an "is-a" relationship (a subclass is a type of superclass)
Example: A Car
class can be a superclass, and a SUV
class can be a subclass. The SUV
class inherits the properties and behaviours of the Car
class
class Car {
public void Drive() {
// Code to drive the car
}
public void Stop() {
// Code to stop the car
}
}
class SUV : Car {
public void OffRoad() {
// Code to drive off-road
}
}
Types of Inheritance:
- Multiple Inheritance: Inherit from multiple Base classes (can get confusing). C++, Python
- Single Inheritance: Inherit from single Super class. Java, C#, Swift, Ruby
Fragile Base Class Problem
The fragile base class problem is a software design issue that arises in object-oriented programming when changes made to a base class can inadvertently break the functionality of derived classes. This problem occurs due to the tight coupling between the base class and the derived classes in inheritance hierarchies
Inheritance Coupling: Inheritance creates a tight coupling between the base class (superclass) and the derived classes (subclasses). Any changes made to the base class can potentially affect the behaviour of the derived classes
Limited Extensibility: The fragile base class problem limits the extensibility of the software system, as modifications to the base class can become increasingly risky and costly over time. Developers may avoid making changes due to the fear of breaking existing functionality (brittle software)
Mitigation strategies:
- Favour Composition over Inheritance: Use composition to create relationships between classes instead of inheritance. This approach can help reduce the coupling between classes and make the system more flexible and maintainable
- Software developers can use design principles such as the Open/Closed Principle (OCP) and the Dependency Inversion Principle (DIP) to design classes that are more extensible and less prone to the fragile base class proble
Polymorphism
Polymorphism describes methods that are able to take on many forms
Polymorphism means having many forms
- Poly: Many
- Morph: Form
Polymorphism is the ability of an object to take on many forms. It allows methods to do different things based on the object that they are acting upon
- Ability to implement inherited properties or methods in different ways across multiple abstractions
There are multiple form of polymorphism:
Dynamic Polymorphism: Uses the same interface for methods on different types of objects
- Occurs during the runtime of the program
- Describes when a method signature is in both a subclass and a superclass
- The methods share the same name but have different implementation
- Method overriding: When a subclass provides a specific implementation of a method that is already provided by one of its superclasses
- Inherit from the same abstract class with same methods
- Inheritance, abstract classes, and interfaces are all possible implementations of polymorphism
Example: A
Shape
class with adraw()
method. ACircle
class and aSquare
class that inherit from theShape
class. TheCircle
class and `Squarecsharpclass Shape { public virtual void Draw() { // Code to draw the shape } } class Circle : Shape { public override void Draw() { // Code to draw a circle } } class Square : Shape { public override void Draw() { // Code to draw a square } } var square = new Square(); square.Draw(); // Draw a square var circle = new Circle(); circle.Draw(); // Draw a circle
Static (Compile-Time) Polymorphism: Uses method overloading
Occurs during compile-time rather than during runtime
This refers to when multiple methods with the same name but different arguments are defined in the same class
This can done through method overloading. Despite the methods having the same name, their signatures are different due to their different arguments
Ways to differentiate methods of the same name:
- Different number of parameters
- Different types of parameters
- Different order of parameters
Method overloading can cause trouble of you do not keep straight which parameters you need for which implementation. Calling the wrong method because not realizing different arguments types were passed
Implements multiple methods with the same name, but different parameters
Example: A
Calculator
class with anAdd()
method. TheAdd()
method can take two integers, two doubles, or two stringscsharpclass Calculator { public int Add(int a, int b) { return a + b; } public double Add(double a, double b) { return a + b; } public string Add(string a, string b) { return a + b; } } var calculator = new Calculator(); calculator.Add(1, 2); // 3 calculator.Add(1.5, 2.5); // 4.0 calculator.Add("Hello", "World"); // HelloWorld
Coupling and Cohesion
Coupling and Cohesion are two important concepts in software design
Coupling
Coupling is the degree of interdependence between software modules
Tight Coupling: When a class is dependent on another class
- Changes in one class require changes in another class
- Can lead to difficult to maintain code
Loose Coupling: When a class is not dependent on another class
- Changes in one class do not require changes in another class
- Allows for greater flexibility and maintainability
To achieve loose coupling:
- Dependency Injection: Pass dependencies to a class instead of creating them inside the class
- Interfaces: Use interfaces to define the contract between classes
- Event-Driven Programming: Use events to communicate between classes
- Publish-Subscribe Pattern: Use the publish-subscribe pattern to communicate between classes
Cohesion
Cohesion is the degree to which the elements inside a module belong together
High Cohesion: When the elements inside a module belong together
- The module is focused on a single task
- The module is easier to understand and maintain
Low Cohesion: When the elements inside a module do not belong together
- The module is focused on multiple tasks
- The module is harder to understand and maintain
To achieve high cohesion:
- Single Responsibility Principle (SRP): A class should have only one reason to change
- Separation of Concerns: Divide a program into distinct sections, each addressing a separate concern
- Modular Programming: Break a program into smaller, more manageable pieces
Coupling and Cohesion
High Cohesion and Low Coupling: The ideal situation
- Modules are focused on a single task
- Modules are not dependent on each other
- Modules are easier to understand and maintain
Low Cohesion and High Coupling: The worst situation
- Modules are focused on multiple tasks
- Modules are dependent on each other
- Modules are harder to understand and maintain
Composition and Aggregation
Composition and Aggregation are two ways to model relationships between classes
Composition
Composition is a "has-a" relationship between classes
Composition: When a class contains an object of another class
- The object is part of the class
- The object cannot exist without the class
- The object is created and destroyed with the class
Examples:
- A
Car
class contains anEngine
object - A
House
class contains aRoom
object
Composition involves creating complex objects by combining simpler objects or components. In composition, objects are assembled together to form larger structures, with each component object maintaining its own state and behaviour
Prefer composition over inheritance, as it is more flexible and allows for greater code reuse
When to use Composition:
- When you need more flexibility in constructing objects by assembling smaller, reusable components
- When there is no clear "is-a" relationship between classes, and a "has-a" relationship is more appropriate like a
Car
has anEngine
- When you want to avoid the limitations of inheritance, such as tight coupling and the fragile base class problem
When to use Inheritance:
- When there is a clear "is-a" relationship between classes, and subclass objects can be treated as instances of their superclass like a
SUV
is aCar
- When you want to promote code reuse by inheriting properties and behaviours from existing classes
- When you want to leverage polymorphism to allow objects of different subclasses to be treated as uniformly through their common superclass interface