The S.O.L.I.D. Principles of Class Design
The
S.O.L.I.D. principles
seem to be the least common denominator of creating great classes; even
before Design Patterns. I recommend taking some time to really think
about each of them and how you can apply them. Let's dive in, one by
one.
The Single Responsibility Principle
There should never be more than one reason for a class to change.
Basically, this means that your classes should exist for one purpose
only. For example, let's say you are creating a class to represent a
SalesOrder
.
You would not want that class to save to the database, as well as
export an XML-based receipt. Why? Well if later on down the road, you
want to change database type (or if you want to change your XML schema),
you're allowing one responsibility's changes to possibly alter another.
Responsibility is the heart of this principle, so to rephrase there
should never be more than one responsibility per class.
The Open Closed Principle
Software entities (classes, modules, functions, etc.) should be open
for extension, but closed for modification. At first, this seems to be
contradictory: how can you make an object behave differently without
modifying it? The answer: by using abstractions, or by placing
behavior(responsibility) in derivative classes. In other words, by
creating base classes with override-able functions, we are able to
create new classes that do the same thing differently without changing
the base functionality. Further, if properties of the abstracted class
need to be compared or organized together, another abstraction should
handle this. This is the basis of the "keep all object variables
private" argument.
The Liskov Substitution Principle
Functions that use pointers or references to base classes must be
able to use objects of derived classes without knowing it. In other
words, if you are calling a method defined at a base class upon an
abstracted class, the function must be implemented properly on the
subtype class. Or, "when using an object through its base class
interface, [the] derived object must not expect such users to obey
preconditions that are stronger than those required by the base class."
The ever-popular illustration of this is the
square-rectangle example. Turns out a square is not a rectangle, at least behavior-wise.
The Dependency Inversion Principle
Depend on abstractions, not on concretions or High level modules
should not depend upon low level modules. Both should depend upon
abstractions. Abstractions should not depend upon details. Details
should depend upon abstractions. (I like the first explanation the
best.) This is very closely related to the open closed principle we
discussed earlier. By passing dependencies (such as connectors to
devices, storage) to classes as abstractions, you remove the need to
program dependency specific. Here's an example: an
Employee
class that needs to be able to be persisted to XML and a database. If we placed
ToXML()
and
ToDB()
functions
in the class, we'd be violating the single responsibility principle. If
we created a function that took a value that represented whether to
print to XML or to DB, we'd be hard-coding a set of devices and thus be
violating the open closed principle. The best way to do this would be
to:
- Create an
abstract
class (named DataWriter
, perhaps) that can be inherited from for XML (XMLDataWriter
) or DB (DbDataWriter
) Saving, and then
- Create a class (named
EmployeeWriter
) that would expose an Output(DataWriter saveMethod)
that accepts a dependency as an argument. See how the Output
method
is dependent upon the abstractions just as the output types are? The
dependencies have been inverted. Now we can create new types of ways for
Employee
data to be written, perhaps via HTTP/HTTPS by
creating abstractions, and without modifying any of our previous code!
No rigidity--the desired outcome.
The Interface Segregation Principle
Clients should not be forced to depend upon interfaces that they do
not use. My favorite version of this is written as "when a client
depends upon a class that contains interfaces that the client does not
use, but that other clients do use, then that client will be affected by
the changes that those other clients force upon the class." Kinda
sounds like the inheritance specific single responsibility principle.
No comments:
Post a Comment