Sunday, April 07, 2013

DevPreview: Agile Principles Patterns and Practices Part2 II: Agile Design Principles

This post is in devpreview - skip if you're not passionate about this topic, leave comments if you are. 

I read lots, and often forget what I wrote about.  To help me remember, I’m going to take notes on my blog. Feel free to discuss what you've seen in the comments.

Terminology

Term
Definition
Entity
The "thing" to which principles are applied.
Client
An entity which consumes this entity .
Dependent
The entity on which this entity depends to function.


Summary of Principles

At 10,000 feet, design principles ensure that changes to a software entity, due to bugs or requirement changes,  have minimal impact on clients. 

Principle
Elevator Pitch
SRP: Single Responsibility
Entities should have one and only one reason to change.
OCP: Open Closed
Entities should be closed to changes, but open to extension
LSP: Liskov Substitution
Derived entity must be "substitutable" for the base entity in all regards.

DIP: Dependency Inversion
An entity should define the interfaces it requires of dependents. 
ISP: Interface Segregation
An entity who provides functionality for multiple types of clients should expose that functionality via multiple interfaces.


SRP: Single Responsibility Principle
An entity should have a single responsibility. This can be tested if the entity can only require change for a single reason.

You need to use it if:
You're changing your entity for multiple reasons (e.g. how the objects are drawn, how the objects are persisted)

Required Because Traditionally
We lumped lots of functionality into one big object.

You're over doing it if:
You've created separate classes which have never required changes.

OCP: Open Closed Principle
Changes should only occur at predefined extension points.

You're need to use it if:
Every time you change your software, the changes are all through lots of the code.

Required Because Traditionally
We didn't think about where software would require flexibility.

You're over doing it if:
You have extension points that have only a single implementation.

LSP: Liskov Substitution Principle
Derived entity must be "substitutable" for the base entity in all regards.

You need to use it if:
Derived classes break existing functionality.

Required Because Traditionally
Derived entities implemented "is-a" from a structural perspective. Inheritance needs to be "is-a" from a behavioral point of you - more appropriately named "acts-as".

Example Violation:
Square deriving from rectangle. Square is-a rectangle structurally, but no be behaviorally.  We can't substitute a square for a rectangle for example, for a rectangle - rect.Height = 3; rect.Width=7; assert(rect.Height ==3) is not true for a square.

Notes
The "key" for my understanding of this principle is that the invariants break on the base class, not on the derived class. AKA a Square can be accessed as a rectangle, but a rectangle cannot be access as a square.

IS-A is behavioral, not structural!  The better English word for this is can be substituted, or acts-as.

Unit tests against the base class will often catch LSP violations.
Another way to catch these is with pre and post conditions on base classes.

You're over doing it if:
I don't think you can over due this principle.


DIP: Dependency Inversion Principle
Entities should provide interfaces for their dependents to implement.

You need to use it if:
Your entities require changes when your dependents change.

Required Because Traditionally
We implemented entities directly on the implementations exposed by their dependents.

You're over doing it if:
You're creating interfaces for well baked objects in the base classes (e.g. String) and your software   will not be used cross platform.

Notes
This is called inversion, as traditionally, entities simply consumed the implementation provided by their dependents.  If I had my druthers would be called "dependency specification".

At the core:
  • No variable should hold a ref to a concrete class
  • No class should derive from a concrete class
  • No class should override an implemented base class method

ISP: The interface segregation principle
A class can implement many interfaces, but each interface is cohesive!

You need to use it if:
The interfaces do not ad-here to the SRP principle.

Required Because Traditionally
We used to create interfaces that didn't match individual behavior, but instead created interfaces to represent an implemented class.

You're over doing it if:
You have a bunch of single method interfaces.

Notes:
Clients should not be forced to depend on a method they do not need.

Fat classes are OK, but fat interfaces are not

Two separation methods:

  1. Multiple inheritance of interface
  2. Use an adapter to convert from type 1 to type 2 (Add example)

Even when a client depends on two interfaces implemented by the same class, specify the dependency as two interfaces.

If you expose two interfaces with similar functionality, repeat the duplication in the interfaces.  Recall the interfaces will be used by independent clients, and you don't want to force a change in a client if their needs have not changed.

No comments: