-Robert C. Martin aka Uncle Bob
The Single Responsibility Principle states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
Low Coupling but high Cohesion!
class Book
{
string GetTitle()
{
return "Solid is very ghard";
}
string GetAuthor()
{
return "Supperrrrrrrrr Sommeerrrrrrrrr";
}
void TurnPage()
{
// Keep calm and turn the page !
}
void PrintCurrentPage()
{
Console.WriteLine("current page content");
}
}
class Book
{
string GetTitle()
{
return "Solid is very ghard";
}
string GetAuthor()
{
return "Supperrrrrrrrr Sommeerrrrrrrrr";
}
void TurnPage()
{
// The next chapter doesn't start until you turn the page !
}
string PrintCurrentPage()
{
return "current page content";
}
}
interface IPrinter
{
void PrintPage(string page);
}
class ConsolePrinter : IPrinter
{
public void PrintPage(string page)
{
Console.WriteLine(page);
}
}
class HtmlConsolePrinter : IPrinter
{
public void PrintPage(string page)
{
Console.WriteLine("<div style='single-page'>" + page + "</div>");
}
}
The Open / Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Open to Extension
New behavior can be added in the future
Closed to Modification
Changes to source or binary code are not required
Parameters (Procedural Programming)
Child types override behavior of a base class (or interface)
Client code depends on abstraction
public class Launcher
{
public bool LaunchMissile()
{
Console.WriteLine("Missile launched");
return true;
}
}
public class SufraceToAirMissileLauncher:Launcher
{
}
static void Main(string[] args)
{
var staLauncher = new SufraceToAirMissileLauncher();
var isLaunched = staLauncher.LaunchMissile();
}
public class Launcher
{
public bool LaunchMissile()
{
Console.WriteLine("Missile launched");
return true;
}
}
public class SufraceToAirMissileLauncher
{
private Launcher launcher = new Launcher();
public bool LaunchMissile()
{
return launcher.LaunchMissile();
}
}
static void Main(string[] args)
{
var staLauncher = new SufraceToAirMissileLauncher();
var isLaunched = staLauncher.LaunchMissile();
}
Experience Tells You!
Otherwise – “Fool me once, shame on you; fool me twice, shame on me”
Remember TANSTAAFL!
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
}
public class AreaCalculator
{
public double Area(Rectangle[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Width*shape.Height;
}
return area;
}
}
public double Area(object[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle)
{
Rectangle rectangle = (Rectangle) shape;
area += rectangle.Width*rectangle.Height;
}
else
{
Circle circle = (Circle)shape;
area += circle.Radius * circle.Radius * Math.PI;
}
}
return area;
}
public abstract class Shape
{
public abstract double Area();
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width * Height;
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Radius * Radius * Math.PI;
}
}
public double Area(Shape[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Area();
}
return area;
}
The Liskov Substitution Principle states that Subtypes must be substitutable for their base types.
Child classes should never break the parent class' type definitions.
Example:
Shape -> Rectangle -> Square
LSP violation. Square is not substitutable by rectangle..
Better: Shape -> Rectangle & Shape -> Square
Child classes must not:
And in general must not require calling code to know they are different from their base type.
foreach (var emp in Employees) {
if (emp is Manager) {
_printer.PrintManager(emp as Manager);
} else {
_printer.PrintEmployee(emp);
}
}
public abstract class Base
{
public abstract void Method1();
public abstract void Method2();
}
public class Child : Base
{
public override void Method1()
{
throw new NotImplementedException();
}
public override void Method2()
{
// do stuff
}
}
// Child does not need Method1 so better to use different base type. -> LSP
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
Consider Refactoring to a new Base Class
The Interface Segregation Principle states that Clients should not be forced to depend on methods they do ot use.
Interface Segregation violations result in classes that depend on things they do not need, increasing coupling and reducing flexibility and maintainability
public override string ResetPassword (string username, string answer)
{
throw new NotImplementedException();
}
Unimplemented interface methods:
Remember this also violates Liskov Substitution Principle!
Once there is pain
If you find yourself depending on a “fat” interface you own
If you find “fat” interfaces are problematic but you do not own them
public interface IPersist
{
bool Save(ReservationDetails reservation);
}
public class ReservationLog : IPersist
{
public bool Save(ReservationDetails reservation)
{
// imagine some code here to perist data to database...
return true;
}
public void Log(ReservationDetails reservation)
{
// imagine some code here to log to a log repository
}
public void SendNotification(ReservationDetails reservation)
{
// imagine some code here that notifies about logging
}
}
public interface IPersist
{
bool Save(ReservationDetails reservation);
void Log(ReservationDetails reservation);
void SendNotification(ReservationDetails reservation);
}
public class ReservationDatabase : IPersist
{
public bool Save(ReservationDetails reservation)
{
// imagine some code here to persist data to database...
return true;
}
public void Log(ReservationDetails reservation)
{
throw new NotImplementedException();
}
public void SendNotification(ReservationDetails reservation)
{
throw new NotImplementedException();
}
}
public interface ILog
{
void Log(ReservationDetails reservation);
void SendNotification(ReservationDetails reservation);
}
public interface IPersist
{
bool Save(ReservationDetails reservation);
}
public class ReservationLog : IPersist, ILog
public class ReservationDatabase : IPersist
High-level modules should not depend on low-level modules. Both should depend on abstractions
Abstractions should not depend on details. Details should depend on abstractions.
When DIP is violated:
Difficult to test
public class HelloWorldHidden
{
public string Hello(string name)
{
if (DateTime.Now.Hour < 12) return "Good morning, " + name;
if (DateTime.Now.Hour < 18) return "Good afternoon, " + name;
return "Good evening, " + name;
}
}
public class HelloWorldExplicit
{
private readonly DateTime _timeOfGreeting;
public HelloWorldExplicit(DateTime timeOfGreeting)
{
_timeOfGreeting = timeOfGreeting;
}
public string Hello(string name)
{
if (_timeOfGreeting.Hour < 12) return "Good morning, " + name;
if (_timeOfGreeting.Hour < 18) return "Good afternoon, " + name;
return "Good evening, " + name;
}
}
Classes Should Declare What They Need
Use of new keyword
Use of static methods/properties
foreach(var item in cart.Items)
{
try
{
var inventorySystem = new InventorySystem();
inventorySystem.Reserve(item.Sku, item.Quantity);
}
}
message.Subject = "Your order placed on " + DateTime.Now.ToString();
DataAccess.SaveCustomer(myCustomer);