0% found this document useful (0 votes)
18 views25 pages

SOLID

Uploaded by

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

SOLID

Uploaded by

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

Readings:

S.O.L.I.D. Principles of Object-Oriented Programming in C# (educative.io)

Single Responsibility Principle in C#: A Comprehensive Guide | by Anto Semeraro | DevPatterns Hub |
Apr, 2023 | Medium

Single Responsibility Principle


Objective
The objective of this lab exercise is to practice implementing the Single
Responsibility Principle (SRP) in a simple C# program.

Requirements
For this exercise, you will need:

 A text editor or an IDE that supports C# development


 A C# compiler, such as the Microsoft .NET compiler

Instructions
1. Start by creating a new C# console application.
2. Create a class called Order that represents an order in a store. This class
should have the following responsibilities:
 Calculate the total price of the order
 Save the order to a database
 Send an email confirmation to the customer
3. Write code to implement each of these responsibilities in the Order class.
4. Test the Order class to ensure that it works as expected.
5. Refactor the Order class to separate its responsibilities into three separate
classes:
 OrderCalculator for calculating the total price of the order
 OrderDatabase for saving the order to the database
 OrderEmail for sending the email confirmation
6. Update the Order class to use these three new classes to perform its
responsibilities.
7. Test the Order class again to ensure that it still works as expected.

using System;
using System.Collections.Generic;

namespace SingleResponsibilityPrinciple
{
class Program
{
static void Main(string[] args)
{
// create an order
Order order = new Order();

// add some products to the order


order.Products.Add(new Product("Product 1", 10.0m));
order.Products.Add(new Product("Product 2", 20.0m));
order.Products.Add(new Product("Product 3", 30.0m));

// calculate the total price of the order


decimal totalPrice = order.CalculateTotalPrice();

// save the order to the database


order.SaveToDatabase();

// send an email confirmation to the customer


order.SendEmailConfirmation();

Console.WriteLine("Order successfully processed!");


}
}

public class Product


{
public string Name { get; set; }
public decimal Price { get; set; }

public Product(string name, decimal price)


{
Name = name;
Price = price;
}
}

public class Order


{
public List<Product> Products { get; set; }

public Order()
{
Products = new List<Product>();
}

public decimal CalculateTotalPrice()


{
decimal totalPrice = 0.0m;

foreach (Product product in Products)


{
totalPrice += product.Price;
}

return totalPrice;
}

public void SaveToDatabase()


{
Console.WriteLine("Order saved to database.");
}

public void SendEmailConfirmation()


{
Console.WriteLine("Email confirmation sent to customer.");
}
}
}

Liskov Substitution Principle


Objective
The objective of this lab exercise is to practice implementing the Liskov
Substitution Principle (LSP) in a simple C# program.

Requirements
For this exercise, you will need:

 A text editor or an IDE that supports C# development


 A C# compiler, such as the Microsoft .NET compiler

Instructions
1. Start by creating a new C# console application.
2. Create a base class called Animal that represents an animal. This class should have
the following properties:
 Name (string) - the name of the animal
 Age (int) - the age of the animal
3. Create a Mammal class that inherits from the Animal class. This class should have the
following properties:
Nursing (bool) - indicates whether or not the mammal nurses its young

4. Create a Dog class that inherits from the Mammal class. This class should have the
following properties:
 Breed (string) - the breed of the dog
5. Create a Cat class that also inherits from the Mammal class. This class should have
the following properties:
 Declawed (bool) - indicates whether or not the cat has been declawed
6. Write code to implement each of these classes.
7. Create a method called MakeSound in the Animal class that outputs the sound that
the animal makes to the console.
8. Create a method called Bark in the Dog class that outputs the sound that the dog
makes to the console.
9. Create a method called Meow in the Cat class that outputs the sound that the cat
makes to the console.
10.Create a new List<Animal> object and add one Dog object and one Cat object to the
list.
11.Iterate through the list and call the MakeSound method on each object. Ensure that
the correct sound is output for each animal.
12.Refactor the code to remove the Bark and Meow methods from the Dog and Cat
classes, respectively. Instead, create an interface called ISoundMaker that has a
method called MakeSound.
13.Make the Dog and Cat classes implement the ISoundMaker interface by
implementing the MakeSound method.
14.Test the code again to ensure that the correct sound is output for each animal.

using System;

using System.Collections.Generic;

namespace LiskovSubstitutionPrinciple

class Program

