Object Oriented Modelling and Design

BCS1430

Dr. Ashish Sai

📅 Week 2 Lecture 1 & 2

💻 BCS1430.ashish.nl

📍 EPD150 MSM Conference Hall

Object-Oriented Analysis and Design

What is Object-Oriented Analysis and Design?

Analyze the system

Model the system

Design the software

What is Object-Oriented Analysis and Design?

  • Analysis is the investigation of the problem - what are we trying to do?
    • where use cases are created and requirements analysis are done

What is Object-Oriented Analysis and Design?

  • Object-Oriented Analysis (OOA):
    • This is the process of looking at a problem, system, or situation, and identifying the objects and interactions between those objects.

⚙️

What is Object-Oriented Analysis and Design?

  • Design is a conceptual solution that meets the requirements – how can we solve the problem

  • Object-Oriented Design (OOD):

    • We take the analysis results and mold them into a design that can be implemented in a specific programming language such as Java.
flowchart LR
    A[Analysis] --> B[Design] --> C[Implementation \n Java]

Why does it matter?

  • In the real world, complex systems are often best understood through the interactions of simpler parts. OOA&D leverages this principle to create modular, reusable and flexible software.

Benefits of OOA&D

Modularity: Degree to which a system or computer program is composed of discrete components such that a change to one component has minimal impact on other components. ( ISO/IEC 2011)

LowCouplingSystemDesign A Component A [Functions] B Component B [Functions] A->B C Component C [Functions] B->C D Component D [Functions] C->D

Benefits of OOA&D

  • Reusability: Objects and classes created for one project can be used in another, reducing development time and increasing productivity.

  • Degree to which an asset can be used in more than one system, or in building other assets. (ISO 25010)

ReusabilitySoftwareDevelopment AuthLib Authentication Library [Finance App] CRMSystem CRM System AuthLib->CRMSystem ECommerce E-Commerce Platform AuthLib->ECommerce MobileApp Mobile Banking App AuthLib->MobileApp DataParser Data Parsing Module [Data Analysis Tool] DataParser->CRMSystem DataParser->ECommerce DataParser->MobileApp

Benefits of OOA&D

  • Flexibility: Systems designed with OOA&D can be easily adapted to meet changing requirements.

OO Analaysis in Software Development

  • OOA is the investigative phase where you dive deep into the problem domain.

  • Ask the right questions and identify the objects and interactions.

  • Goal: Create a model of the real world that can be translated into a software system.

It’s easier to change a design than to change a built system.

OO Design in Software Development

  • Turn the conceptual model (from OOA) into a blueprint for building the system.

  • Decompose about the system architecture, the choice of system components etc.

Well-Designed Software

Understanding Software Quality

ISO 25010 defines software quality with a comprehensive set of attributes:

  • Functionality
  • Reliability
  • Usability
  • Efficiency
  • Maintainability
  • Portability
  • Compatibility
  • Security

Quality Attribute: Functionality

  • Definition: The degree to which a product or system provides functions that meet stated and implied needs when used under specified conditions

  • Key Aspects:

    • Functional completeness
    • Functional correctness
    • Functional appropriateness

Quality Attribute: Reliability

  • Definition: Reliability is about the software’s capacity to maintain its performance level under stated conditions for a specified period.

  • Key Aspects:

    • Maturity
    • Fault Tolerance
    • Recoverability

Quality Attribute: Usability

  • Definition: Usability refers to how well a product or system can be used to achieve specified goals effectively, efficiently, and satisfactorily.

  • Key Aspects:

    • Understandability
    • Learnability
    • Operability

Quality Attribute: Efficiency

  • Definition: Efficiency relates to the relationship between the performance level of the software and the amount of resources used, under stated conditions.

  • Key Aspects:

    • Time Behavior
    • Resource Utilization
    • Capacity

Quality Attribute: Maintainability

  • Definition: Maintainability is the ease with which the software can be modified to correct faults, improve performance, adapt to a changed environment, or enhance the product.

  • Key Aspects:

    • Modularity
    • Reusability
    • Analyzability

How to design good (OO) software?

Developing good (OO) Software

  1. Understand the requirements
  2. Analyze the requirements in an OO context (OO Analaysis)
  3. Design the software using OO design principles (more on this in the later half of the lecture)

Gathering Requirements

Introduction to Software Requirements

Requirements are descriptions of the system’s features, and constraints.

Key Points:

  • Essential for guiding development and testing.

  • Help in setting customer expectations.

  • Basis for project planning and design decisions.

Types of Software Requirements

Software Requirements:

  • Functional Requirements

  • Non-functional Requirements

  • Domain Requirements

Functional Requirements (FR)

Functional Requirements describe:

  • The services the system must provide
  • How the system should react to particular inputs
  • How it should behave in certain situations.

Functional Requirements Characteristics:

  • Clearly state what the system should do.

  • Include calculations, technical details, data manipulation and processing, and other specific functionality.

Functional Requirements (FR)

Example:

“The system shall allow users to enter their credentials to log in.”

“Upon receiving a user’s input, the system shall calculate and display the results within 2 seconds.”

Non-Functional Requirements

Non-functional requirements set criteria to judge the operation of a system, rather than specific behaviors, including constraints on the system and standards.

Types:

  • Performance: Speed, responsiveness, consumption.

  • Reliability: Frequency of failure, recoverability.

  • Usability: Understandability, learnability, operability.

  • Security: Access control, integrity, confidentiality.

