Fix your Clojure code: Clojure comes with design patterns (Part 2)

This post is the second in a series. You can find the first post in the series here where I covered Null Object, Singleton, Command, Observer, State, Visitor, and Iterator.

Patterns

Our story continues

Fix your Clojure code: Clojure comes with design patterns (Part 2)

Edmund, a bright, awkward young man with a masters degree from a prestigious University, loves functional programming, but his views are too pure for Suzi&aposs taste. He always drones on about how we don&apost need to think about any of those design patterns things because "that&aposs only for object-oriented programming".

However, Edmund&aposs latest project with an intern, a load testing application, was taking far too long, like most projects at StartupAI. Despite the mythical (wo)man-month, Suzi&aposs manager asks her to join the project, and she agrees.

"We see the heart of this project as state machine" Edmund says, during a code walk through.

To Suzi surprise, she sees a dense case form. "Uh oh" thinking to herself.

Next Edmund shows her the dozens of functions created to manufacture types to kick start the state machine. Of course, whenever they have to change these functions, they have to change the state machine and vise-versa. To top it all off, they&aposre using Clojure futures to generate the load tests, attempting to coordinate all the futures and their requests. Lucky for Suzi, she knows why they&aposre not progressing on the project, and why it doesn&apost work at all.

"I have an idea, why don&apost you replace this case form with a state pattern, and these functions with a builder?"  Suzi says gently presenting the idea.

"Oh, I just prefer not to do those kind of things in functional programming." He replies.

"Right, but it would decouple the state machine from the this namespace, and you can decouple your functions here from this code down here." She says while pointing with her pen at the functions on the screen from earlier, and gesturing downward at the state machine. Continuing, "And, you can ditch this whole futures thing. Just use core.async".

"What&aposs core.async?" Edmund asks no one in particular while typing &aposclojure core async&apos into the browser search bar.

"Communicating Sequential Processes in Clojure. It handles thread pooling and everything, all we have to do is dispatch work to the &aposgo loops&apos." Suzi answers.

Oh.

Builder

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

A lot of patterns become thinking about, and structuring of, the flow of higher-order functions and their composition. While the functions may seem vague, we need to keep in mind our function&aposs intent. With the builder pattern we want to delegate object (or function) construction (composition) .

When to use it

When you want to decouple the construction of a complex object from the context it&aposs used in.