static void Main(string[] args)

List<Animal> animals = new List<Animal>();

animals.Add(new Dog("Fido", 2, "Golden Retriever"));

animals.Add(new Cat("Fluffy", 4, true));


foreach (Animal animal in animals)

animal.MakeSound();

public class Animal

public string Name { get; set; }

public int Age { get; set; }

public virtual void MakeSound()

Console.WriteLine("Animal sound");

public class Mammal : Animal

public bool Nursing { get; set; }

public class Dog : Mammal

public string Breed { get; set; }

public override void MakeSound()

{
Console.WriteLine("Woof!");

public class Cat : Mammal

public bool Declawed { get; set; }

public override void MakeSound()

Console.WriteLine("Meow!");

public interface ISoundMaker

void MakeSound();

}
Applying the Open/Closed Principle
In this exercise, you will practice applying the Open/Closed Principle (OCP) to
a simple software project.

Problem description
You are working on a project for a restaurant that wants to create a software
system for managing its menu. The menu consists of various items, such as
appetizers, entrees, and desserts, each with a name, description, and price.

The restaurant wants to be able to easily update the menu by adding or


removing items, but it also wants to ensure that the software is maintainable
and extensible. Specifically, the restaurant wants to be able to add new
types of items to the menu without having to modify the existing code.

Initial design
The initial design for the menu management system consists of a MenuItem
class that represents an item on the menu, as well as several derived classes
for specific types of menu items, such as Appetizer, Entree, and Dessert.
Each derived class adds its own properties to the base MenuItem class.

public class MenuItem


{
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}

public class Appetizer : MenuItem


{
public bool IsVegan { get; set; }
}

public class Entree : MenuItem


{
public string MeatType { get; set; }
}

public class Dessert : MenuItem


{
public bool IsGlutenFree { get; set; }
}

The restaurant uses a Menu class to manage the collection of menu items. It
has methods for adding and removing items, as well as a method for
displaying the current menu.

public class Menu


{
private List<MenuItem> items = new List<MenuItem>();

public void AddItem(MenuItem item)


{
items.Add(item);
}

public void RemoveItem(MenuItem item)


{
items.Remove(item);
}

public void DisplayMenu()


{
foreach (MenuItem item in items)
{
Console.WriteLine("{0} - {1} - ${2}", item.Name, item.Description,
item.Price);
}
}
}
Exercise instructions
1. Refactor the Menu class to be open for extension but closed for modification.
Specifically, modify the AddItem and RemoveItem methods so that they can
accept any type of MenuItem object, without requiring changes to the existing
code.
2. Implement two new classes that derive from MenuItem: Drink and SideDish.
Each new class should add its own properties to the base MenuItem class.
3. Modify the Menu class to allow for the addition and removal of Drink and
SideDish objects, without requiring changes to the existing code.
4. Test the modified Menu class by creating instances of Appetizer, Entree,
Dessert, Drink, and SideDish objects, adding them to the menu, and
displaying the menu.

Expected outcomes
After completing this exercise, you should have a Menu class that is open for
extension but closed for modification. You should also have two new classes,
Drink and SideDish, that can be added to the menu without requiring
changes to the existing code.

This exercise demonstrates how the Open/Closed Principle can be applied by


designing software components that are open for extension but closed for
modification, allowing for easy addition of new functionality without breaking
existing code.

In this lab exercise, we applied the Open/Closed Principle (OCP) to a simple


software project that manages a restaurant menu. We started with an initial
design that had a MenuItem class and several derived classes for specific
types of menu items, such as Appetizer, Entree, and Dessert. We also had a
Menu class that managed the collection of menu items.

To make the Menu class open for extension but closed for modification, we
modified the AddItem and RemoveItem methods so that they can accept any
type of MenuItem object, without requiring changes to the existing code. We
achieved this by changing the method parameters from MenuItem to object,
which allows any type of object to be passed in.

public class Menu

private List<MenuItem> items = new List<MenuItem>();


public void AddItem(object item)

if (item is MenuItem menuItem)

items.Add(menuItem);

public void RemoveItem(object item)

if (item is MenuItem menuItem)

items.Remove(menuItem);

public void DisplayMenu()

foreach (MenuItem item in items)

Console.WriteLine("{0} - {1} - ${2}", item.Name, item.Description, item.Price);

Next, we implemented two new classes that derive from MenuItem: Drink and
SideDish. Each new class added its own properties to the base MenuItem class.
public class Drink : MenuItem