Non-Functional Requirements

Example:

“The system should load the homepage in under 3 seconds when accessed from a standard broadband connection.”

“User data should be encrypted using industry-standard encryption algorithms.”

Domain Requirements

Domain requirements reflect domain-specific knowledge, standards, or regulations that the software must comply with.

Key Aspects:

  • Reflect application domain behavior.

  • Influence system functionality and performance.

  • Include legal and regulatory requirements.

Domain Requirements

Example:

“The software must store medical data in compliance with healthcare regulations.”

“The system must perform currency conversion according to international financial standards.”

The Art of Gathering Requirements

  • Requirements gathering is the process of collecting the needs and specifications from stakeholders to design a system that meets their expectations.

  • Understand stakeholder needs, and translate those into technical specifications.

Stakeholders

Stakeholders are:

  • Individuals or groups with an interest in the success of the project.

Example:

  • Clients, end-users, project managers, developers or your examiner.

Techniques for Gathering Requirements

  • Techniques:
    • Interviews,
    • Surveys/Questionnaires,
    • Observations,
    • Workshops, and
    • Brainstorming sessions.
  • We will focus on Use Cases for this course.

Introduction to Use Cases

  • A use case is a detailed description of how users perform tasks, outlining a system’s behavior from a user’s perspective.

    • Starts with a user’s goal and ends when that goal is fulfilled, providing a sequence of steps and interactions.

Importance of Use Cases

Use cases are essential for understanding complex user interactions and system responses. They help in:

  • Visualizing system functionality.

  • Identifying potential issues.

  • Providing a framework for tests.

  • Communicating requirements across teams.

Elements of a Use Case

  • Actor: The user performing the behavior.
  • Stakeholder: Individuals with interests in the system.
  • Preconditions: States that must be true before the use case runs.
  • Triggers: Events that initiate the use case.
  • Main Success Scenarios (Basic Flow): The expected successful path.
  • Alternative Paths (Alternative Flows): Variations when deviations occur.

Writing a Use Case

  1. Identify users: Determine who will be using the system.
  2. Define actions: Each action becomes a use case.
  3. Describe normal and alternate courses: Outline the basic and alternative flows.
  4. Note commonalities: Identify similar patterns across use cases.
  5. Repeat for all users: Ensure comprehensive coverage.

Simple Laundry Use Case

  • Actor: Housekeeper
  • Basic Flow: Sorting, washing, drying, folding, ironing, and discarding items.
  • Preconditions: It’s Wednesday, and there is laundry.
  • Trigger: Dirty laundry is present.
  • Post Conditions: All laundry is clean and either folded or hung up.

Alternative Flows in Laundry Use Case

  • Alternative Flow 1: If items are wrinkled, they are ironed and hung.
  • Alternative Flow 2: If items are still dirty, they are rewashed.
  • Alternative Flow 3: If items have shrunk, they are discarded.

Example Use Case: Enter Order

Description: This use case describes how a customer can enter a new order into the system.

Actors:

  • Customer

  • System

  • Market

Preconditions: Customer is logged on to the website.

Postconditions:

  • The trade has been processed.

  • The Customer has received the confirmation from the system.

Basic Flow of Enter Order

Step ID Actor Action Notes and References
BF-1 Customer Customer navigates to the order entry page.
BF-2 System System displays the order entry page.
BF-3 Customer Customer enters the following order details: Buy/Sell, Quantity, Stock Symbol
BF-4 Customer Customer submits the order.
BF-5 System System validates the information entered by the customer. AF-1: Customer enters invalid information
BF-6 System System submits the order to the marketplace.
BF-7 Market Market executes the order.
BF-8 Market Market sends the execution report to the system.
BF-9 System System displays the execution report to the customer.

Alternate Flows - Invalid Information

AF-1: Customer enters invalid information

Step ID Actor Action Notes and References
AF-1-1 System If the customer has entered invalid information the system will display an error message: “You have entered invalid information.”
AF-1-2 Customer Customer corrects the information and resubmits.
AF-1-3 [Go to BF-5]

Exception Flows - System Issues

EF-1: System is down or unavailable

Step ID Actor Action Notes and References
EF-1-1 System If the system is down/unavailable the system will display a message to the customer: “The system is currently unavailable.”
EF-1-2 [Use case ends]

EF-2: System cannot connect to the market

Step ID Actor Action Notes and References
EF-1-1 System If the system cannot connect to the market the system will display a message to the customer: “The system cannot connect to the market. Please try again later.”
EF-1-2 [Use case ends]

Supplemental Requirements

ID Name Description
SR-1 Tabbing The system will enable the user to tab from field to field on the order entry page.

Use Cases: Key Takeaways

  • User-Centric Design: Focus on user actions and system responses.

  • Detailing Flows: Clear definition of basic, alternate, and exception flows.

  • Supplemental Requirements: Address additional user experience enhancements.

  • Adaptability: The use case’s structure allows for easy updates and modifications.

Requirements Prioritization

  • Not all requirements are of equal importance.

  • Evaluate the urgency, value, and feasibility of each requirement. (Prioritization)

  • Technique(s): MoSCoW (Must have, Should have, Could have, Won’t have this time)

Requirements Prioritization: MoSCoW Technique

MoSCoW technique is a prioritization tool used to reach a common understanding on the importance of various requirements.

Components:

  • Must have (M): Essential features, required for successful and functional project.

  • Should have (S): Important but not vital features.

  • Could have (C): Desirable features that are beneficial but not crucial.

  • Won’t have this time (W): Features that, may be useful, are not a priority for this iteration.

Case Study: Starbucks Mobile Order & Pay

Starbucks introduced Mobile Order & Pay to allows customers to place orders and pay in advance to reduce waiting times.

Objectives:

  • Improve customer satisfaction by reducing in-store waiting times.

  • Increase operational efficiency and order accuracy.

  • Enhance the customer’s personalization and convenience.

Use Case: Placing an Order through Mobile Order & Pay

Actor: Customer

Preconditions:

  • The customer has the Starbucks mobile app installed.

  • The customer has an account with payment information.

Basic Flow:

  1. The customer opens the app and selects the ‘Order’ option.

  2. The customer browses the menu and selects items to order.

  3. The customer reviews the order and makes any modifications.

  4. The customer confirms the order and selects a pickup location.

  5. The customer pays for the order through the app.

  6. The system sends the order to the selected store.

  7. The customer receives a notification when the order is ready for pickup.

Postconditions:

  • The customer picks up the order and is satisfied with the service.

Derived Requirements from Use Case

Functional Requirements:

  • FR1: The app must allow users to browse the menu and select items.

  • FR2: The app must provide an option for order modification before confirmation.

  • FR3: The app must securely process payment information.

  • FR4: The system must send the order to the selected store and confirm readiness through a notification.

Non-Functional Requirements:

  • NFR1: The app should load the menu within 2 seconds (Performance).

  • NFR2: The payment system must comply with PCI DSS standards (Security).

  • NFR3: The app should be accessible and user-friendly (Usability).

  • NFR4: The system should handle 1000 orders simultaneously without performance degradation (Scalability).

MoSCoW Prioritization - Functional Requirements

Must have (M):

  • FR1: Browse menu and select items - critical for basic functionality.

  • FR3: Securely process payment - essential for trust and legal compliance.

Should have (S):

  • FR2: Order modification - important for user satisfaction but not critical.

Could have (C):

  • Additional personalized recommendations based on user preferences.

Won’t have this time (W): - Advanced custom ordering options that require new technology.

MoSCoW Prioritization - Non-Functional Requirements

Must have (M):

  • NFR2: Compliance with PCI DSS for payment security - non-negotiable.

  • NFR3: Basic app usability - essential for user adoption.

Should have (S):

  • NFR1: Fast menu loading - important for a good user experience.

Could have (C):

  • NFR4: Scalability to handle more than 1000 orders - desirable for future growth.

Won’t have this time (W):

  • Integration with third-party loyalty systems - considered for future phases.

Requirements Change

The Nature of Changing Requirements

  • Changes in requirements are a natural and expected part of the software development lifecycle.

  • Reasons:

    • New market trends, technological innovations, regulatory changes, and user feedback.

The Ripple Effect of Change

  • When requirements change, the effects ripple through the project.

  • These changes can impact project scope, timelines, costs, and even the overall quality of the product.

Analysis

Object-Oriented Analysis

What is OOA?
Methodical approach to understanding and modeling a system by viewing it through the lens of the ‘objects’ (real-world entities) it involves.

Object-Oriented Analysis

Key Aspects:
- Understanding the Domain: Examining the problem or system from an object-centric perspective.
- Capturing Requirements: Identifying what the system must do from the viewpoint of the objects.
- Defining the System: Creating a model that effectively addresses and fulfills the identified needs.

Identifying Objects and Classes

Core Concepts:
- Objects: Instances from the problem domain represented in the system.
- Classes: Blueprints defining attributes and behaviors of objects.

Identifying Objects and Classes

Process:

  1. Examine the Domain: Analyze the real-world scenario to model.

  2. Spot Key Entities: Identify significant entities that play vital roles.

  3. Define Nature and Functionality: Determine attributes and operations for each entity.

Identifying Objects and Classes

Example: - Library System: Key objects like books, members, and loans with specific attributes and behaviors.

Identify Relationships and Interactions

Objects do not exist in isolation!

Types of Relationships:

  • Associations: Simple connections between objects.

  • Aggregations: Whole-part relationships indicating a collective.

  • Compositions: Strong whole-part relationships with dependency.

Identify Relationships and Interactions

Example: - E-commerce Platform: ‘Customer’ places an ‘Order’ containing ‘Products’.

Associations

Definition:
Represents a “use-a” or “has-a” relationship where one object uses or interacts with another.

  • Can be one-to-one, one-to-many, many-to-one, or many-to-many.

Associations

class Customer {
    private Order order;
    void placeOrder() { /*...*/ }
}

class Order {
    // Order related methods
}

Here, a Customer has an association with Order as it places an order. This is a one-to-one relationship.

Aggregations

Definition:
A specialized form of association representing a “whole-part” relationship, with objects having their own life cycle but forming a whole.

  • Denotes a ‘has-a’ relationship.

  • The part can exist independently of the whole.

Aggregations

class Team {
    private List<Player> players;
}

class Player {
    // Player specific methods
}

A Team consists of multiple Players. Players can exist without a team, illustrating the whole-part relationship.

Compositions

Definition:
Composition is a strong form of aggregation implying ownership. When the whole is destroyed, so are the parts.

  • Represents a strong ‘contains-a’ relationship.

  • The life cycle of the part is tied to the whole.

Compositions

class Engine {
    // Engine specific methods
}

class Car {
    private Engine engine = new Engine();
    void start() { /* Starts the engine */ }
}

A Car has a Engine as part of its composition. If the car ceases to exist, so does the engine, indicating a strong dependency.

Object Interaction Analysis

  • Object Interaction Analysis is a technique used to understand how objects in the system will communicate and collaborate.

  • Interaction diagrams are used to visualize these interactions 1.

Object Interaction Analysis

  • For example, in an airline reservation system, understanding how a ‘Passenger’ object interacts with ‘Flights’, ‘Tickets’, and ‘Payments’ objects is crucial for designing a functional system.

Assigning Responsibilities with CRC Cards

Class-Responsibility-Collaborator (CRC) Cards:
CRC cards is a simple tool used to define the behaviors and interactions of classes.

Components of a CRC Card:

  • Class Name: The entity or concept being modeled.

  • Responsibilities: What the class should know or do (its behavior).

  • Collaborators: Other classes this class interacts with or uses.

CRC Card Example - ShoppingCart

Class Name: ShoppingCart

Responsibilities:

  • Add Item: Include a new product in the cart.

  • Calculate Total: Compute the total cost of items in the cart.

  • Remove Item: Take out a product from the cart.

  • Checkout: Initiate the purchasing process.

Collaborators:

  • Product: Items that can be added to the shopping cart.

  • User: The customer who owns the shopping cart.

CRC Card Example - ShoppingCart

class ShoppingCart {
    private List<Product> products;
    private User owner;

    void addItem(Product product) { /*...*/ }
    double calculateTotal() { /*...*/ }
    void removeItem(Product product) { /*...*/ }
    void checkout() { /*...*/ }
}

class Product { /* Product details */ }
class User { /* User details */ }

The ShoppingCart class has clear responsibilities like managing items and calculating totals, and it collaborates with Product and User classes to fulfill these responsibilities.

Organizing Systems with Package Diagrams

  • Package diagrams are used to group related classes, interfaces, or other packages into higher-level modules.

Class Diagrams: The Static Blueprint

  • Class diagrams show the classes, along with their attributes and operations, and the relationships between them.

Design

Good Design in Software

  • Beyond aesthetics, good software design is about crafting solutions that are effective, efficient, and maintainable.

  • It’s the art of making complex systems understandable and usable.

Key Attributes:

  • Simplicity, Consistency, Modularity, Usability, Maintainability, Robustness

Simplicity

  • Simplicity means focusing on essential elements, making software more understandable and less error-prone.

In Practice:

  • A Java method that performs a single, well-defined function is a good example. It’s easy to understand, test, and maintain.

Simplicity

// Simple method to add two numbers
int add(int a, int b) {
    return a + b;
}
  • Reduces complexity, enhances maintainability, and improves user experience.

Consistency

  • Consistency involves uniformity in visual elements, terminology, and behavior.

In Practice:

  • A Java GUI application using a consistent color scheme and button styles throughout.

Consistency

// Consistent use of button style
Button saveButton = new Button("Save");
saveButton.setStyle("-fx-background-color: #FF0000");
// Other buttons would use the same style

Modularity

  • Modularity means designing systems as separate, interchangeable components.

In Practice:

  • Java packages organizing classes by functionality, allowing independent development and testing.

Modularity

// Package for order processing
package com.company.orderprocessing;
public class OrderProcessor { /*...*/ }

// Separate package for customer management
package com.company.customermanagement;
public class CustomerManager { /*...*/ }

Impact: - Enhances flexibility, manageability, and scalability.

Usability

  • Usability focuses on making software intuitive and accessible based on users’ needs and limitations.

In Practice:

  • A Java web application with a straightforward, well-organized layout, clear instructions, and responsive feedback.

Usability

// Simple, clear form submission feedback
if(submissionSuccess) {
    displayMessage("Form submitted successfully!");
} else {
    displayMessage("Submission failed. Please try again.");
}

Impact:

  • Makes software easy and pleasant to use, enhancing user satisfaction.

Maintainability

Concept:

  • Maintainable design allows for easy understanding, correction, adaptation, and enhancement.

In Practice:

  • Writing clean, well-documented Java code and adhering to architectural patterns.

Maintainability

/**
 * Calculates the monthly payment for a given loan.
 * @param loanAmount Total amount of the loan.
 * @param termInYears Number of years for the loan.
 * @param interestRate Annual interest rate.
 * @return The monthly payment amount.
 */
public double calculateMonthlyPayment(double loanAmount, int termInYears, double interestRate) { /*...*/ }

Impact:

  • Ensures software can evolve and adapt efficiently over time.

Robustness

Concept:

  • Robustness means the system’s ability to handle errors and unexpected situations gracefully.

In Practice:

  • Java applications implementing error handling and validation to manage unexpected inputs or disruptions.

Robustness

try {
    // Attempt a risky operation
} catch (SpecificException e) {
    // Handle exception and provide recovery options
}

Impact: - Keeps systems stable and functional under various conditions, enhancing reliability.

Case Study: Airbnb Design Overhaul

Challenges:

  • Users struggled with a complex booking process and inconsistent interface.

  • Hosts were concerned about guest reliability.

Case Study: Airbnb Design Overhaul

Design Strategy:

  • Simplified Booking: Streamlined steps for easier navigation and user understanding.

  • Consistent Visual Language: Unified colors, typography, and buttons for intuitive use.

  • Modular Design System: Flexible and scalable, allowing easy updates and feature additions.

Read more: https://airbnb.design

Airbnb’s Transformation & Results

Usability Improvements:

  • Transparent interfaces with better listings and improved review systems.

  • Enhanced error handling offering clear feedback and recovery options.

Impact:

  • Marked increase in bookings and user engagement post-redesign.

  • Strengthened brand recognition as a reliable and user-friendly lodging option.

Airbnb’s Transformation & Results

Read more: How Design Thinking Transformed Airbnb from a Failing Startup to a Billion Dollar Business

OO Design Principles

Fundamental Design Principles

  • DRY (Don’t Repeat Yourself): Avoid duplication.

  • KISS (Keep It Simple, Stupid): Keep the design simple and straightforward.

  • YAGNI (You Aren’t Gonna Need It): Don’t implement something until it is necessary.

DRY Principle

Definition: Avoid duplication in code. Every piece of knowledge should have a single, unambiguous representation in the system.

Impact:

  • Reduces redundancy, making code easier to maintain and modify.

DRY (Don’t Repeat Yourself) Principle

// Before applying DRY
class ReportGenerator {
    void generateReport(Data data) {
        // Data validation code
        // Report generation code
    }
    void generateSummaryReport(Data data) {
        // Data validation code (Repeated)
        // Summary report generation code
    }
}
// After applying DRY
class ReportGenerator {
    void validateData(Data data) {
        // Data validation code
    }
    void generateReport(Data data) {
        validateData(data);
        // Report generation code
    }
    void generateSummaryReport(Data data) {
        validateData(data);
        // Summary report generation code
    }
}

KISS Principle

Definition: Simplicity in design. Avoid unnecessary complexity and keep things straightforward.

Impact:

  • Enhances understandability and reduces the chance of errors. Simplified code is often more reliable and easier to debug.

KISS Principle

// Before applying KISS
class Calculator {
    double complexCalculate(double a, double b) {
        // Unnecessarily complex calculations
    }
}
// After applying KISS
class Calculator {
    double add(double a, double b) {
        return a + b; // Simple and straightforward
    }
}

YAGNI Principle

Definition: Implement only those features that are necessary. Avoid adding functionality until it is required.

Impact:

  • Prevents over-engineering and keeps the codebase lean and focused. Ensures resources are used effectively on what’s truly needed.

YAGNI Principle

// Before applying YAGNI
class FeatureSet {
    void essentialFeature() { /*...*/ }
    void futureFeature() { /* Might be used later */ }
}
// After applying YAGNI
class FeatureSet {
    void essentialFeature() { /*...*/ }
    // Remove futureFeature() until it's needed
}

More Design Principles

  • Separation of Concerns: Different functionality managed by separate code.

  • Principle of Least Astonishment: Software behaves how users will expect it to.

  • Law of Demeter: Object should assume as little as possible about the structure or properties of anything else.

Separation of Concerns

Definition:

  • Dividew a program into distinct sections, each handling a specific aspect of the application’s functionality.

Impact:

  • Helps in isolating issues, streamlining updates, and collaborate more effectively.

Separation of Concerns

// Before: Mixing data access with business logic
class UserHandler {
    void createUser(String username) {
        // Database connection code
        // User creation logic
    }
}

// After: Separated data access and business logic
class UserDataAccess {
    void saveUser(User user) { /* Database code to save user */ }
}
class UserHandler {
    UserDataAccess dataAccess = new UserDataAccess();
    void createUser(String username) {
        User user = new User(username);
        dataAccess.saveUser(user); // Separated concern
    }
}

Principle of Least Astonishment

Definition:

  • Software should behave in a way that users predictably expect. The design should match common user expectations to prevent confusion and errors.

Impact:

  • Enhances user satisfaction and system intuitiveness.

Principle of Least Astonishment

// Before: Confusing method name
class FileProcessor {
    void erase(File file) { /* Actually saves the file */ }
}
// After: Method name reflects its action
class FileProcessor {
    void save(File file) { /* Clearly saves the file */ }
}

Law of Demeter

Definition:

  • Minimal knowledge between objects. An object should only interact with its direct components and not concern itself with the internal details of other objects.

Impact:

  • Reduces the dependencies between components of a system, leading to a looser coupling and more modular architecture.

Law of Demeter

// Before: Violating Law of Demeter
class Customer {
    Wallet wallet;
    //...
}
class Shop {
    void chargeCustomer(Customer customer) {
        double amount = customer.wallet.getMoney(); // Directly accessing customer's wallet
        // Charge logic
    }
}
// After: Adhering to Law of Demeter
class Customer {
    Wallet wallet;
    double payAmount(double amount) { return wallet.deduct(amount); }
}
class Shop {
    void chargeCustomer(Customer customer) {
        customer.payAmount(50); // Interacting only with the Customer interface
    }
}

GRASP Principles

GRASP Principles - Core Concepts

What is GRASP?
General Responsibility Assignment Software Patterns (GRASP) are guidelines for assigning responsibilities in object-oriented design to improve robustness and maintainability.

GRASP Principles

Principles Overview:

  • Information Expert: Responsibilities go to the class with the most related information.

  • Creator: The class that needs an object or has initializing data should create it.

  • Controller: Designate a class to handle system events and user input.

  • Low Coupling: Minimize class interdependencies for flexibility.

  • High Cohesion: Keep related functions together for focused class design.

  • Polymorphism: Handle alternatives based on object types dynamically.

  • Pure Fabrication: Create classes for better design, even if they don’t represent real-world entities.

Information Expert

Principle: Assign responsibility to the class that has the necessary information to fulfill it.

  • Classes should be assigned responsibilities based on the data they hold or the information they can access.

Information Expert

Benefits:

  • Reduces redundancy and complexity.

  • Enhances maintainability and cohesion.

  • Makes the system more modular and easier to navigate.

Application:

  • Typically applied during the design phase to ensure that each class has a clear and focused role, handling operations that are directly related to its information.

Information Expert - Before

class Order {
    private List<Item> items;
    // Other Order related methods...
}

class OrderCalculator {
    // This class is wrongly taking the responsibility of calculating the total
    double calculateTotalCost(Order order) {
        double total = 0;
        for(Item item : order.getItems()) {
            total += item.getPrice();
        }
        return total;
    }
}
  • OrderCalculator is taking the responsibility that naturally belongs to the Order class, leading to a less intuitive and more fragmented design.

Information Expert - After

  • Assign the responsibility of calculating the total cost to the class with the most knowledge required to perform it - the Order class.
class Order {
    private List<Item> items;

    // Information Expert for calculating total cost
    double calculateTotalCost() {
        double total = 0;
        for(Item item : items) {
            total += item.getPrice();
        }
        return total;
    }
}

class OrderCalculator {
    // No longer responsible for calculating total cost
}

Creator - Overview

Principle: Assign class B the responsibility to create an instance of class A if B closely uses A or holds the data that will initialize A.

  • Ensure that objects are created by the classes that use them most or have the necessary data to initialize them.

Creator - Overview

Benefits:

  • Enhances encapsulation and clarity.

  • Reduces dependencies and coupling between classes.

  • Simplifies the system’s structure.

Application:

  • Useful in deciding where to put creation logic, especially in complex systems where the right placement of object creation can significantly affect the design’s clarity and maintainability.

Creator - Before

// Order is responsible for creating Item instances, but it doesn't directly contain or closely use them
class Order {
    void addNewItem() {
        Item newItem = new Item(); // Order creates Item instances
        // ...
    }
}

class ShoppingCart {
    private List<Item> items;
    // ShoppingCart logic...
}

Problem:

  • The Order class is creating Item instances, but it doesn’t have a logical or direct relationship with Item, leading to poor encapsulation and design.

Creator - After Applying

  • Assign the responsibility to create Item instances to the ShoppingCart class, which logically contains and manages items.
class Order {
    // No longer responsible for creating Item instances
}

class ShoppingCart {
    private List<Item> items;

    // Creator for Item instances
    void addItem() {
        Item newItem = new Item(); // ShoppingCart creates Item instances
        items.add(newItem);
    }
}

Controller - Overview

Principle: Assign the responsibility of handling a system event to a class representing the overall system, a root object, or a subsystem.

  • Designate a ‘controller’ class to handle system events or user input, serving as an intermediary between the UI and the system’s business logic.

Controller - Overview

Benefits:

  • Centralizes control logic.

  • Decouples the UI from the underlying business logic.

  • Simplifies maintenance and enhances scalability.

Application:

  • Typically used to define how the system will respond to user actions or other events, ensuring there’s a clear and consistent way to manage these interactions.

Controller - Before Applying

class UserInterface {
    // Directly handling user input and business logic
    void onLoginButtonClick(String username, String password) {
        // Validate credentials
        // Directly access the database to verify user
        // Manage session
    }
}
  • The UserInterface class is overloaded with responsibilities, handling UI events, business logic, and data access, making the system hard to maintain and scale.

Controller - After Applying

Solution:

  • Introduce a controller class to act as an intermediary between the UI and the system logic.
class UserInterface {
    // Delegates handling of the login event to the LoginController
    void onLoginButtonClick(String username, String password) {
        LoginController controller = new LoginController();
        controller.handleLoginRequest(username, password);
    }
}

class LoginController {
    AuthenticationService authService;

    void handleLoginRequest(String username, String password) {
        // Handle the login process
        User user = authService.authenticate(username, password);
        // Manage session and other login-related tasks
    }
}

Low Coupling

Understanding Coupling

Definition:

  • Coupling is the measure of how interdependent classes or modules are. Low coupling means that a change in one class has minimal impact on other classes.

Understanding Coupling

// High Coupling: Direct dependency
class Order {
    Payment payment;
    void processOrder() { payment.processPayment(); }
}

// Low Coupling: Reduced dependency
class Order {
    PaymentProcessor processor;
    void processOrder() { processor.processPayment(); }
}

class PaymentProcessor {
    void processPayment() { /*...*/ }
}

Low Coupling

Principle: Design to minimize the dependencies between classes to reduce the impact of changes and improve reusability.

  • Low Coupling involves reducing the interconnectedness of classes so that changes in one class have minimal impacts on others.

Low Coupling

Benefits:

  • Increases the flexibility of the system.

  • Makes the codebase more resilient to changes.

  • Facilitates easier testing and maintenance.

Application:

  • Critical in designing systems where change is anticipated, ensuring that modifications in one part of the system don’t cause widespread issues.

Low Coupling - Before Applying

class User {
    // Directly dependent on specific storage implementation
    FileStorage storage;

    void saveUserData() {
        // Directly uses FileStorage methods
        storage.writeData(this);
    }
}
  • User class is highly coupled with FileStorage, making it difficult to change storage methods or test the User class independently.

Low Coupling - After Applying

Solution:

  • Introduce an interface to abstract the storage mechanism, reducing the direct dependency between User and FileStorage.
interface Storage {
    void writeData(User user);
}

class FileStorage implements Storage {
    void writeData(User user) { /*...*/ }
}

class User {
    Storage storage;

    void saveUserData() {
        // Uses Storage interface, not directly tied to FileStorage
        storage.writeData(this);
    }
}

High Cohesion

Understanding Cohesion

Definition: - Cohesion refers to the degree to which elements of a module or class belong together.

  • A class with high cohesion performs a small range of tasks related to a particular purpose or concept.

Understanding Cohesion

// Low Cohesion: Handles unrelated tasks
class Utility {
    void handleDataProcessing() { /*...*/ }
    void manageUserInterface() { /*...*/ }
}

// High Cohesion: Focused on a single task
class DataProcessor {
    void handleDataProcessing() { /*...*/ }
}

High Cohesion - Overview

Principle: Keep related and similar functionalities together in a class, ensuring that each class has a clear, narrowly focused role.

  • Classes should not take on responsibilities that could be better handled by others.

High Cohesion - Overview

Benefits:

  • Simplifies understanding of the system.

  • Enhances the ability to manage and modify code.

  • Promotes single responsibility and focused class design.

Application: - Essential in ensuring that the system remains organized and each part is as independent and focused as possible, promoting better design and easier future changes.

High Cohesion - Before Applying

class UserManager {
    void createUser() { /*...*/ }
    void deleteUser() { /*...*/ }
    void generateReport() { /* Unrelated to user management */ }
}
  • UserManager is handling both user management and report generation, making it less cohesive and more complex.

High Cohesion - After Applying

class UserManager {
    void createUser() { /*...*/ }
    void deleteUser() { /*...*/ }
    // Removed report generation


}

class ReportGenerator {
    void generateReport() { /* Focused on report generation */ }
}
  • Higher cohesion in UserManager and ReportGenerator makes each class more focused, understandable, and maintainable.

Polymorphism - Overview

Principle: Use polymorphism to handle alternatives based on object type, where the behavior varies depending on the class of the object.

  • Polymorphism in object-oriented programming allows objects of different classes to be treated as objects of a common superclass. It’s a way to use a single interface to represent different underlying forms (data types).

Polymorphism

Benefits:

  • Enhances flexibility and reusability by allowing different classes to be used interchangeably.

  • Simplifies code by eliminating the need for multiple conditional statements.

Application:

  • Commonly used when implementing system behaviors that can vary across different classes but are accessed through a common interface.

Polymorphism - Before Applying

class AnimalSound {
    void makeSound(Animal animal) {
        if (animal instanceof Dog) {
            System.out.println("Woof");
        } else if (animal instanceof Cat) {
            System.out.println("Meow");
        }
        // More conditions for other animal types
    }
}

class Dog extends Animal { /*...*/ }
class Cat extends Animal { /*...*/ }
  • AnimalSound class becomes cumbersome and difficult to maintain as more animal types are added.

Polymorphism - After Applying

abstract class Animal {
    abstract void makeSound();
}

class Dog extends Animal {
    void makeSound() { System.out.println("Woof"); }
}

class Cat extends Animal {
    void makeSound() { System.out.println("Meow"); }
}

class AnimalSound {
    void makeSound(Animal animal) {
        animal.makeSound(); // Polymorphism in action
    }
}
  • Each animal class knows how to make its sound, eliminating the need for conditional logic in AnimalSound and making the code more scalable and maintainable.

Pure Fabrication - Overview

Principle: Create a class that doesn’t represent a concept in the problem domain, particularly to achieve low coupling, high cohesion, or to encapsulate change.

  • Pure Fabrication is a made-up class that doesn’t represent anything in the real world!

Pure Fabrication

Benefits:

  • Enhances maintainability and reusability.

  • Allows for better separation of concerns and encapsulation.

Application:

  • Useful when a behavior doesn’t fit well into existing real-world domain classes or when a particular design problem is best solved independently of the domain model.

Pure Fabrication - Before Applying

class Order {
    // Order related data and methods...
    
    void saveOrder() {
        // Direct database access code to save the order
        // This mixes business logic with data access logic
    }
}
  • The Order class is directly handling database operations, which is not its primary responsibility, leading to a design that’s hard to maintain and scale.

Pure Fabrication - After Applying

class Order {
    // Order related data and methods...
}

class OrderRepository {
    void saveOrder(Order order) {
        // Specific database access code to save the order
    }
}
  • OrderRepository is a pure fabrication that takes over database responsibilities, leading to a cleaner separation of concerns and a more maintainable design.

SOLID Principles

SOLID Principles - Core Concepts

What is SOLID?

SOLID represents five fundamental principles in object-oriented programming and design that promote software maintainability and extensibility.

SOLID Principles - Core Concepts

Key Objectives:

  • Single Responsibility: Encourage classes to have one reason to change.

  • Open/Closed: Design modules that are open for extension but closed for modification.

  • Liskov Substitution: Ensure subclasses can replace their superclasses without altering the program’s correctness.

  • Interface Segregation: Favor client-specific interfaces over general-purpose ones.

  • Dependency Inversion: Depend on abstractions rather than concrete implementations.

Single Responsibility Principle (SRP) - Overview

Definition:

A class should have one, and only one, reason to change, promoting modularity and separation of concerns.

Importance:

  • Simplifies understanding and modification of classes.

  • Enhances cohesion and reduces the impact of changes.

SRP - Before Applying

class UserManager {
    void createUser() { /*...*/ }
    void sendEmail(String message) { /*...*/ } // Unrelated to user management
}
  • UserManager handling both user creation and email sending mixes different concerns, making it less cohesive and more prone to errors.

SRP - After Applying

class UserManager {
    void createUser() { /*...*/ }
}

class EmailService {
    void sendEmail(String message) { /*...*/ }
}
  • Each class now has a single responsibility, making the system more maintainable and understandable.

Open/Closed Principle (OCP) - Overview

Definition:

Software entities should be open for extension but closed for modification, promoting flexible and scalable systems.

  • Use abstraction and polymorphism to extend behavior without altering existing code.

How?:

  • Designing a class hierarchy where new functionality can be added through subclasses or implementing interfaces without changing existing code.

OCP - Before Applying

class GraphicEditor {
    void drawShape(Shape shape) {
        if (shape.type == 1) {
            drawRectangle(shape);
        } else if (shape.type == 2) {
            drawCircle(shape);
        }
        // Adding a new shape requires modifying this method
    }
}
  • Adding a new shape requires changes to GraphicEditor, violating the open/closed principle.

OCP - After Applying

abstract class Shape {
    abstract void draw();
}

class Rectangle extends Shape {
    void draw() { /* Draw rectangle */ }
}

class Circle extends Shape {
    void draw() { /* Draw circle */ }
}

class GraphicEditor {
    void drawShape(Shape shape) {
        shape.draw(); // No modification needed for new shapes
    }
}
  • New shapes can be added without modifying GraphicEditor, adhering to the open/closed principle and making the system more extensible.

Liskov Substitution Principle (LSP) - Overview

Definition:

Subclasses should be substitutable for their base classes without affecting the program’s correctness.

  • Ensures that a subclass can stand in for its parent class without causing unexpected behavior.

LSP - Before Applying

Java Example:

class Bird {
    void fly() { /*...*/ }
}

class Ostrich extends Bird {
    // Ostriches can't fly, but they're subclassed from Bird
    void fly() { throw new UnsupportedOperationException(); }
}
  • Using an Ostrich object where a Bird is expected can cause unexpected errors due to the overridden fly method.

LSP - After Applying

abstract class Bird {
    // Some common bird behavior
}

class FlyingBird extends Bird {
    void fly() { /* Implement flying */ }
}

class Ostrich extends Bird {
    // Ostrich-specific behavior, no fly method
}
  • FlyingBird and Ostrich are now both substitutable for Bird without causing unexpected behavior, adhering to the Liskov Substitution Principle.

Interface Segregation Principle (ISP) - Overview

Definition:

Clients should not be forced to depend on interfaces they do not use, encouraging fine-grained interfaces over general-purpose ones.

  • Designing small, focused interfaces that classes can implement without being forced to include unnecessary methods.

ISP - Before Applying

interface Worker {
    void work();
    void eat();
    void takeBreak();
}

class Robot implements Worker {
    void work() { /*...*/ }
    void eat() { /* Robots don't eat */ }
    void takeBreak() { /* Robots don't take breaks */ }
}
  • Robot is forced to implement eat and takeBreak, which are irrelevant to its functionality.

ISP - After Applying

interface Worker {
    void work();
}

interface HumanWorker extends Worker {
    void eat();
    void takeBreak();
}

class Robot implements Worker {
    void work() { /*...*/ }
}

class Human implements HumanWorker {
    void work() { /*...*/ }
    void eat() { /*...*/ }
    void takeBreak() { /*...*/ }
}
  • Robot now only implements the relevant Worker interface, and Human implements the extended HumanWorker interface. This segregation makes the system more flexible and maintainable.

Dependency Inversion Principle (DIP) - Overview

Definition:

High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.

  • Using interfaces or abstract classes to define high-level policies, which are then implemented by concrete classes without the high-level modules knowing the details of the implementation.

DIP - Before Applying

class OrderProcessor {
    MySQLDatabase database; // Directly dependent on a specific database implementation

    void processOrder(Order order) {
        database.save(order); // Tightly coupled to MySQLDatabase
    }
}
  • OrderProcessor is directly dependent on MySQLDatabase, making it hard to switch to a different database or test the order processing independently.

DIP - After Applying

interface Database {
    void save(Order order);
}

class MySQLDatabase implements Database {
    void save(Order order) { /*...*/ }
}

class OrderProcessor {
    Database database; // Depends on the abstraction

    void processOrder(Order order) {
        database.save(order); // Not tied to a specific implementation
    }
}
  • OrderProcessor now depends on the Database abstraction, not the concrete MySQLDatabase implementation. This makes the system more flexible and easier to modify or test.