Course: CSCI 1260

These aren’t required—but they’re good design.

Single Responsibility Principle

  • Classes should only have one responsibility
// Without SRP
class Employee {
	public string Name { get; set; }
	public double Salary { get; set; }
 
	public void SaveToDatabase() { }
	public void GenerateReport() { }
}
 
// With SRP
public class Employee {
	public string Name { get; set; }
	public double Salary { get; set; }
}
 
public class EmployeeRepository {
	public void SaveToDatabase(Employee employee) { }
}
 
public class ReportGenerator {
	public void GeneratReport(Employee employee) { }
}

Open/Closed Principle

  • A class should be open for extension but closed for modification
  • Essentially, using polymorphism correctly
// Without OCP
public class AreaCalculator {
	public double CalculateArea(object shape) => shape switch
		Circle c => Math.PI * c.Radius * c.Radius,
		Rectangle r => r.Width * r.Height,
		_ => throw new NotSupportedException("Invalid shape")
}
 
// With OCP
public interface IShape {
	double CalculateArea();
}
 
public class Circle : IShape {
	public double Radius { get; set; }
	public double CalculateArea() => Math.PI * Radius * Radius;
}
 
public class Rectangle : IShape {
	public double Width { get; set; }
	public double Height { get; set; }
	public double CalculateArea() => Width * Height;
}
 
public class AreaCalculator {
	public double CalculateArea(IShape shape) => shape.CalculateArea();
}

Liskov Substitution Principle

  • A subclass should be substitutable for its base class without affecting correctness
// Without LSP
public class Rectangle {
	public virtual double Width { get; set; }
	public virtual double Height { get; set; }
	public double GetArea() => Width * Height;
}
 
public class Square : Rectangle {
	public override double Width {
		set { base.Width = base.Height = value; } 
	}
	
	public override double Height { 
		set { base.Width = base.Height = value; }
	 }
}
 
// With LSP
public interface IShape {
	double GetArea();
}
 
public class Rectangle : IShape {
	public double Width { get; set; }
	public double Height { get; set; }
	public double GetArea() => Width * Height;
}
 
public class Square : IShape {
	public double Side { get; set; }
	public double GetArea() => Side * Side;
}

Interface Segregation Principle

  • A class should not be forced to implement an interface it does not use
// Without ISP
public interface IWorker {
	void Work();
	void Eat();
}
 
public class Robot : IWorker {
	public void Work() { }
	public void Eat() {
		throw new NotImplementedException() // robots can't eat
	}
}
 
// With ISP
public interface IWorkable {
	void Work();
}
 
public interface IEatable {
	void Eat();
}
 
public class Human : IWorkable, IEatable {
	public void Work() { }
	public void Eat() { }
}
 
public class Robot : IWorkable {
	public void Work() { }
}

Dependency Inversion Principle

  • High-level modules should not depend on low-level modules
  • They should depend on abstractions
// Without DIP
public class EmailService {
	public void SendEmail(string message) { }
}
 
public class Notification {
	private EmailService _emailService = new EmailService();
	public void Notify(string message) {
		_emailService.SendEmail(message);
	}
}
 
// With DIP
public interface IMessageService {
	void SendMessage(string message);
}
 
public class EmailService : IMessageService {
	public void SendMessage(string message) { }
}
 
public class Notification(IMessageService messageService) {
	private readonly IMessageService _messageService = messageService;
 
	public void Notify(string message) {
		_messageService.SendMessage(message);
	}
}