0% found this document useful (0 votes)
4 views33 pages

Assignment 7

The document outlines the application of SOLID principles in Java for software design, emphasizing maintainability and scalability. It provides detailed examples of refactoring use cases such as payment processing, notification systems, and content management systems to adhere to these principles. Each principle is illustrated with UML diagrams, demonstrating how to separate responsibilities, ensure extensibility, and depend on abstractions.

Uploaded by

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

Assignment 7

The document outlines the application of SOLID principles in Java for software design, emphasizing maintainability and scalability. It provides detailed examples of refactoring use cases such as payment processing, notification systems, and content management systems to adhere to these principles. Each principle is illustrated with UML diagrams, demonstrating how to separate responsibilities, ensure extensibility, and depend on abstractions.

Uploaded by

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

Assignment 7 David Rilett

Instructions: 1. For each plantUML code, replace it with the UML diagram it
creates and edit the connections between classes for accuracy as needed.
Do not remove the existing labels and explanations 2. For each scenario,
edit the comments on it by making reference to your UML.

Your name goes here:

SOLID principles are a set of design principles that promote maintainability, flexibility, and scalability in
object-oriented software development. These principles include Single Responsibility Principle (SRP),
Open/Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle
(ISP), and Dependency Inversion Principle (DIP). Here's how you can apply these principles in Java
while implementing business logic from a use case:
1. Single Responsibility Principle (SRP):

Identify the responsibilities of your use case. A use case typically involves multiple steps or
actions.
Create separate classes or methods for each responsibility. For example, separate classes
for input validation, business rules, and data persistence.
public class UseCase {
private Validator validator;
private BusinessLogic businessLogic;
private DataPersistence dataPersistence;

public UseCase(Validator validator, BusinessLogic businessLogic,


DataPersistence dataPersistence) {
this.validator = validator;
this.businessLogic = businessLogic;
this.dataPersistence = dataPersistence;
}

public void execute() {


if (validator.isValid()) {
businessLogic.perform();
dataPersistence.saveData();
} else {
// Handle invalid input
}
}
}
2. Open/Closed Principle (OCP):

Design your classes and modules to be open for extension but closed for modification.
Use interfaces and abstract classes to define contracts that can be extended without
modifying existing code.

public interface BusinessLogic {


void perform();
}

public class ConcreteBusinessLogic implements BusinessLogic {


@Override
public void perform() {
// Implementation of business logic
}
}
3. Liskov Substitution Principle (LSP):

Ensure that derived classes can be substituted for their base classes without affecting the
correctness of the program.
Follow the contracts defined by interfaces and base classes.
public interface BusinessLogic {
void perform();
}

public class ExtendedBusinessLogic implements BusinessLogic {


@Override
public void perform() {
// Extended implementation of business logic
}
}

4. Interface Segregation Principle (ISP):

Avoid fat interfaces by breaking them into smaller, specific interfaces.


Clients should not be forced to depend on interfaces they do not use.
public interface Validator {
boolean isValid();
}

public interface DataPersistence {


void saveData();
}
5. Dependency Inversion Principle (DIP):

Depend on abstractions, not concretions.


High-level modules should not depend on low-level modules; both should depend on
abstractions.
public class UseCase {
private Validator validator;
private BusinessLogic businessLogic;
private DataPersistence dataPersistence;

public UseCase(Validator validator, BusinessLogic businessLogic, DataPersistence dataPersistence) {


this.validator = validator;
this.businessLogic = businessLogic;
this.dataPersistence = dataPersistence;
}
public class UseCase {
private Validator validator;
private BusinessLogic businessLogic;
private DataPersistence dataPersistence;

public UseCase(Validator validator, BusinessLogic businessLogic,


DataPersistence dataPersistence) {
this.validator = validator;
this.businessLogic = businessLogic;
this.dataPersistence = dataPersistence;
}

public void execute() {


if (validator.isValid()) {
businessLogic.perform();
dataPersistence.saveData();
} else {
// Handle invalid input
}
}
}

public interface BusinessLogic {


void perform();
}

public class ConcreteBusinessLogic implements BusinessLogic {


@Override
public void perform() {
// Implementation of business logic
}
}
public interface BusinessLogic {
void perform();
}

public class ExtendedBusinessLogic implements BusinessLogic {


@Override
public void perform() {
// Extended implementation of business logic
}
}
public interface Validator {
boolean isValid();
}
public interface DataPersistence {
void saveData();
}}

