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:
- Multiple inheritance of interface
- 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.
Comments