0% found this document useful (0 votes)
24 views46 pages

Software Design Principles & Patterns

The document outlines key software design principles and patterns essential for creating maintainable and scalable software systems. It introduces the SOLID principles, emphasizing single responsibility, open-closed, Liskov substitution, interface segregation, and dependency inversion, alongside other principles like DRY, KISS, and YAGNI. Additionally, it discusses various design patterns, including creational, structural, and behavioral patterns, highlighting their roles in improving code flexibility, reusability, and maintainability.

Uploaded by

tamangsujita36
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
24 views46 pages

Software Design Principles & Patterns

The document outlines key software design principles and patterns essential for creating maintainable and scalable software systems. It introduces the SOLID principles, emphasizing single responsibility, open-closed, Liskov substitution, interface segregation, and dependency inversion, alongside other principles like DRY, KISS, and YAGNI. Additionally, it discusses various design patterns, including creational, structural, and behavioral patterns, highlighting their roles in improving code flexibility, reusability, and maintainability.

Uploaded by

tamangsujita36
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 46

SOFTWARE DESIGN

PRINCIPLES &
PATTERNS
Prepared by Susmita Majhi
021BSCIT045
SOFTWARE DESIGN
Software design principles are fundamental guidelines that facilitate the creation of
well-structured and maintainable software systems. Basically, Software Design is
only a part of the development process in the Design Step.
Before we do Software Design(low-level) we have to complete Software Architecture
(high-level). Choosing an architecture will determine how to deal with performance,
fault tolerance, scalability, and reliability. Software Design is responsible for code
level(low-level) such as, what each module is doing, class scope, methods purposes,
and so on.
SOFTWARE DESIGN
Software Design Principles
• The key objectives of software design principles include:
• Encouraging code reusability and modularity.
• Promoting loose coupling between components or modules.
• Ensuring code readability and understandability.
• Enhancing testability, maintainability, and scalability.
• Facilitating effective collaboration among team members.
Software Design Principles
SOLID is a set of design principles that guide developers in creating maintainable,
scalable, and efficient object-oriented software systems. These principles provide a
foundation for writing code that is easy to understand, modify, and extend.
SOLID is an acronym that represents five fundamental principles:
Single Responsibility Principle (SRP)
Open-Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
SOLID
Single Responsibility Principle (SRP)
Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one reason to
change, meaning it should have just one responsibility. In other words, each class
should focus on a single task or concern to make the code more maintainable and
understandable.
Advantages:
• Easier maintenance & understanding – Simpler, more readable code.
• Reduced coupling – Less dependency between classes, improving flexibility.
• Increased cohesion – Methods and properties remain focused on one task.
Single Responsibility Principle (SRP)
// Not the best design // A much better design, adhering to the SRP
class CMS { class UserManager {
createUser() { createUser() {
// Create a new user // Create a new user
} }
deleteUser() {
deleteUser() { // Delete a user
// Delete a user
}
}
}
createContent() { class ContentManager {
// Create new content createContent() {
} // Create new content
}
editContent() { editContent() {
// Edit existing content // Edit existing content
} }
}
publishContent() {
// Publish content class ContentPublisher {
} publishContent() {
} // Publish content
}
}
Open-Closed Principle (OCP)
The Open-Closed Principle states that software entities (classes, modules, functions,
etc.) should be open for extension but closed for modification. In other words, you
should be able to add new functionality to a class without modifying its existing code,
by extending it or using other mechanisms like composition.