By applying these SOLID principles, you create a more modular, maintainable, and extensible
codebase for your business logic in Java.

Scenario: Payment Processing

Original Use Case (Without SOLID):

Use Case: Process Payment

The system needs to process payments, including validation and persistence.


@startuml
class PaymentProcessor {
+processPayment(payment: Payment): void
}

class Payment {
-amount: double
-customerId: String
-paymentMethod: String
+validate(): boolean
+persist(): void
}

PaymentProcessor --> Payment


@enduml

Refactored Use Case (Applying SOLID):

1. Single Responsibility Principle (SRP):


Separate responsibilities of payment validation and persistence.

@startuml
interface PaymentValidator {
+validate(payment: Payment): boolean
}

interface PaymentPersister {
+persist(payment: Payment): void
}

class PaymentProcessor {
-validator: PaymentValidator
-persister: PaymentPersister

+processPayment(payment: Payment): void


}

class Payment {
-amount: double
-customerId: String
-paymentMethod: String
}

PaymentProcessor *--> PaymentValidator


PaymentProcessor *--> PaymentPersister
@enduml

2. Open/Closed Principle (OCP):


Make the PaymentProcessor open for extension (for handling different payment methods)
but closed for modification.
@startuml
interface PaymentValidator {
+validate(payment: Payment): boolean
}

interface PaymentPersister {
+persist(payment: Payment): void
}

interface PaymentMethodProcessor {
+processPayment(payment: Payment): void
}

class PaymentProcessor {
-validator: PaymentValidator
-persister: PaymentPersister
-methodProcessor: PaymentMethodProcessor

+processPayment(payment: Payment): void


}

class CreditCardProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class PayPalProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class Payment {
-amount: double
-customerId: String
-paymentMethod: String
}

PaymentProcessor *--> PaymentValidator


PaymentProcessor *--> PaymentPersister
PaymentProcessor *--> PaymentMethodProcessor
@enduml

3. Liskov Substitution Principle (LSP):


Ensure that the CreditCardProcessor and PayPalProcessor can be substituted for the
PaymentMethodProcessor.

@startuml
interface PaymentValidator {
+validate(payment: Payment): boolean
}

interface PaymentPersister {
+persist(payment: Payment): void
}

interface PaymentMethodProcessor {
+processPayment(payment: Payment): void
}

class PaymentProcessor {
-validator: PaymentValidator
-persister: PaymentPersister
-methodProcessor: PaymentMethodProcessor

+processPayment(payment: Payment): void


}

class CreditCardProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class PayPalProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class Payment {
-amount: double
-customerId: String
-paymentMethod: String
}

PaymentProcessor *--> PaymentValidator


PaymentProcessor *--> PaymentPersister
PaymentProcessor *--> PaymentMethodProcessor
@enduml

4. Interface Segregation Principle (ISP):


Separate the interfaces for PaymentValidator and PaymentPersister.
@startuml
interface PaymentValidation {
+validate(payment: Payment): boolean
}

interface PaymentPersistence {
+persist(payment: Payment): void
}

interface PaymentMethodProcessor {
+processPayment(payment: Payment): void
}

class PaymentProcessor {
-validation: PaymentValidation
-persistence: PaymentPersistence
-methodProcessor: PaymentMethodProcessor

+processPayment(payment: Payment): void


}

class CreditCardProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class PayPalProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class Payment {
-amount: double
-customerId: String
-paymentMethod: String
}

PaymentProcessor *--> PaymentValidation


PaymentProcessor *--> PaymentPersistence
PaymentProcessor *--> PaymentMethodProcessor
@enduml

5. Dependency Inversion Principle (DIP):


Depend on abstractions (PaymentValidation, PaymentPersistence,
PaymentMethodProcessor) rather than concrete implementations.
@startuml
interface PaymentValidation {
+validate(payment: Payment): boolean
}

interface PaymentPersistence {
+persist(payment: Payment): void
}