public string Size { get; set; }

public class SideDish : MenuItem

public bool IsVegetarian { get; set; }

Finally, we modified the Menu class to allow for the addition and removal of Drink
and SideDish objects, without requiring changes to the existing code. We achieved
this by calling the AddItem and RemoveItem methods with instances of the new
classes.

Menu menu = new Menu();

menu.AddItem(new Appetizer { Name = "Spinach Dip", Description = "A creamy spinach dip served with
pita bread.", Price = 6.99m, IsVegan = false });

menu.AddItem(new Entree { Name = "Chicken Alfredo", Description = "A classic pasta dish with creamy
alfredo sauce and grilled chicken.", Price = 12.99m, MeatType = "Chicken" });

menu.AddItem(new Dessert { Name = "Cheesecake", Description = "A rich and creamy cheesecake
topped with fresh berries.", Price = 7.99m, IsGlutenFree = false });

menu.AddItem(new Drink { Name = "Soda", Description = "A refreshing carbonated beverage.", Price =
2.99m, Size = "Medium" });

menu.AddItem(new SideDish { Name = "Fries", Description = "Crispy golden fries.", Price = 3.99m,
IsVegetarian = true });

menu.DisplayMenu();

When we ran the program, we were able to see the complete menu,
including the newly added Drink and SideDish objects, without modifying the
existing code.
This exercise demonstrated how the Open/Closed Principle can be applied to
design software components that are open for extension but closed for
modification. By allowing new types of menu items to be added to the Menu
class without requiring changes to the existing code, we were able to make
the software more maintain
Applying the Interface Segregation Principle
In this lab exercise, we will be applying the Interface Segregation Principle
(ISP) to a software project that simulates an online store.

We start with an initial design that has an ICart interface and a Cart class
that implements it:

public interface ICart


{
void AddItem(Product product);
void RemoveItem(Product product);
decimal CalculateTotal();
}

public class Cart : ICart


{
private List<Product> items = new List<Product>();

public void AddItem(Product product)


{
items.Add(product);
}

public void RemoveItem(Product product)


{
items.Remove(product);
}
public decimal CalculateTotal()
{
decimal total = 0;
foreach (Product product in items)
{
total += product.Price;
}
return total;
}
}

Next, we add a new requirement to the project: customers should be able to view
the contents of their cart before checkout. We decide to create a new interface
called ICartViewer that has a single method ViewCart:

public interface ICartViewer


{
void ViewCart();
}

We then modify the Cart class to implement the ICartViewer interface and add a
new ViewCart method:

public class Cart : ICart, ICartViewer


{
private List<Product> items = new List<Product>();

public void AddItem(Product product)


{
items.Add(product);
}
public void RemoveItem(Product product)
{
items.Remove(product);
}

public decimal CalculateTotal()


{
decimal total = 0;
foreach (Product product in items)
{
total += product.Price;
}
return total;
}

public void ViewCart()


{
Console.WriteLine("Your cart contains:");
foreach (Product product in items)
{
Console.WriteLine("- {0} (${1})", product.Name, product.Price);
}
}
}

However, this design violates the Interface Segregation Principle because clients
that only need to add or remove items from the cart are forced to depend on the
ViewCart method, which they don't need. To fix this, we split the ICart interface
into two smaller interfaces: ICartWritable and ICartReadable. The ICartWritable
interface includes the AddItem and RemoveItem methods, and the ICartReadable
interface includes the CalculateTotal method. The Cart class then implements
both interfaces.
public interface ICartWritable
{
void AddItem(Product product);
void RemoveItem(Product product);
}

public interface ICartReadable


{
decimal CalculateTotal();
}

public class Cart : ICartWritable, ICartReadable, ICartViewer


{
private List<Product> items = new List<Product>();

public void AddItem(Product product)


{
items.Add(product);
}

public void RemoveItem(Product product)


{
items.Remove(product);
}

public decimal CalculateTotal()


{
decimal total = 0;
foreach (Product product in items)
{
total += product.Price;
}
return total;
}

public void ViewCart()


{
Console.WriteLine("Your cart contains:");
foreach (Product product in items)
{
Console.WriteLine("- {0} (${1})", product.Name, product.Price);
}
}
}

public interface ICartWritable


{
void AddItem(Product product);
void RemoveItem(Product product);
}

public interface ICartReadable


{
decimal CalculateTotal();
}

public class Cart : ICartWritable, ICartReadable, ICartViewer


{
private List<Product> items = new List<Product>();
public void AddItem(Product product)
{
items.Add(product);
}

public void RemoveItem(Product product)


{
items.Remove(product);
}

public decimal CalculateTotal()


{
decimal total = 0;
foreach (Product product in items)
{
total += product.Price;
}
return total;
}

public void ViewCart()


{
Console.WriteLine("Your cart contains:");
foreach (Product product in items)
{
Console.WriteLine("- {0} (${1})", product.Name, product.Price);
}
}
}
Now, clients can depend on either ICartWritable or `ICartReadable as
needed, without being forced to depend on theViewCart` method.

Finally, we create a new class called CartViewer that implements the


ICartViewer interface. This class has a reference to an instance of Cart and
uses it to display the contents of the cart:

public class CartViewer : ICartViewer


{
private Cart cart;

public CartViewer(Cart cart)


{
this.cart = cart;
}

public void ViewCart()


{
cart.ViewCart();
}
}
With this design, clients can now depend on either ICartWritable or ICartReadable
or ICartViewer as needed, without being forced to depend on methods they don't
need. This is a good example of the Interface Segregation Principle in action.

Conclusion
In this lab exercise, we applied the Interface Segregation Principle (ISP) to a
software project that simulates an online store. By splitting the ICart
interface into smaller interfaces and creating separate classes that
implement them, we were able to create a more flexible and maintainable
design that respects the needs of different clients.
Dependency Inversion Principle
n this lab exercise, we will apply the Dependency Inversion Principle (DIP) to
a simple console application that simulates a weather station. The
application reads temperature data from a sensor and displays it on the
console.

Step 1: Define the high-level modules


First, we need to define the high-level modules of our application. In this
case, we have two main modules:

 The Sensor module, which reads temperature data from the sensor.
 The Display module, which displays the temperature data on the console.

We want these two modules to be loosely coupled, so that we can change or


replace them without affecting the other module.

Step 2: Define the abstractions


Next, we need to define the abstractions that will allow us to achieve loose
coupling between the two modules. We will create two interfaces:

 The ITemperatureSensor interface, which defines a method to read temperature


data.
 The ITemperatureDisplay interface, which defines a method to display temperature
data.

public interface ITemperatureSensor


{
double ReadTemperature();
}

public interface ITemperatureDisplay


{
void DisplayTemperature(double temperature);
}

Step 3: Implement the low-level modules


Now, we need to implement the low-level modules that will use the
abstractions we defined earlier. We will create two classes:
 The Thermometer class, which implements the ITemperatureSensor interface and
reads temperature data from a fake sensor.
 The ConsoleDisplay class, which implements the ITemperatureDisplay interface
and displays temperature data on the console.

public class Thermometer : ITemperatureSensor


{
public double ReadTemperature()
{
// read temperature from a fake sensor
return 25.0;
}
}

public class ConsoleDisplay : ITemperatureDisplay


{
public void DisplayTemperature(double temperature)
{
Console.WriteLine($"The temperature is {temperature} degrees Celsius.");
}
}

Step 4: Implement the high-level module


Finally, we need to implement the high-level module that uses the
abstractions we defined earlier to communicate with the low-level modules.
We will create a class called WeatherStation that takes instances of
ITemperatureSensor and ITemperatureDisplay in its constructor:

public class WeatherStation


{
private ITemperatureSensor temperatureSensor;
private ITemperatureDisplay temperatureDisplay;

public WeatherStation(ITemperatureSensor temperatureSensor, ITemperatureDisplay


temperatureDisplay)
{
this.temperatureSensor = temperatureSensor;
this.temperatureDisplay = temperatureDisplay;
}

public void Run()


{
// read temperature from the sensor
double temperature = temperatureSensor.ReadTemperature();
// display temperature on the console
temperatureDisplay.DisplayTemperature(temperature);
}
}

Note that the WeatherStation class does not depend on the Thermometer or
ConsoleDisplay classes directly. Instead, it depends on the abstractions
ITemperatureSensor and ITemperatureDisplay. This allows us to easily swap
out the low-level modules without affecting the high-level module.

Conclusion
In this lab exercise, we applied the Dependency Inversion Principle (DIP) to a
simple console application that simulates a weather station. By defining
abstractions for the high-level module to depend on, and implementing those
abstractions in separate low-level modules, we achieved loose coupling
between the modules and made the application more flexible and
maintainable.

You might also like