Advantages:
Increased Flexibility – New payment methods can be added without modifying
PaymentProcessor.
Improved Maintainability – Reduces the risk of breaking existing functionality.
Enhanced Modularity – Each payment method is independent and reusable.
Without OCP (Violation)
class AreaCalculator {
calculateArea(shape) {
if (shape instanceof Circle) return Math.PI * shape.radius ** 2;
if (shape instanceof Rectangle) return shape.width * shape.height;
}
}
With OCP (Better Design)
class Shape {
calculateArea() {} // To be overridden
}
class Circle extends Shape {
constructor(radius) { super(); this.radius = radius; }
calculateArea() { return Math.PI * this.radius ** 2; }
}
class Rectangle extends Shape {
constructor(width, height) { super(); this.width = width; this.height = height; }
calculateArea() { return this.width * this.height; }
}
class AreaCalculator {
calculateArea(shape) { return shape.calculateArea(); }
}
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that objects of a derived class should be able
to replace objects of the base class without affecting the program's correctness. In
other words, derived classes should adhere to the behavior and contracts defined by
the base class.
Advantages:
Improved Code Reusability – Derived classes can be substituted for base classes,
making the code more flexible and reusable.
Enhanced Maintainability – Ensures consistency in class hierarchies, simplifying
the reasoning about derived class behaviors.
Better Abstractions – Robust abstractions are created, reducing the risk of bugs or
unintended behavior.
Liskov Substitution Principle (LSP)
// Base class
// Eagle-specific flying behavior
class Bird { } layEggs() {
fly() {
// Eagle-specific egg-laying behavior
// Default implementation for flying }
} }
layEggs() {
// Incorrect derived class - violates LSP
// Default implementation for laying eggs class Penguin extends Bird {
} // Penguins cannot fly, so the fly method shouldn't be inherited
or implemented
// Derived class }
class Eagle extends Bird {

fly() {
Liskov Substitution Principle (LSP)
// A better design adhering to LSP
class FlightlessBird extends Bird {
// Remove or override the fly method as it's not applicable to flightless birds
fly() {
throw new Error("This bird cannot fly.");
}}
class Penguin extends FlightlessBird {
// Penguin-specific egg-laying behavior
layEggs() {
// ...
}}
Interface Segregation Principle (ISP)
The Interface Segregation Principle states that clients should not be forced to
depend on interfaces they do not use. In other words, large interfaces should be
split into smaller, more specific ones so that a class implementing the interface
only needs to focus on methods that are relevant to its functionality.
Advantage:
Reduced coupling between classes: By splitting interfaces into smaller, more
focused ones, you minimize the dependencies between classes, leading to a more
flexible and maintainable codebase.
Increased code readability: Smaller interfaces are easier to understand and
implement, making your code more readable.
Easier to update and modify interfaces: Since the interfaces are more focused,
they're less likely to change, making it easier to implement and update them
without breaking existing code.
Interface Segregation Principle (ISP)
Before Applying ISP: class CreditCardPayment: PaymentMethod {
func processCreditCardPayment() {
protocol PaymentMethod {
// Code to process a credit card payment
func }
processCreditCardPayment()
func processPayPalPayment() func processPayPalPayment() {
} // This method is not relevant for credit card payments
}
}

In this example, the CreditCardPayment class has to implement the


processPayPalPayment method even though it doesn't use it. This violates the ISP
because the class is forced to have unnecessary methods.
Interface Segregation Principle (ISP)

After Applying ISP:


class CreditCardPaymentProcessor:
protocol CreditCardPayment { CreditCardPayment {
func processCreditCardPayment() func processCreditCardPayment() {
}
// Code to process a credit card payment
protocol PayPalPayment { }
func processPayPalPayment() }
}
With this approach, we follow the ISP by allowing classes to implement only the
methods they actually need. This makes the code more focused, easier to understand,
and avoids the burden of implementing unnecessary methods.
Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that high-level modules should not depend on
low-level modules; both should depend on abstractions. Furthermore, abstractions should
not depend on details; details should depend on abstractions. In simple terms, this principle
encourages you to depend on abstract interfaces rather than concrete implementations,
promoting loose coupling and better separation of concerns.
Benefits:
Increased Flexibility – Easily swap implementations without affecting high-level modules.
Improved Maintainability – Loosely coupled code is easier to modify.
Enhanced Testability – Easier to mock or stub dependencies for testing.
Dependency Inversion Principle (DIP)
class ConcreteUserRepository{
fun findUserBy(username: String): User? {
...
}
...
}

class UserService {
private val userRepository: ConcreteUserRepository = ConcreteUserRepository()

fun authenticateUser(username: String, password: String): Boolean {


// Logic to authenticate user using the DatabaseService
val existingUser = userRepository.findUserBy(username)
if(existingUser != null) {
// authenticate
...
}
}
...
}
Dependency Inversion Principle (DIP)

• class UserIntegrationTest{

val userService = UserService()

@Test
fun `authenticateUser returns true for existing username`(){
// given
val username = "existingUser"
// add user to database
...

// when
val actual = userService.authenticateUser(username)

// then
assertThat(actual).isTrue()

}
}
DRY (Don't Repeat Yourself)
DRY (Don't Repeat Yourself)
The DRY principle is a software development guideline that encourages developers to
avoid duplicating code and logic. It emphasizes the importance of reusing code
through abstractions, modularization, and encapsulation. By following the DRY
principle, you can create a codebase that is easier to maintain, modify, and extend.
Benefits:
Easier maintenance: When you need to make changes or fix bugs, you only have to
do it in one place, reducing the risk of introducing inconsistencies or errors.
Improved readability: By eliminating duplication, you can make the code more
concise and easier to understand.
Faster development: Reusing code allows you to build features more quickly and
reduces the amount of code you need to write and maintain.
DRY (Don't Repeat Yourself)
Techniques to achieve DRY code:
Functions and methods: Encapsulate repeated logic within functions or methods that
can be called from multiple places.
Inheritance and composition: Reuse common functionality through inheritance or
composition, avoiding the duplication of code across classes or components.
Modularization: Break your code into smaller, reusable modules or libraries that can
be imported and used in different parts of the application.
Design patterns: Apply established design patterns that promote reusability and avoid
duplication.
Code refactoring: Continuously review and refactor your code to identify and
eliminate repetitions.
KISS (Keep It Simple, Stupid)
KISS (Keep It Simple, Stupid)
The KISS principle is a software development guideline that encourages developers
to keep their solutions as simple as possible. It's based on the idea that the simplest
solution is often the best, and unnecessary complexity should be avoided. The KISS
principle aims to make code easier to understand, maintain, and modify.
Benefits:
Improved code readability: Simple code is easier to read, understand, and debug,
making it more maintainable in the long run.
Faster development: By focusing on simplicity, you can often build features more
quickly and with fewer bugs.
Easier collaboration: Simple code is easier for other team members to work with,
leading to better collaboration and knowledge sharing.
KISS (Keep It Simple, Stupid)
When to apply KISS:

The KISS principle should be applied throughout the software development process, from
design to implementation. It's particularly useful when:
• Designing code architecture, ensuring it's easy to understand and modify.
• Writing code, by choosing straightforward algorithms and data structures.
• Refactoring code, by simplifying complex logic and removing unnecessary components.
YAGNI (You Aren't Gonna Need It)

Focus on implementing only the features that are needed now, not
anticipating future requirements.
Benefits:
Faster Development – Delivers features quickly by focusing on
current needs.
Reduced Complexity – Keeps the codebase simple and
maintainable.
Minimized Waste – Avoids wasting resources on unnecessary
features.
YAGNI (You Aren't Gonna Need It)
When to apply YAGNI:

YAGNI is particularly useful in Agile development methodologies, where the focus is


on iterative and incremental development. It should be applied when:
• The future requirements are uncertain or subject to change.
• You are working on a tight schedule and need to prioritize the most important
features.
• You want to minimize the risk of overengineering and code bloat.
Software Design Patterns

A design pattern is a general repeatable solution to a commonly occurring problem in


software design. A design pattern isn't a finished design that can be transformed
directly into code. It is a description or template for how to solve a problem that can
be used in many different situations.
Creational Patterns
Creational Patterns
These patterns deal with object creation mechanisms, trying to create objects in a
manner suitable to the situation.
Singleton – Ensures a class has only one instance and provides a global point of
access.
Factory Method – Defines an interface for creating objects, but lets subclasses alter
the type of objects that will be created.
Abstract Factory – Provides an interface for creating families of related or
dependent objects without specifying their concrete classes.
Builder – Separates the construction of a complex object from its representation.
Prototype – Creates new objects by copying an existing object, known as the
prototype.
Factory Method pattern
The Factory Method pattern is a creational design pattern that provides an interface for
creating objects in a superclass but allows subclasses to alter the type of objects that will
be created. It encapsulates the object creation logic, decoupling it from the client code that
uses the objects.
Components of Factory Method Design Pattern
• Product: Represents the interface of the objects created by the factory method.
• ConcreteProduct: Implements the Product interface and represents the concrete objects
created by the factory method.
• Creator: Declares the factory method, which returns an instance of the Product interface.
• ConcreteCreator: Implements the factory method to create instances of
ConcreteProduct.
Advantages
• Encourages loose coupling between client code and the created objects.
• Providesa centralized point of control for object creation, facilitating easier
maintenance and testing.
• Supportsthe Open/Closed Principle, allowing for the addition of new product types
without modifying existing client code.

Disadvantages
• Increases complexity in the codebase, especially when dealing with multiple
factories and product types.
• May introduce runtime errors if the factory method is not implemented properly or
if there are inconsistencies in product creation logic across subclasses.
Structural Patterns
• These patterns focus on how classes and objects are composed to form larger structures.
• Adapter – Allows incompatible interfaces to work together.
• Bridge – Decouples abstraction from implementation, allowing both to vary independently.
• Composite – Allows objects to be composed into tree-like structures to represent part-
whole hierarchies.
• Decorator – Adds additional functionality to an object dynamically without modifying its
structure.
• Facade – Provides a simplified interface to a complex subsystem.
• Flyweight – Reduces the number of objects created to minimize memory usage.
• Proxy – Provides a surrogate or placeholder for another object.
Structural Patterns
Adapter Design Pattern
Adapter design pattern is one of the structural design pattern and it is used so that two
unrelated interfaces can work together.
The Components of the Adapter Design Pattern -:
The Adapter pattern involves three main components -:
a) Target: This represents the interface or class that the client code expects to work with. It
defines the operations that the client can use.
b) Adaptee: This refers to the existing class or interface that needs to be adapted. It has a
different interface that is incompatible with the client’s expectations.
c) Adapter: This is the class that bridges the gap between the Target and Adaptee. It
implements the Target interface and internally uses an instance of the Adaptee to perform the
desired operations. The Adapter class acts as a wrapper or translator, adapting the Adaptee’s
interface to match the Target interface.
Adapter Design Pattern
Behavioral Patterns
These patterns focus on communication between objects, defining the
responsibilities of objects and how they interact.
Observer – Allows an object to notify other objects about changes without
knowing who or what those objects are.
Strategy – Defines a family of algorithms and makes them interchangeable.
Command – Encapsulates a request as an object, allowing for parameterization
and queuing.
Iterator – Provides a way to access elements of a collection sequentially
without exposing the collection’s underlying representation.
State – Allows an object to alter its behavior when its internal state changes.
Behavioral Patterns
Improved Communication Between Objects – Behavioral patterns focus on how objects
interact with each other, promoting better communication and coordination.

Enhanced Flexibility – They allow dynamic behavior changes during runtime, which
makes systems more adaptable to new requirements.

Separation of Concerns – These patterns often lead to clearer responsibility divisions,


making the system easier to maintain and scale.

Promotes Reusability – Many behavioral patterns encourage the reuse of objects and
functionality, reducing redundancy in the system.

Increased Extensibility – These patterns can simplify adding new behaviors or features
without modifying existing code.
Behavioral Patterns
Template Method – Defines the structure of an algorithm, deferring some
steps to subclasses.
Mediator – Promotes loose coupling by keeping objects from referring to
each other explicitly and letting the mediator handle communication.
Chain of Responsibility – Passes a request along a chain of handlers,
allowing multiple objects to process it.
Memento – Captures and restores an object's state without violating
encapsulation.
Behavioral Patterns
Observer Design Pattern
The Observer Design Pattern is one of the most commonly used behavioral design
patterns in software development. It is particularly useful when you want to establish a
one-to-many relationship between objects. This allows an object (known as the subject)
to notify other objects (known as observers) when there is a change in its state.
The Observer Pattern helps with:
• Decoupling:It decouples the subject from its observers, meaning the subject doesn’t
need to know who or how many observers there are.
• Event handling: Whenever the state of the subject changes, all dependent observers
are notified automatically.
• Dynamic relationships: New observers can be added or removed at runtime without
affecting the subject.
Advantages
• Loose coupling: The subject does not need to know who its observers are, only that
they implement the Observer interface.
• Open/Closed Principle: You can add or remove observers without modifying the
subject’s code.

Disadvantages
• Memory leaks: If observers are not properly removed, memory leaks can occur, as
the subject holds references to them.
• Unpredictable updates: Observers may update in an unpredictable order, which
can cause issues if the order of updates is important.
Observer Design Pattern
CONCLUSION
Design Patterns:

These are well-established solutions to common design challenges. Patterns like Singleton, Factory,
Adapter, etc., allow developers to avoid reinventing the wheel and implement efficient solutions that are
tried and tested.

Improved Software Quality:

By following SOLID principles and using design patterns, developers reduce redundancy, improve
readability, and ensure code is easy to extend. This leads to higher-quality software that is easier to
maintain.

Reduced Complexity:

SOLID principles and design patterns help break down complex systems into smaller, manageable
pieces, reducing the overall complexity of development and making it easier to handle future changes.

You might also like