interface PaymentMethodProcessor {
+processPayment(payment: Payment): void
}

class PaymentProcessor {
-validation: PaymentValidation
-persistence: PaymentPersistence
-methodProcessor: PaymentMethodProcessor

+processPayment(payment: Payment): void


}

class CreditCardProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class PayPalProcessor implements PaymentMethodProcessor {


+processPayment(payment: Payment): void
}

class Payment {
-amount: double
-customerId: String
-paymentMethod: String
}

PaymentProcessor *--> PaymentValidation


PaymentProcessor *--> PaymentPersistence
PaymentProcessor *--> PaymentMethodProcessor
@enduml

This refactoring adheres to SOLID principles, making the code more modular, maintainable, and
extensible. Each class or interface now has a single responsibility, and dependencies are inverted to
depend on abstractions.

Let's consider a simple scenario of a notification system that sends messages through different
channels. We'll start with a basic use case and then refactor it to adhere to SOLID principles using
for visual representation.

Scenario: Notification System

Original Use Case (Without SOLID):

Use Case: Send Notification

The system needs to send notifications through different channels (e.g., email, SMS).

@startuml

class NotificationService {
+sendNotification(message: String, channel: String): void
}

NotificationService --> EmailService


NotificationService --> SMSService

class EmailService {
+sendEmail(message: String): void
}

class SMSService {
+sendSMS(message: String): void
}
@enduml

Refactored Use Case (Applying SOLID):

1. Single Responsibility Principle (SRP):


Separate responsibilities for handling different channels.
@startuml
interface NotificationSender {
+sendNotification(message: String): void
}

class EmailService implements NotificationSender {


+sendNotification(message: String): void
+sendEmail(message: String): void
}

class SMSService implements NotificationSender {


+sendNotification(message: String): void
+sendSMS(message: String): void
}
@enduml

2. Open/Closed Principle (OCP):


Make the NotificationSender open for extension (for adding new channels) but closed for
modification.

@startuml
interface NotificationSender {
+sendNotification(message: String): void
}

class EmailService implements NotificationSender {


+sendNotification(message: String): void
+sendEmail(message: String): void
}

class SMSService implements NotificationSender {


+sendNotification(message: String): void
+sendSMS(message: String): void
}

class PushNotificationService implements NotificationSender {


+sendNotification(message: String): void
+sendPushNotification(message: String): void
}
@enduml
3. Liskov Substitution Principle (LSP):
Ensure that EmailService, SMSService, and PushNotificationService can be
substituted
@startuml
interface NotificationSender {
+sendNotification(message: String): void
}

class EmailService implements NotificationSender {


+sendNotification(message: String): void
+sendEmail(message: String): void
}

class SMSService implements NotificationSender {


+sendNotification(message: String): void
+sendSMS(message: String): void
}

class PushNotificationService implements NotificationSender {


+sendNotification(message: String): void
+sendPushNotification(message: String): void
}
@enduml

4. Interface Segregation Principle (ISP):


Separate interfaces for EmailSender, SMSSender, and PushNotificationSender.

@startuml
interface EmailSender {
+sendEmail(message: String): void
}

interface SMSSender {
+sendSMS(message: String): void
}

interface PushNotificationSender {
+sendPushNotification(message: String): void
}

interface NotificationSender extends EmailSender, SMSSender, PushNotificationSender {


+sendNotification(message: String): void
}

class EmailService implements EmailSender {


+sendEmail(message: String): void
}

class SMSService implements SMSSender {


+sendSMS(message: String): void
}

class PushNotificationService implements PushNotificationSender {


+sendPushNotification(message: String): void
}
@enduml

5. Dependency Inversion Principle (DIP):


Depend on abstractions (EmailSender, SMSSender, PushNotificationSender) rather than
concrete implementations.

@startuml
interface EmailSender {
+sendEmail(message: String): void
}

interface SMSSender {
+sendSMS(message: String): void
}

interface PushNotificationSender {
+sendPushNotification(message: String): void
}

interface NotificationSender extends EmailSender, SMSSender, PushNotificationSender {


+sendNotification(message: String): void
}

class EmailService implements EmailSender {


+sendEmail(message: String): void
}

