SOLID
Tim Sommer
-Robert C. Martin aka Uncle Bob
Single Responsibility Principle
What?
The Single Responsibility Principle states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
What is a responsibility?
- “A reason to change”
- A difference in usage scenarios from the client’s perspective
Cohesion & Coupling
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>");
}
}
Open Closed Principle
What?
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
How?
Parameters (Procedural Programming)
- Allow client to control behavior specifics via a parameter
- Combined with delegates/lambda, can be very powerful approach
- Inheritance / Template Method Pattern
Child types override behavior of a base class (or interface)
- Composition / Strategy Pattern
Client code depends on abstraction
- Provides a “plug in” model
- Implementations utilize Inheritance; Client utilizes Composition
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();
}
Composition over inheritance?
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();
}
Composition over inheritance?
When?
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;
}
Liskov Substitution Principle
What?
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
Substitutability
Child classes must not:
- Remove base class behavior
- Violate base class invariants
And in general must not require calling code to know they are different from their base type.
You can smell it !
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
When?
- If you notice obvious smells like those shown
-
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
Tips !
Consider Refactoring to a new Base Class
- Given two classes that share a lot of behavior but are not substitutable...
- Create a third class that both can derive from
- Ensure substitutability is retained between each class and the new base
Interface Segragation Principle
What?
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
It Smells !
public override string ResetPassword (string username, string answer)
{
throw new NotImplementedException();
}
Unimplemented interface methods:
Remember this also violates Liskov Substitution Principle!
When?
Once there is pain
If you find yourself depending on a “fat” interface you own
- If there is no pain, there’s no problem to address.
- Create a smaller interface with just what you need
- Have the fat interface implement your new interface
- Reference the new interface with your code
If you find “fat” interfaces are problematic but you do not own them
- Create a smaller interface with just what you need
- Implement this interface using an Adapter that implements the full interface
Tips !
- Keep interfaces small, cohesive, and focused
- Whenever possible, let the client define the interface
- Whenever possible, package the interface with the client
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
Dependency Inversion Principle
What?
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:
- Tight coupling
- No way to change implementation details (OCP violation)
-
Difficult to test
- Framework
- Third Party Libraries
- Database
- File System
- Web Services
- System Resources (Clock)
- Configuration
- The new Keyword
- Static methods
- Thread.Sleep
- Random
Dependencies?
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
It Smells !
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);
SOLID - Qframe
By Tim Sommer
SOLID - Qframe
- 580