Clojure analogue
Keep in mind
Sample code
;; builder
;; now we can delegate our map construction to this
;; function.
(defn build-s3-client
  [mock-creds ssl default-bucket sts]
  (let [if-fn (fn [cond f] (if cond f identity))]
    (comp
     (if-fn (not (empty? creds))
       #(let [secret (s/gen :specs/client-secret)
              access-key (s/gen :specs/access-key)]
          (merge % {:client-secret (g/generate secret)
                    :access-key (g/generate access-key)})))

     (if-fn ssl #(assoc % :ssl true))

     (if-fn (and (string? default-bucket)
                 (not (empty? default-bucket)))
       #(assoc % :default-bucket default-bucket))

     (if-fn (and (string? sts)
                 (not (empty? sts))
                 (some? (re-matches #"." sts)))
       #(assoc % :sts-grant sts)))))
   
(def s3-client 
  (build-s3-client {} false "my-bucket" "real-token"))

;; Chaining technique
(defn ->s3-client
  []
  (let [{:keys [aws-secret-key
                aws-access-key]} env]
    {:client-secret client-secret
     :access-key access-key}))

(defn with-ssl
  [m]
  (assoc m :ssl true))

(defn with-default-bucket
  [m bucket]
  (assoc m :default-bucket bucket))

(defn with-sts
  [m token]
  (assoc m :sts-grant token))

(defn with-mock-credentials
  [m]
  (let [secret (s/gen :specs/client-secret)
        access-key (s/gen :specs/access-key)]
    (merge m {:client-secret (g/generate secret)
              :access-key (g/generate access-key)})))

(defn build-s3-client
  [bucket]
  (-> (->s3-client)
      (with-ssl)
      (with-default-bucket bucket)))

(defn mock-s3-client
  [bucket]
  (-> (->s3-client)
      (with-mock-credentials)
      (with-bucket bucket)))

Chain of Responsibility

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

The structure of this pattern seems similar to the builder, but our intent for our functions is different. And, If I&aposm being honest, the only Chain of Responsibility I&aposve ever seen, even outside of Clojure, is the middleware chain in ring applications.

When to use it

When you want to decouple a request from its handler.

Clojure analogue
Keep in mind
Sample code

Ring middleware gives us the best and most prevalent example of a Chain of Responsibility in Clojure. In this example wrap-authentication and wrap-authorization are adapted from buddy.auth.

(defn wrap-authentication
  "Ring middleware that enables authentication for your ring
  handler. When multiple `backends` are given each of them gets a
  chance to authenticate the request."
  [handler & backends]
  (fn authentication-handler
    ([request]
     (handler (apply authentication-request request backends)))
    ([request respond raise]
     (handler (apply authentication-request request backends) 
              respond 
              raise))))
    
;; lots of code in between. Just pretend it&aposs here for
;; the sake of this example.

(defn wrap-authorization
  "Ring middleware that enables authorization
  workflow for your ring handler.
  The `backend` parameter should be a plain function
  that accepts two parameters: request and errordata
  hashmap, or an instance that satisfies IAuthorization
  protocol."
  [handler backend]
  (fn authorization-handler
    ([request]
     (try (handler request)
          (catch Exception e
            (authorization-error request e backend))))
    ([request respond raise]
     (try (handler request respond raise)
          (catch Exception e
            (respond (authorization-error request e backend)))))))
     
;; In another namespace:
;; Middleware usage
(def app
  (-> compojure-routes
      (wrap-authorization jwe)
      (wrap-authentication jwe)
      (wrap-cors :access-control-allow-origin [#".*"]
                 :access-control-allow-methods [:get :post])
      (wrap-json-response {:pretty false})
      (wrap-json-body {:keywords? true})
      (wrap-accept {:mime ["application/json" :as :json
                           "text/html" :as :html]})
      (wrap-cookies)
      (wrap-params)))

Proxy

Provide a surrogate or placeholder for another object to control access to it.

Unlike the only Chain of Responsibility I&aposve seen, I have seen a proxy once or twice. Usually developers don&apost use the proxy form, rather opting to wrap a function in another function to restrict access, lazy typing, or virtualization.

When to use it

The Gang of Four quote is pretty legible on this one. Use a proxy as a &aposmiddleman&apos for (controlling) access to another object. You can also use it for creating a lazily loaded object if a LazySeq doesn&apost do it for you.

Clojure analogue
Keep in mind
Sample code
(defn access-controlled-input-stream
  "Returns an access controlled InputStream"
  [user]
  (proxy [java.io.InputStream] []
    (read
      ([]
       (if (allowed? user)
         (let [^java.io.InputStream this this]
           (proxy-super read))
         (throw (ex-info "Unauthorized"
                         {:user user}))))
      ([^bytes b]
       (if (allowed? user)
         (let [^java.io.InputStream this this]
           (proxy-super read b))
         (throw (ex-info "Unauthorized"
                         {:user user}))))
      ([^bytes b off len]
       (if (allowed? user)
         (let [^java.io.InputStream this this]
           (proxy-super read b off len))
         (throw (ex-info "Unauthorized"
                         {:user user})))))))

Adapter

Convert the interface of a class into a another interface clients expect. Adapter lets classes work together that couldn&apost otherwise because of incompatible interfaces.

You don&apost need to always use a function to wrap something, you can also use an adapter. Adapters are the ultimate glue code and de facto polymorphism (protocols) in Clojure. If we consider a type an interface (or bag of functions™), any library or API could be considered a interface, so we can wrap it in an adapter for core business logic for when we want to swap it out1. Or not, if you&aposre an "aren&apost going to need it" person.

When to use it

When you want to use an implementation with a different interface. Also called a wrapper.

Clojure analogue
Keep in mind
Sample code
;; Shamelessly using protocols example from last post

;; our expect interface
(defprotocol MoneySafe
  "A type to convert other money type to bigdec"
  (make-safe [this] "Coerce a type to be safe for money arithmetic"))

;; our conversions, could also extend Java objects here.
(extend-protocol MoneySafe
  java.lang.Number 
  (make-safe [this] (bigdec this))

  java.lang.String
  (make-safe [this]
    (try
      (-> this
          (Double/parseDouble)
          (bigdec))
      (catch NumberFormatException nfe
        (println "String must be string of number characters"))
      (catch Exception e
        (println "Unknown error converts from string to money")
        (throw e))))

  clojure.lang.PersistentVector
  (make-safe [this]
    (try
      (let [num-bytes (->> this (filter int?) (count))]
        (if (= (count this) num-bytes)
          (->> this
               (map char)
               (clojure.string/join)
               (Double/parseDouble)
               (bigdec))
          (throw (ex-info "Can only convert from vector of bytes"
                          {:input this}))))
      (catch NumberFormatException nfe
        (println "Vector must be bytes representing ASCII number chars")
        (throw nfe))
      (catch Exception e
        (println "Error converting from vector of bytes to money")
        (throw e)))))

;; our client
(defn ->money
  [x]
  (make-safe x))

Template Method

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine steps of an algorithm without changing the algorithm&aposs structure.

Yet lots of people need template methods (functions?) since I&aposve seen plenty of template functions in Clojure codebases over the years. Template functions get pretty hairy when the delegate functions get spread out over the codebase, doubly so if unamed lambdas are used. If you need to write functions across the vast expanse of your namespaces, use a strategy instead.

When to use it

Use a template method when you want to decouple primitives or subroutines of an algorithm from the overaching structure.

Clojure analogue
Keep in mind
Sample code
;; our template defaults
(defn- build-foundation-default
  []
  (println "Building foundation with cement iron rods and sand"))
  
(defn- build-windows-default
  []
  (println "Building glass windows"))

;; The "template"
(defn build-house-template
  ([build-pillars build-walls]
   (build-house-template build-pillars
                         build-walls
                         build-foundation-default
                         build-windows-default))
  ([build-pillars build-walls build-foundation]
   (build-house-template build-pillars
                         build-walls
                         build-foundation
                         build-windows-default))
  ([build-pillars build-walls build-foundation build-windows]
   (build-foundation)
   (build-pillars)
   (build-walls)
   (build-windows)
   (println "House is built.")))

(defn wooden-house
  []
  (build-house-template #(println "Building pillars with wood coating")
                        #(println "Building wooden walls")))

(defn glass-house
  []
  (build-house-template #(println "Building pillars with glass coating")
                        #(println "Building glass walls")))

;; our client
(defn house-builder
  [build-house]
  (build-house))

Flyweight

Use sharing to support large numbers of fine-grained objects efficiently.

Naturally, the Strategy pattern would follow from the template function, but I decided to do flyweight next. Flyweight is basically a cache. If you want to get fancy, you can use the Clojure core.cache library where you get a cache protocol, conveinence macros, and eviction algorithms. Real caching. If you need to share some good ol&apos intrinsic state, here is a couple flyweight ideas for you.

When to use it

Use a Flyweight when you have overlapping object usage between clients. Flyweights typically destinguish between extrinsic state and intrinsic state where the extrinsic state is context dependend and intrinsic is context independent. So, the intrinsic state gets shared between client contexts.

Clojure analogue
Keep in mind
Sample code
(defprotocol Sprite
  "Describes a totally real sprite type"
  (draw! [this x y height width]
    "draw something to the screen."))

(deftype PlayerSprite [sprite-sheet x y height width]
  Sprite
  (draw! [this x y height width]
    "Drew the player on the screen."))

(deftype EnemySprite [sprite-sheet x y height width]
  Sprite
  (draw! [this x y height width]
    "Drew the enemy on the screen."))

;; Our "flyweights"
;; could store anything in memoize.
(def player-sprite
  (memoize #(->PlayerSprite (slurp "./spritesheet.png")
                            {:x 0 :y 0 :height 32 :width 32})))

(def enemy-sprite
  (memoize #(->EnemySprite (slurp "./spritesheet.png")
                           {:x 33 :y 33 :height 32 :width 32})))


;; Atom example
;; our flyweight function is similar to the body of memoize
;; except we want to use a keyword to get value instead
(defn ->flyweight
  []
  (let [mem (atom {})
        player-coords {:x 0 :y 0 :height 32 :width 32}
        enemy-coords {:x 33 :y 33 :height 32 :width 32}
        sprites {:player #(->PlayerSprite (slurp "./spritesheet.png")
                                          player-coords)
                 :enemy #(->EnemySprite (slurp "./spritesheet.png")
                                        enemy-coords)}]
    (fn [k & args]
      (if-let [e (find @mem k)]
        (val e)
        (when-let [f (get sprites k)]
          (let [ret (apply f args)]
            (swap! mem assoc k ret)
            ret))))))

(def flyweight (->flyweight))

Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

There&aposs no official Clojure library for Strategy though. This is kind of a boring pattern in Clojure because it&aposs just a lambda, so I&aposm including it completeness, but what blog post doesn&apost have fluff.

When to use it

Use a Strategy when you need to decouple algorithms from where they&aposre being used.

Clojure analogue
Keep in mind
Sample code
;; I&aposm not going to type out the sorts because I haven&apost
;; been in a CS classroom for years.
(defn quick-sort
  [coll])

(defn merge-sort
  [coll])

(defn radix-sort
  [coll])

(defn find-key
  "Performs a binary search on coll after calling
  sort-fn on coll."
  [key coll sort-fn]
  (find (sort-fn coll) key))
  
;; client somewhere
(find-key :fun-key (get-list) radix-sort)
Thanks for reading. As I said earlier, this is the second post in a series, and I think there might be one or two more, so subscribe for the next one or follow me on twitter @janetacarr , or don&apost ¯\_(ツ)_/¯ . You can also join the discussion about this post on twitter, hackernews, or reddit if you think I&aposm wrong.

1. Some people call this ports and adapters architecture.
2.The template method example was inspired by this Digital Ocean blog post.