class SMSService implements SMSSender {


+sendSMS(message: String): void
}

class PushNotificationService implements PushNotificationSender {


+sendPushNotification(message: String): void
}
@enduml
This refactoring adheres to SOLID principles, making the code more modular, maintainable, and
extensible. Each class or interface now has a single responsibility, and dependencies are inverted to
depend on abstractions.
Let's consider a scenario of a content management system. We'll start with a basic use case and
then refactor it to adhere to SOLID principles using for visual representation.

Scenario: Content Management System

Original Use Case (Without SOLID):

Use Case: Manage Content

The system needs to manage different types of content (e.g., articles, videos).

@startuml
class ContentManager {
+addContent(content: Content): void
}

class Content {
-type: String
-data: String
+display(): void
}

class Article extends Content {


+display(): void
}

class Video extends Content {


+display(): void
}
@enduml

Refactored Use Case (Applying SOLID):

1. Single Responsibility Principle (SRP):


Separate responsibilities for content management and displaying content.
@startuml
interface ContentDisplay {
+display(): void
}

class ContentManager {
+addContent(content: Content): void
}

class Content {
-type: String
-data: String
}

class Article extends Content implements ContentDisplay {


+display(): void
}

class Video extends Content implements ContentDisplay {


+display(): void
}
@enduml

2. Open/Closed Principle (OCP):


Make the ContentManager open for extension (for adding new content types) but closed
for modification.

@startuml
interface ContentDisplay {
+display(): void
}

interface Content {
// Common content properties and methods
}

class ContentManager {
+addContent(content: Content): void
}

class Article implements Content, ContentDisplay {


// Article-specific properties and methods
}
class Video implements Content, ContentDisplay {
// Video-specific properties and methods
}

class Image implements Content, ContentDisplay {


// Image-specific properties and methods
}
@enduml

3. Liskov Substitution Principle (LSP):


Ensure that Article, Video, and Image can be substituted for Content and
ContentDisplay.

@startuml
interface ContentDisplay {
+display(): void
}

interface Content {
// Common content properties and methods
}

class ContentManager {
+addContent(content: Content): void
}

class Article implements Content, ContentDisplay {


// Article-specific properties and methods
}

class Video implements Content, ContentDisplay {


// Video-specific properties and methods
}

class Image implements Content, ContentDisplay {


// Image-specific properties and methods
}
@enduml

4. Interface Segregation Principle (ISP):


Separate interfaces for ArticleDisplay, VideoDisplay, ImageDisplay.

@startuml
interface ContentDisplay {
+display(): void
}

interface ArticleDisplay {
+display(): void
}

interface VideoDisplay {
+display(): void
}

interface ImageDisplay {
+display(): void
}

