S.O.L.I.D. Principles with Clojure and Python

The S.O.L.I.D. Principles that have been promoted by Robert C. Martin provide an
excellent guideline for building software that will be easy to maintain in the
long term.

Single Responsibility Principle

It can often be tempting to handle several different operations in a single
class or function, but making changes to this later on could easily cause
difficulty as the outcomes of every existing use case would have to be
considered. By restricting them to only one job, you can still create the
desired outcome by composing them together.

;; .. we can split them out into seperate functions;; and compose them together(defncancel[event];; ..)(defnnotify[event];; ..)

(notify(cancel{:type:dinner}))

Open Closed Principle

The above example neglects to consider the maintainability of this cancel
function, and others like it. Adding a new type of event would involve adding a
new case inside of the ‘cancel’ function, and inside any others, (for example a
‘book’ function).

The open/closed principle emphasises that you should be able to add new
entities, but not modify existing ones. Clojure has an excellent means of
doing this through either protocols or multimethods:

;; Instead of handling each case inside of a single function (that has to be;; modified in order to add new cases):(defncancel[event](case(:typeevent):talk;; ..:fun-run;; ..:dinner;; ..))

;; We can define a multimethod that can be extended on a case by case basis;; events-library-namespace.events(defmulticancel:type)(defmethodcancel:event[event];;..)(defmethodcancel:fun-run[event];;..)

;; This module can now be extended from another namespace, for example if it was;; included from another library:;; another-namespace.events(defmethodcancel:dinner[event];;..)

Liskov Substitution Principle

According to L.S.P. when making a subclass it should be possible to change
instances of them with instances of the parent class.

One of the best explanations of this that I’ve found is here:
http://maksimivanov.com/posts/liskov-substitution-principle, which uses the
example of a Duck base class that implements a ‘quack’ method, and a
MechanicalDuck subclass that implements it, but requires a battery property to
be defined. The outcomes of calling this method on an instance of each class
would be different based on the state of the battery property, and would
therefore mean that they could not be interchanged while maintaining the same
behaviour.

A classic example of this is with a Square class extending a Rectangle. Since
a square is just a rectangle where the width is the same as the height, you
could implement methods for setting the width and height that would automatically
set the height/width:

classRectangle:

defset_height(self,height):self.height=height;

defset_width(self,width):self.width=width;

classSquare(Rectangle):

defset_height(self,height):self.height=height;self.width=width;

defset_width(self,width):self.height=height;self.width=width;

But this could confuse someone calling one of the methods on the square, as when
calling set_height they would not be expecting the width to be changed and
vice-versa.

A better abstraction might be to have a more generic Shape class that implements
a set_dimensions method:

Interface Segregation Principle

When defining a class, you only really want the methods that that particular
class will need. An AdminUser class that extends a User class might need a login
method, whereas an AnonymousUser class would not. An INamed interface could
therefore be defined that provides the change-name method for only the AdminUser
class to inherit, while the AnonymousUser class would not require it at all.
This principle mostly applies to typed languages, but in Clojure an equivalent
can be found in Protocols:

;; Rather than bundling all of the methods into the IUser protocol:(defprotocolIUser(change-language[user])(change-name[user]))

;; Which would mean that the anonymous user would also have to implement a;; change-name method, which doesn't make sense when you're anonymous:(defrecordAnonymousUser[user]IUser(change-language[language](assocuser:languagelanguage))(change-name[name](assocuser:namename)))

;; Instead a INamed protocol can be defined that only the AdminUser;; needs to implement(defprotocolIUser(change-language[language]))

Dependency Inversion Principle

It can be easy to define modules that depend on a lower level module leading to
them to be tightly coupled to a specific implementation. Clojure provides some
built-in options for avoiding this coupling, through features such as protocols
and multimethods, but other languages can also achieve this through solutions
such as Dependency Injection, such as by passing higher order functions to
handle the lower-level logic.

;; Instead of calling a specific function per type(defnvalidate-move-pawn[xy];; ..)

(defnvalidate-move-bishop[xy];; ..)

(defnvalidate-move-queen[xy];; ..)

;; .. which would require the modules that are calling them;; to know about them specifically, making the code less flexible:(map#(case(:type%):pawn(validate-move-pawn):bishop(validate-move-bishop):queen(validate-move-queen))pieces)

;; It's preferable to keep the lower level code seperated out, so that;; it doesn't have to know about the implementation, for instance through;; with a Protocol:

(defprotocolIPiece(validate-move[piece]))

(defrecordPawn[piece]IPiece(validate-move[xy];; ..))

(defrecordBishop[piece]IPiece(validate-move[xy];; ..))

(defrecordQueen[piece]IPiece(validate-move[xy];; ..))

(defpieces[(Pawn.)(Queen.)(Bishop.)]);; Note there is no need for any type checks(map#(validate-move23)pieces)