SOLID
SOLID
Single Responsibility Principle in C#: A Comprehensive Guide | by Anto Semeraro | DevPatterns Hub |
Apr, 2023 | Medium
Requirements
For this exercise, you will need:
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();
public Order()
{
Products = new List<Product>();
}
return totalPrice;
}
Requirements
For this exercise, you will need:
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
animal.MakeSound();
Console.WriteLine("Animal sound");
{
Console.WriteLine("Woof!");
Console.WriteLine("Meow!");
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.
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.
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.
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.
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.
items.Add(menuItem);
items.Remove(menuItem);
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
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.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:
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:
We then modify the Cart class to implement the ICartViewer interface and add a
new ViewCart method:
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);
}
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.
The Sensor module, which reads temperature data from the sensor.
The Display module, which displays the temperature data on the console.
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.