interface Content {
// Common content properties and methods }

class ContentManager {
+addContent(content: Content): void
}

class Article implements Content, ArticleDisplay { // Article-specific


properties and methods }

class Video implements Content, VideoDisplay { //


Video-specific properties and methods }

class Image implements Content, ImageDisplay { //


Image-specific properties and methods }
@enduml

5. Dependency Inversion Principle (DIP):


Depend on abstractions (ContentDisplay, ArticleDisplay, VideoDisplay,
ImageDisplay) rather than concrete implementations.
@startuml
interface ContentDisplay {
+display(): void
}

interface ArticleDisplay {
+display(): void
}

interface VideoDisplay {
+display(): void
}

interface ImageDisplay {
+display(): void
}

interface Content {
// Common content properties and methods }

class ContentManager {
+addContent(content: Content): void
}

class Article implements Content, ArticleDisplay { // Article-specific


properties and methods }

class Video implements Content, VideoDisplay { //


Video-specific properties and methods }

class Image implements Content, ImageDisplay { //


Image-specific properties and methods }
@enduml

27/50
This refactoring adheres to SOLID principles, making the code more modular, maintainable, and
extensible. Each class or interface now has a single responsibility, and dependencies are inverted to
depend on abstractions.

Let's consider a scenario of an online shopping system. We'll start with a basic use case and then
refactor it to adhere to SOLID principles using for visual representation.

Scenario: Online Shopping System

Original Use Case (Without SOLID):

Use Case: Manage Products and Orders

The system needs to manage products and process orders.

@startuml
class ProductService {
+addProduct(product: Product): void
}

class OrderService {
+createOrder(cart: ShoppingCart): void
}

class ShoppingCart {
-items: List<Item>
+addItem(item: Item): void
+checkout(): void
}

class Product {
-name: String
-price: double
}

class Item {
-product: Product
-quantity: int
+calculateTotal(): double
}
@enduml

Refactored Use Case (Applying SOLID):

1. Single Responsibility Principle (SRP):


Separate responsibilities for managing products, handling orders, and managing the
shopping cart.
@startuml
class ProductManager {
+addProduct(product: Product): void
}

class OrderProcessor {
+createOrder(cart: ShoppingCart): void
}

class ShoppingCart {
-items: List<Item>
+addItem(item: Item): void
+checkout(): void
}

class Product {
-name: String
-price: double
}

class Item {
-product: Product
-quantity: int
+calculateTotal(): double
}
@enduml

2. Open/Closed Principle (OCP):


Make the OrderProcessor open for extension (for handling different order processing
methods) but closed for modification.

@startuml
interface OrderProcessor {
+createOrder(cart: ShoppingCart): void
}
class StandardOrderProcessor implements OrderProcessor {
+createOrder(cart: ShoppingCart): void
}

class ExpressOrderProcessor implements OrderProcessor {


+createOrder(cart: ShoppingCart): void
}

class ShoppingCart {
-items: List<Item>
+addItem(item: Item): void
+checkout(orderProcessor: OrderProcessor): void
}

class Product {
-name: String
-price: double
}

class Item {
-product: Product
-quantity: int
+calculateTotal(): double
}
@enduml

3. Liskov Substitution Principle (LSP):


Ensure that StandardOrderProcessor and ExpressOrderProcessor can be substituted for
OrderProcessor.

@startuml
interface OrderProcessor {
+createOrder(cart: ShoppingCart): void
}

class StandardOrderProcessor implements OrderProcessor {


+createOrder(cart: ShoppingCart): void
}

class ExpressOrderProcessor implements OrderProcessor {


+createOrder(cart: ShoppingCart): void
}
class ShoppingCart {
-items: List<Item>
+addItem(item: Item): void
+checkout(orderProcessor: OrderProcessor): void
}

class Product {
-name: String
-price: double
}

class Item {
-product: Product
-quantity: int
+calculateTotal(): double
}
@enduml

4. Interface Segregation Principle (ISP):


Separate interfaces for ProductManagement, OrderProcessing, and
ShoppingCartManagement.

@startuml
interface ProductManagement {
+addProduct(product: Product): void
}

interface OrderProcessing {
+createOrder(cart: ShoppingCart): void
}

interface ShoppingCartManagement {
+addItem(item: Item): void
+checkout(orderProcessor: OrderProcessing): void }

class ProductManager implements ProductManagement {


+addProduct(product: Product): void
}

class StandardOrderProcessor implements OrderProcessing {


+createOrder(cart: ShoppingCart): void
}
class ExpressOrderProcessor implements OrderProcessing {
+createOrder(cart: ShoppingCart): void
}

class ShoppingCart implements ShoppingCartManagement { -items:


List<Item>
+addItem(item: Item): void
+checkout(orderProcessor: OrderProcessing): void }

class Product {
-name: String
-price: double
}

class Item {
-product: Product
-quantity: int
+calculateTotal(): double
}

@enduml

5. Dependency Inversion Principle (DIP):


Depend on abstractions (ProductManagement, OrderProcessing,
ShoppingCartManagement) rather than concrete implementations.
@startuml
interface ProductManagement {
+addProduct(product: Product): void
}

interface OrderProcessing {
+createOrder(cart: ShoppingCart): void
}

interface ShoppingCartManagement {
+addItem(item: Item): void
+checkout(orderProcessor: OrderProcessing): void }

class ProductManager implements ProductManagement {


+addProduct(product: Product): void
}

class StandardOrderProcessor implements OrderProcessing {


+createOrder(cart: ShoppingCart): void
}

class ExpressOrderProcessor implements OrderProcessing {


+createOrder(cart: ShoppingCart): void
}
class ShoppingCart implements ShoppingCartManagement { -items:
List<Item>
+addItem(item: Item): void
+checkout(orderProcessor: OrderProcessing): void }

class Product {
-name: String
-price: double
}

class Item {
-product: Product
-quantity: int
+calculateTotal(): double
}
@enduml

This refactoring adheres to SOLID principles, making the code more modular, maintainable, and
extensible. Each class or interface now has a single responsibility, and dependencies are inverted to
depend on abstractions.

Let's consider a scenario involving authentication and authorization in a web application. We'll start
with a basic use case and then refactor it to adhere to SOLID principles using for visual
representation.

Scenario: Web Application Authentication and Authorization

Original Use Case (Without SOLID):

Use Case: User Authentication and Authorization

The system needs to handle user authentication and authorization.

@startuml
class AuthenticationService {
+authenticate(username: String, password: String): boolean
+authorize(user: User, role: String): boolean
}

class User {
-username: String
-password: String
-roles: List<String>
}

class UserController {
+login(username: String, password: String): String
+checkAuthorization(user: User, role: String): boolean
}
@enduml

List of the example scenarios that have been discussed:


1. Payment Processing System:

Scenario: Processing payments with different payment methods.


Refactoring for SOLID principles: Separating responsibilities, open for extension but
closed for modification, interface segregation, and dependency inversion.

46/50
2. Notification System:

Scenario: Sending notifications through different channels (email, SMS).


Refactoring for SOLID principles: Single responsibility, open for extension but closed for
modification, Liskov substitution, interface segregation, and dependency inversion. 3. Content
Management System:

Scenario: Managing different types of content (articles, videos).


Refactoring for SOLID principles: Single responsibility, open for extension but closed for
modification, Liskov substitution, interface segregation, and dependency inversion. 4. Online
Shopping System:

Scenario: Managing products and processing orders.


Refactoring for SOLID principles: Single responsibility, open for extension but closed for
modification, Liskov substitution, interface segregation, and dependency inversion. 5. Web
Application Authentication and Authorization:

Scenario: Handling user authentication and authorization in a web application.


Refactoring for SOLID principles: Single responsibility, open for extension but closed for
modification, Liskov substitution, interface segregation, and dependency inversion.

Each example demonstrates the application of SOLID principles to improve code structure,
maintainability, and flexibility.

SUMMARY
Here is a set of instructions to follow for each scenario example to refactor the code according to
SOLID principles:

Instructions for Refactoring to SOLID Principles:


1. Payment Processing System:

Identify and separate the responsibilities related to payment validation and


persistence. Introduce interfaces for payment validation and persistence.
Implement concrete classes for payment validation and persistence.
Refactor the PaymentProcessor to use the new interfaces.

2. Notification System:

Identify and separate the responsibilities related to different notification channels (e.g.,
email, SMS).
Introduce interfaces for notification channels.
Implement concrete classes for each notification channel.
Refactor the NotificationService to use the new interfaces.
3. Content Management System:

Identify and separate the responsibilities related to content management and


content display.
Introduce interfaces for content management and display.
Implement concrete classes for each content type.
Refactor the ContentManager to use the new interfaces.
4. Online Shopping System:

Identify and separate the responsibilities related to product management, order


processing, and shopping cart management.
Introduce interfaces for each responsibility.
Implement concrete classes for product management, order processing, and shopping cart
management.
Refactor the ContentManager to use the new interfaces.
5. Web Application Authentication and Authorization:

Identify and separate the responsibilities related to authentication, authorization, and


user interface handling.
Introduce interfaces for authentication, authorization, and user interface
handling. Implement concrete classes for each responsibility.
Refactor the UserController to use the new interfaces and apply dependency injection.

General Instructions:
For each refactoring, consider the Single Responsibility Principle (SRP), Open/Closed Principle
(OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and
Dependency Inversion Principle (DIP).
Ensure that classes and interfaces have clear responsibilities and are loosely coupled.
Apply dependency injection to promote flexibility and adherence to DIP.
Update class relationships and dependencies in diagrams accordingly.

Generalized Instructions for Refactoring to SOLID Principles:


1. Identify and Separate Responsibilities:

Examine the existing code and identify distinct responsibilities within the system, such as
validation, persistence, notification channels, content management, order processing, and
authentication.
2. Introduce Interfaces:

For each identified responsibility, introduce corresponding interfaces that define the
contract for the associated functionality. This promotes adherence to the Interface
Segregation Principle (ISP).
3. Implement Concrete Classes:

Create concrete classes that implement the introduced interfaces. Each concrete class
should encapsulate the specific behavior related to its responsibility. This supports the
Single Responsibility Principle (SRP).
4. Refactor Existing Code:

Modify the existing code to use the newly introduced interfaces and concrete classes.
Update dependencies to rely on abstractions rather than concrete implementations,
following the Dependency Inversion Principle (DIP).
5. Apply Dependency Injection:

Whenever applicable, consider applying dependency injection to inject dependencies into


classes rather than instantiating them internally. This enhances flexibility and supports the
Dependency Inversion Principle (DIP).
6. Consider Open/Closed Principle (OCP):

Design the system to be open for extension but closed for modification. When adding new
functionality or modifying existing behavior, favor extending existing interfaces or
introducing new interfaces.
7. Ensure Liskov Substitution Principle (LSP):

Verify that concrete classes implementing interfaces can be substituted for instances of
those interfaces without affecting the correctness of the system. This ensures consistency
and extensibility.
8. Update Diagrams:

Reflect the changes made in the code by updating the diagrams. Clearly represent
the relationships, interfaces, and dependencies to visually communicate the design
improvements.

Appendix:
By following these generalized instructions, students can systematically apply SOLID principles
across various scenarios, ensuring the codebase becomes modular, maintainable, and extensible.
The principles of SOLID, when consistently applied, contribute to robust software design and
development.

Robert C. Martin, also known as "Uncle Bob," is a software engineer and one of the authors of the
SOLID principles. The SOLID principles are a set of five design principles that promote
maintainability, flexibility, and readability in object-oriented software design. Keep in mind that
there might be updates or changes beyond my last knowledge update in January 2022. As of
then, the SOLID principles are:

Single Responsibility Principle (SRP):

Definition: A class should have only one reason to change, meaning that a class should have only
one responsibility or job. If a class has more than one responsibility, it becomes more difficult to
understand, modify, and maintain.
Guidance: Design classes that have a single reason to change. If you can describe a class with
the word "and," it might be doing more than one thing.

Open/Closed Principle (OCP):

Definition: Software entities (classes, modules, functions, etc.) should be open for extension but
closed for modification. This means that you should be able to add new functionality without
altering the existing code.
Guidance: Use abstraction and polymorphism to design systems that can be easily extended
without modifying existing code. This involves creating interfaces or abstract classes that can be
implemented or extended by new code.

Liskov Substitution Principle (LSP):

Definition: Subtypes must be substitutable for their base types without altering the correctness of
the program. In other words, if a class is a subtype of another class, it should be able to replace
that class without affecting the program's behavior.
Guidance: Ensure that derived classes adhere to the contracts and expectations set by their base
classes. Subtypes should extend rather than contradict the behavior of the base types.
Interface Segregation Principle (ISP):

Definition: A class should not be forced to implement interfaces it does not use. This means that a
class should only be required to implement methods that are relevant to its functionality.
Guidance: Design fine-grained interfaces specific to the needs of clients. Avoid creating large,
monolithic interfaces that force implementing classes to provide methods they don't need.

Dependency Inversion Principle (DIP):

Definition: High-level modules (e.g., business logic) should not depend on low-level modules (e.g.,
data access or specific implementations), but both should depend on abstractions. Abstractions
should not depend on details; details should depend on abstractions.

Guidance: Use dependency injection, abstractions, and inversion of control to achieve a flexible
and loosely coupled architecture. High-level modules should depend on abstract interfaces,
allowing them to be independent of the low-level implementation details

https://blog.cleancoder.com/uncle-bob/2020/10/18/Solid-Relevance.html

https://en.wikipedia.org/wiki/SOLID

https://sujoyu.github.io/-previewer/

1.​ Association:

●​ Line Type: Solid line.

●​ Arrow: By default, no arrowhead. You can use --> to add an arrowhead at one end, <-- for the other
end, or <-> for both ends.

●​ Syntax Example: A -- B, A --> B, A <-- B, A <-> B.

2.​ Aggregation:

●​ Line Type: Solid line with an open diamond at the aggregate end.

●​ Arrow: No arrowhead.

●​ Syntax Example: A o-- B.

3.​ Composition:

●​ Line Type: Solid line with a filled diamond at the composite end.

●​ Arrow: No arrowhead.

●​ Syntax Example: A *-- B.


4.​ Generalization (Inheritance):

●​ Line Type: Solid line with a hollow triangle at the child end.

●​ Arrow: No arrowhead.

●​ Syntax Example: A <|-- B.

5.​ Realization (Interface):

●​ Line Type: Dashed line with a hollow triangle at the implementing class end.

●​ Arrow: No arrowhead.

●​ Syntax Example: A <|.. B.

6.​ Dependency:

●​ Line Type: Dashed line.

●​ Arrow: By default, an arrowhead pointing from the dependent to the independent element. You can use
..> for no arrowhead.

●​ Syntax Example: A ..> B, A --> B, A ..|> B.

7.​ Association Class:

●​ Line Type: Solid line connecting three elements (two classes and an association class).

●​ Arrow: By default, no arrowhead.

●​ Syntax Example: A -- (B, C).

The arrow points from the dependent element (the element that relies on the other) to the independent
element.

Worked Example:
Scenario: File Management System
Original Use Case (Without SOLID):
Use Case: Process Different Types of Files

The system needs to handle different types of files, such as text files, image files, and audio files.
@startuml
class FileManager {
+processFile(file: File): void
}

class File {
-type: String
-data: byte[]
}

class TextFile extends File {


+displayText(): void
}

class ImageFile extends File {


+displayImage(): void
}

class AudioFile extends File {


+playAudio(): void
}
@enduml

Refactored Use Case (Applying SOLID):


Single Responsibility Principle (SRP):
Separate responsibilities for file processing and displaying file content.

@startuml
interface FileDisplay {
+display(): void
}

class FileManager {
+processFile(file: FileDisplay): void
}

class File {
-type: String
-data: byte[]
}

class TextFile extends File implements FileDisplay {


+display(): void
}

class ImageFile extends File implements FileDisplay {


+display(): void
}

class AudioFile extends File implements FileDisplay {


+display(): void
}
@enduml
Open/Closed Principle (OCP):
Make the FileManager open for extension (for handling new file types) but closed for modification.

@startuml
interface FileDisplay {
+display(): void
}

interface FileProcessor {
+processFile(file: FileDisplay): void
}

class FileManager implements FileProcessor {


+processFile(file: FileDisplay): void
}

class TextFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class ImageFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class AudioFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}
@enduml

Liskov Substitution Principle (LSP):


Ensure that TextFile, ImageFile, and AudioFile can be substituted for FileDisplay and FileProcessor.

@startuml
interface FileDisplay {
+display(): void
}

interface FileProcessor {
+processFile(file: FileDisplay): void
}

class FileManager implements FileProcessor {


+processFile(file: FileDisplay): void
}

class TextFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class ImageFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class AudioFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}
@enduml
Interface Segregation Principle (ISP):
Separate interfaces for FileDisplay and FileProcessor.
@startuml
interface FileDisplay {
+display(): void
}

interface FileProcessor {
+processFile(file: FileDisplay): void
}

class FileManager implements FileProcessor {


+processFile(file: FileDisplay): void
}

class TextFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class ImageFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class AudioFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}
@enduml
Dependency Inversion Principle (DIP):
Depend on abstractions (FileDisplay and FileProcessor) rather than concrete implementations.

@startuml
interface FileDisplay {
+display(): void
}

interface FileProcessor {
+processFile(file: FileDisplay): void
}

class FileManager implements FileProcessor {


+processFile(file: FileDisplay): void
}

class TextFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class ImageFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}

class AudioFile implements FileDisplay, FileProcessor {


+display(): void
+processFile(file: FileDisplay): void
}
@enduml
This refactoring adheres to SOLID principles, making the code more modular, maintainable, and extensible. Each
class or interface now has a single responsibility, and dependencies are inverted to depend on abstractions.

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy