Clojure Design Patterns
Clojure Design Patterns
com/blog/clojure-design-patterns/
MISHADOFF THOUGHTS
Search ARCHIVE CATEGORIES
ABOUT RSS
Index
▪ Intro
▪ Episode 1. Command
▪ Episode 2. Strategy
▪ Episode 3. State
▪ Episode 4. Visitor
▪ Episode 5. Template Method
▪ Episode 6. Iterator
▪ Episode 7. Memento
▪ Episode 8. Prototype
▪ Episode 9. Mediator
▪ Episode 10. Observer
▪ Episode 11. Interpreter
▪ Episode 12. Flyweight
▪ Episode 13. Builder
▪ Episode 14. Facade
▪ Episode 15. Singleton
▪ Episode 16. Chain of Responsibility
▪ Episode 17. Composite
▪ Episode 18. Factory Method
▪ Episode 19. Abstract Factory
▪ Episode 20. Adapter
▪ Episode 21. Decorator
▪ Episode 22. Proxy
▪ Episode 23. Bridge
▪ Cheatsheet
1 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
▪ Cast
Intro
Our programming language is fucked up.
That’s why we need design patterns.
– Anonymous
Two modest programmers Pedro Veel and Eve Dopler are solving
common software engineering problems and applying design patterns.
Episode 1. Command
Leading IT service provider “Serpent Hill & R.E.E” acquired new project
for USA customer. First delivery is a register, login and logout functionality
for their brand new site.
interface Command {
void execute();
}
Pedro: Every action should implement this interface and define specific
execute behaviour.
@Override
public void execute() {
DB.login(user, password);
}
}
2 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
@Override
public void execute() {
DB.logout(user);
}
}
new SomeInterfaceWithOneMethod() {
@Override
public void execute() {
// do
}
};
3 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: And how do you save function for delayed call in that case?
Eve: Answer yourself. What do you need to call a function?
Pedro: Its name…
Eve: And?
Pedro: …arguments.
Eve: Bingo. All you do is saving a pair (function-name, arguments) and
call it whenever you want using (apply function-name arguments)
Pedro: Hmm… Looks simple.
Eve: Definitely, Command is just a function.
Episode 2. Strategy
Sven Tori pays a lot of money to see a page with list of users. But users
must be sorted by name and users with subscription must appear before all
other users. Obviously, because they pay. Reverse sorting should keep
subscripted users on top.
@Override
public int compare(User u1, User u2) {
if (u1.isSubscription() == u2.isSubscription()) {
return u1.getName().compareTo(u2.getName());
} else if (u1.isSubscription()) {
return -1;
} else {
return 1;
}
}
}
@Override
public int compare(User u1, User u2) {
if (u1.isSubscription() == u2.isSubscription()) {
return u2.getName().compareTo(u1.getName());
} else if (u1.isSubscription()) {
return -1;
} else {
4 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
return 1;
}
}
}
// forward sort
Collections.sort(users, new SubsComparator());
// reverse sort
Collections.sort(users, new ReverseSubsComparator());
(sort (comparator
(fn [u1 u2]
(cond
(= (:subscription u1)
(:subscription u2)) (neg? (compare (:name u1)
(:name u2)))
(:subscription u1) true
:else false))) users)
;; forward sort
(sort-by (juxt (complement :subscription) :name) users)
;; reverse sort
(sort-by (juxt :subscription :name) #(compare %2 %1) users)
Episode 3. State
Sales person Karmen Git investigated the market and decided to provide
user-specific functionality.
5 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
UserState(int newsLimit) {
this.newsLimit = newsLimit;
}
6 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Eve: You just hide value that affects behaviour inside User object. We
could use strategy to pass it directly
user.newsFeed(subscriptionType).
Pedro: Agreed, State is very close to the Strategy. They even have the
same UML diagrams. but we encapsulate balance and bind it to user.
Eve: I think it achieves the same goal using another mechanism. Instead
of providing strategy explicitly, it depends on some state. From clojure
perspective it can be implemented the same way as strategy pattern.
Pedro: But successive calls can change object’s state.
Eve: Correct, but it has nothing to do with Strategy it is just
implementation detail.
Pedro: What about “another mechanism”?
Eve: Multimethods.
Pedro: Multi what?
Eve: Look at this
Eve: And pay function it’s just a plain function, which changes state of
object. We don’t like state too much in clojure, but if you wish.
7 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Episode 4. Visitor
Natanius S. Selbys suggested to implement functionality which allows
users export their messages, activities and achievements in different
formats.
@Override
void export(XML xml) {
XMLExporter.export(this);
}
}
8 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
@Override
void export(XML xml) {
XMLExporter.export(this);
}
}
@Override
public void visit(Message m) {
PDFExporter.export(m);
}
}
9 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Eve: And everything works fine. Moreover, you can add new operations
for activities and messages by just defining new visitors and without
changing their code.
Pedro: That’s really useful. But implementation is tough, it is the same
for clojure?
Eve: Not really, clojure supports it natively via multimethods
Pedro: Multi what?
Eve: Just follow the code… First we define dispatcher function
(defmulti export
(fn [item format] [(:type item) format]))
;; Message
{:type :message :content "Say what again!"}
;; Activity
{:type :activity :content "Quoting Ezekiel 25:17"}
;; Formats
:pdf, :xml
Eve: And now you just provide a functions for different combinatations,
and dispatcher decide which one to call.
10 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(exporter/activity->pdf item))
Pedro: Ok, but there is no hierarchy for :pdf and :xml. They are just
keywords?
Eve: Correct, simple problem - simple solution. If you need advanced
features, you could use adhoc hierarchies or dispatch by class.
Pedro: Quadrocolons?!
Eve: Assume they are just keywords.
Pedro: Ok.
Eve: Then you add functions for every dispatch type ::pdf, ::xml and
::format
11 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Eve: Exactly.
Pedro: First, we must decide what actions should be automated with bot.
Eve: Have you ever played RPG?
Pedro: Fortunately, no
Eve: Oh my… Let’s go, I’ll show you…
2 weeks later
▪ Battle
▪ Quest
▪ Open Chest
12 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
}
}
@Override
void attack(List<Enemy> enemies) {
if (enemies.size() > 10) {
castSpell("Freeze Nova");
castSpell("Teleport");
} else {
for (Enemy e : enemies) {
castSpell("Fireball", e);
}
}
}
}
@Override
void attack(List<Enemy> enemies) {
for (Enemy e : enemies) {
invisibility();
13 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
attack("backstab", e);
}
}
}
(chest? location)
(handle-chest (:chest location))
(enemies? location)
(attack (:enemies location)))
(move-to character (:next-location location)))
;; Mage-specific actions
(defn mage-handle-chest [chest])
14 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Eve: Keep in mind that if these functions are not provided we use default
behavior: do nothing for handle-chest and run away from enemies in
attack
Pedro: Fine, but is this better than approach by subclassing? Seems that
we have a lot of redundant information in move-to call.
Eve: It’s fixable, just define this call once, and give it alias
(defmulti move
(fn [character location] (:class character)))
Episode 6. Iterator
Technical consultant Kent Podiololis complains for C-style loops usage.
15 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Iterator i;
while (i.hasNext()) {
i.next();
}
16 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: Fine then. But I’ve heard iterator is often used to achive laziness,
for example to calculate value only during getNext() call, how list
handle that?
Eve: List can be lazy as well, clojure calls such list “lazy sequence”.
Episode 7: Memento
User Chad Bogue lost the message he was writing for two days.
Implement save button for him.
Pedro: I don’t believe there are people who can type in textbox for two
days. Two. Days.
Eve: Let’s save him.
Pedro: I googled this problem. Most popular approach to implement save
button is Memento pattern. You need originator, caretaker and memento
objects.
Eve: What’s that?
Pedro: Originator is just an object or state that we want to preserve.
(text inside a textbox), caretaker is responsible to save state (save
button) and memento is just an object to encapsulate state.
17 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
@Override
public String toString() {
return "[" + text + "]";
}
}
// type again
textbox.type("song 'Like A Virgin' is about. ");
textbox.type("It's all about a girl...");
18 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(defn init-textbox []
(reset! textbox {:text ""
:color :BLACK
:width 100}))
(defn save []
(reset! memento (:text @textbox)))
(defn restore []
(swap! textbox assoc :text @memento))
(init-textbox)
(type-text "'Like A Virgin' ")
(type-text "it's not about this sensitive girl ")
(save)
(type-text "who meets nice fella")
;; crash
(init-textbox)
(restore)
19 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Episode 8: Prototype
Dex Ringeus detected that users feel uncomfortable with registration
form. Make it more usable.
2 hours later
Pedro: I suggest to use some registration prototype which has all fields
are filled with default values. After user completes the form we modify
filled values.
Eve: Sounds great.
Pedro: Here it is our standard registration form, with prototype in
clone() method.
@Override
protected RegistrationForm clone() throws CloneNotSupportedException {
RegistrationForm prototyped = new RegistrationForm();
prototyped.name = name;
prototyped.email = email;
prototyped.dateOfBirth = (Date)dateOfBirth.clone();
20 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
prototyped.weight = weight;
prototyped.status = status;
List<Child> childrenCopy = new ArrayList<Child>();
for (Child c : children) {
childrenCopy.add(c.clone());
}
prototyped.children = childrenCopy;
prototyped.monthSalary = monthSalary;
List<String> brandsCopy = new ArrayList<String>();
for (String s : favouriteBrands) {
brandsCopy.add(s);
}
prototyped.favouriteBrands = brandsCopy;
return prototyped;
}
}
Pedro: Every time we create a user, call clone() and then override
needed properties.
Eve: Awful! In mutable world clone() is needed to create new object
with the same properties. The hard part is the copy must be deep, i.e.
instead of copying reference you need recursively clone() other objects,
and what if one of them doesn’t have clone()…
Pedro: That’s the problem and this pattern solves it.
Eve: I don’t think it is a solution if you need to implement clone every
time you adding new object.
Pedro: How clojure avoid this?
Eve: Clojure has immutable data structures. That’s all.
Pedro: How does it solve prototype problem?
Eve: Every time you modify object, you get a fresh new immutable copy
of your data, and old one is not changed. Prototype is not needed in
immutable world
(def registration-prototype
{:name "Zed"
:email "zzzed@gmail.com"
:date-of-birth "1970-01-01"
:weight 60
:gender :male
:status :single
:children [{:gender :female}]
:month-salary 1000
:brands ["Adidas" "GAP"]})
21 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
:month-salary 0)
Episode 9: Mediator
Recently performed external code review shows a lot of issues with current
codebase. Veerco Wierde emphasizes tight coupling in chat application.
Pedro: The problem here is the user knows everything about other users.
It is very hard to use and maintain such code. When new user connects to
the chat, you must add a reference to him via addUser for every existing
user.
Eve: So, we just move one piece of responsibility to another class?
22 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: Yes, kind of. We create mega-aware class, called mediator, that
binds all parts together. Obviously, each part knows only about mediator.
(def mediator
(atom {:users []
:send (fn [users text]
(map #(receive % text) users))}))
23 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(swap! mediator
(fn [m]
(update-in m [:users] conj u))))
24 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: And the last part: init tracker with the user and modify its
addMoney method. If transcation amount is greaterthan 100$, notify FBI
and block this user.
public User() {
initTracker();
}
25 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
tracker.update(this);
}
}
}
Eve: Why are you created two separate observers? You could use it in a
one.
;; Tracker
;; Fill Observers
;; User
26 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(add-watch
user
:money-tracker
(fn [k r os ns]
(if (< 100 (- (:balance ns) (:balance os)))
(notify))))
27 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
interface BencodeElement {
String interpret();
}
@Override
public String interpret() {
return "i" + value + "e";
}
}
StringElement(String value) {
this.value = value;
}
@Override
public String interpret() {
return value.length() + ":" + value;
}
}
@Override
public String interpret() {
String content = "";
for (BencodeElement e : list) {
content += e.interpret();
}
return "l" + content + "e";
}
28 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
@Override
public String interpret() {
String content = "";
for (Map.Entry<StringElement, BencodeElement> kv : map.entrySet()) {
content += kv.getKey().interpret() + kv.getValue().interpret();
}
return "d" + content + "e";
}
}
Pedro: And finally, our bencoded string can be constructed from common
datastructures programmatically
// discredit user
Map<StringElement, BencodeElement> mainStructure = new HashMap<StringElement, BencodeElement
// our victim
mainStructure.put(new StringElement("user"), new StringElement("Bertie"));
// just downloads files
mainStructure.put(new StringElement("number_of_downloaded_torrents"), new IntegerElement
// and nothing uploads
mainStructure.put(new StringElement("number_of_uploaded_torrents"), new IntegerElement
// and nothing donates
mainStructure.put(new StringElement("donation_in_dollars"), new IntegerElement(0));
// prefer dirty categories
mainStructure.put(new StringElement("preffered_categories"),
new ListElement(Arrays.asList(
new StringElement("porn"),
new StringElement("murder"),
new StringElement("scala"),
new StringElement("pokemons")
)));
BencodeElement top = new DictionaryElement(mainStructure);
29 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
;; usage
(interpret {"user" "Bertie"
"number_of_downloaded_torrents" 623
"number_of_uploaded_torrent" 0
"donation_in_dollars" 0
"preffered_categories" ["porn"
"murder"
"scala"
"pokemons"
30 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: What?
Eve: They use age values for points, why not precompute these points for
most common ages? Say for age [0, 100]
Pedro: You mean use Flyweight pattern?
Eve: I mean reuse objects.
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Pedro: For this pattern we need two things: precompute most used
points at a startup time, and use static factory method instead of
constructor to return cached object.
Eve: Have you tested it?
Pedro: Sure, the system works like a clock.
Eve: Excellent, here is my version
(defn make-point [x y]
[x y {:some "Important Properties"}])
(def CACHE
(let [cache-keys (for [i (range 100) j (range 100)] [i j])]
31 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(defn make-point-cached [x y]
(let [result (get CACHE [x y])]
(if result
result
(make-point x y))))
Eve: It creates a flat map with pair [x, y] as a key, instead of two-
dimensional array.
Pedro: Pretty the same.
Eve: No, it is much flexible, you can’t use two-dimensional array if you
need to cache three points or non-integer values.
Pedro: Oh, got it.
Eve: Even better, in clojure you can just use memoize function to cache
calls to factory function make-point
Eve: Every call (except first one) with the same parameters return
cached value.
Pedro: That’s awesome!
Eve: Of course, but remember if your function has side-effects,
memoization is bad idea.
32 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
private Coffee() { }
public Builder() { }
33 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
return c;
}
}
}
Pedro: As you see, you can’t instantiate Coffee class easily, you need to
set parameters with nested Builder class
Pedro: Calling to method make checks all required parameters, and could
validate and throw an exception if object is in inconsistent state.
Eve: Awesome functionality, but why so verbose?
Pedro: Beat it.
Eve: A piece of cake, clojure supports optional arguments, everything
what builder pattern is about.
Pedro: Aha, you have three required parameters and three optionals, but
required parameters still without names.
Eve: What do you mean?
Pedro: From the client call I see number 15 but I have no idea what it
might be.
Eve: Agreed. Then, let’s make all parameters are named and add
precondition for required, the same way you do with the builder.
34 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(defn make-coffee
[& {:keys [name amount water milk sugar cinnamon]
:or {name "" amount 0 water 0 milk 0 sugar 0 cinnamon 0}}]
{:pre [(not (empty? name))
(> amount 0)
(> water 0)]}
;; definition goes here
)
Eve: As you see all parameters are named and all required params are
checked in :pre constraint. If constraints are violated AssertionError is
thrown.
Pedro: Interesting, :pre is a part of a language?
Eve: Sure, it’s just a simple assertion. There is also :post constraint,
with the similar effect.
Pedro: Hm, okay. But as you know Builder pattern often used as a
mutable datastucture, StringBuilder for example.
Eve: It’s not a part of clojure philosophy to use mutables, but if you really
want, no problem. Just create a new class with deftype and do not forget
to use volatile-mutable on the properties you want to mutate.
Pedro: Where is the code?
Eve: Here is example of custom implementation of mutable
StringBuilder in clojure. It has a lot of drawbacks and limitations but
you’ve got the idea.
;; interface
(defprotocol IStringBuilder
(append [this s])
(to-string [this]))
;; implementation
(deftype ClojureStringBuilder [charray ^:volatile-mutable last-pos]
IStringBuilder
(append [this s]
(let [cs (char-array s)]
(doseq [i (range (count cs))]
(aset charray (+ last-pos i) (aget cs i))))
(set! last-pos (+ last-pos (count s))))
(to-string [this] (apply str (take last-pos charray))))
;; clojure binding
(defn new-string-builder []
(ClojureStringBuilder. (char-array 100) 0))
35 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
;; usage
(def sb (new-string-builder))
(append sb "Toby Wong")
(to-string sb) => "Toby Wong"
(append sb " ")
(append sb "Toby Chung") => "Toby Wang Toby Chung"
class OldServlet {
@Autowired
RequestExtractorService requestExtractorService;
@Autowired
RequestValidatorService requestValidatorService;
@Autowired
TransformerService transformerService;
@Autowired
ResponseBuilderService responseBuilderService;
Eve: Oh shi…
Pedro: That’s our internal API for developers, every time they need to
process request, inject 4 services, include all imports, and write this
code.
Eve: Let’s refactor it with…
Pedro: …Facade pattern. We resolve all dependencies to a single point
of access and simplify API usage.
36 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
RequestExtractorService requestExtractorService;
@Autowired
RequestValidatorService requestValidatorService;
@Autowired
TransformerService transformerService;
@Autowired
ResponseBuilderService responseBuilderService;
Pedro: Then if you need any service or set of services in the code you
just injecting facade to your code
class NewServlet {
@Autowired
FacadeService facadeService;
Eve: Wait, you’ve just moved all dependencies to one and everytime
using this one, correct?
Pedro: Yes, now everytime some functionality is needed, use
FacadeService. Dependency is already there.
Eve: But we did the same in Mediator pattern?
Pedro: Mediator is behavioral pattern. We resolved all dependency to
Mediator and added new behavior to it.
Eve: And facade?
Pedro: Facade is structural, we don’t add new functionality, we just
expose existing functionality with facade.
37 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Eve: Got it. But seems that pattern very loud word for such little tweak.
Pedro: Maybe.
Eve: Here is clojure version using structure by namespaces
(ns application.old-servlet
(:require [application.request-extractor :as re])
(:require [application.request-validator :as rv])
(:require [application.transformer :as t])
(:require [application.response-builder :as rb]))
(ns application.facade
(:require [application.request-extractor :as re])
(:require [application.request-validator :as rv])
(:require [application.transformer :as t])
(:require [application.response-builder :as rb]))
(ns application.old-servlet
(:use [application.facade]))
38 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: But wait, there was requirement to save UI style per user.
Eve: Probably it was changed.
Pedro: Ok, then we should just save configuration to Singleton and use
it from all the places.
Pedro: That way all configuration will be shared across the UIs.
39 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
40 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: Ok, so let’s just add a filter to replace these rude words with the
asterisks.
Eve: Make sure your solution is extendable, other filters could be
applied.
Pedro: Chain of Responisibility seems like a good pattern candidate for
that. First of all we make some abstract filter.
Pedro: Then, provide implementation for each specific filter you want to
apply
41 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: And finally build a chain of filters which defines an order how
message will be processed.
rejectFilter.setNextFilter(logFilter);
logFilter.setNextFilter(profanityFilter);
profanityFilter.setNextFilter(statsFilter);
Eve: Ok, now clojure turn. Just define each filter as a function.
;; define filters
42 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(some-> message
reject-filter
log-filter
stats-filter
profanity-filter))
Eve: You see how much it is easier, you don’t need every-time call if
(nextFilter != null) nextFilter.process(), because it’s natural.
The next filter defined at the some-> level naturally, instead of calling
manuall setNext.
Pedro: That’s definitely better for composability, but why did you use
some-> instead of ->?
Eve: Just for reject-filter. It could stop further processing, so some->
returns nil as soon as nil encountered as a filter
Pedro: Could you explain more?
Eve: Look at the usage
Pedro: Understood.
Eve: Chain of Responsibility just an approach to function composition
Digging code
43 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
void render();
}
Pedro: Obviously blocks may contain other blocks, that’s the point of
Composite pattern. We may create some specific blocks.
page.addBlock(header);
page.addBlock(body);
header.addBlock(title);
header.addBlock(avatar);
page.render();
44 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: I understand.
Eve: To be specific, here is the tree
A
/ | \
B C D
| | / \
E H J K
/ \ /|\
F G L M N
(def tree
'(A (B (E (F) (G))) (C (H)) (D (J) (K (L) (M) (N)))))
45 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: I think it’s a crap, but anyway, let’s make MazeBuilder generic
and add specific builder for each type of the block. It’s a Factory Method
pattern.
class Maze { }
class WoodMaze extends Maze { }
class IronMaze extends Maze { }
interface MazeBuilder {
Maze build();
}
class WoodMazeBuilder {
@Override
Maze build() {
return new WoodMaze();
}
}
class IronMazeBuilder {
@Override
Maze build() {
return new IronMaze();
}
}
46 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
class Wall {}
class PlasmaWall extends Wall {}
class StoneWall extends Wall {}
class Back {}
class StarsBack extends Back {}
class EarthBack extends Back {}
class Enemy {}
class UFOSoldier extends Enemy {}
class WormScout extends Enemy {}
47 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: See? We have a specific object for each level, let’s create factory
for them.
@Override
public Back buildBack() {
return new StarsBack();
}
@Override
public Enemy buildEnemy() {
return new UFOSoldier();
}
}
@Override
public Back buildBack() {
return new EarthBack();
}
@Override
public Enemy buildEnemy() {
return new WormScout();
}
}
48 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(def underground-level-factory
(partial level-factory
make-stone-wall
make-earth-back
make-worm-scout))
(def space-level-factory
(partial level-factory
make-plasma-wall
make-stars-back
make-ufo-soldier))
Pedro: I knew.
Eve: Everything is fair. Your lovely “set of related Xs”, where X is a
function
Pedro: Yes, clarify, what partial is.
Eve: Provide some parameters for function. So, underground-level-
factory knows how to construct walls, backs and enemies. Everything
other inherited from abstract level-factory function.
Pedro: Handy.
I’ll pay you the half if you break the system and allow my armed commando
to take part in competition.
49 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
Pedro: Aha! System validates only incoming types via Knight interface.
All we need to do is to adapt commando to be a knight. Let’s see how
knight look like
interface Knight {
void attackWithSword();
void attackWithBow();
void blockWithShield();
}
@Override
public void attackWithBow() {
winkToQueen();
take(bow);
attack();
}
@Override
public void attackWithSword() {
winkToQueen();
take(sword);
attack();
}
}
class Commando {
void throwGrenade(String grenade) { }
shot(String rifleType) { }
}
50 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
@Override
public void attackWithBow() {
throwGrenade("F1");
}
@Override
public void attackWithSword() {
shotWithRifle("M16");
}
}
{:name "Lancelot"
:speed 1.0
:attack-bow-fn attack-with-bow
:attack-sword-fn attack-with-sword
:block-fn block-with-shield}
Eve: To adapt commando, just pass his functions instead original ones
{:name "Commando"
:speed 5.0
:attack-bow-fn (partial throw-grenade "F1")
:attack-sword-fn (partial shot "M16")
:block-fn nil}
51 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
public Knight() { }
52 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
@Override
public void blockWithShield() {
super.blockWithShield();
Armor armor = new PowerArmor();
armor.block();
}
}
Knight superKnight =
new KnightWithAdditionalHP(
new KnightWithPowerArmor(
new Galahad()));
53 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(block-with-power-armor)))))
interface IBar {
void makeDrink(Drink drink);
}
interface Drink {
List<Ingredient> getIngredients();
}
interface Ingredient {
String getName();
double getAmount();
}
54 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
bar.subtract(i);
}
}
}
;; interface
(defprotocol IBar
(make-drink [this drink]))
;; Bart's implementation
(deftype StandardBar []
IBar
(make-drink [this drink]
(println "Making drink " drink)
:ok))
;; our implementation
(deftype ProxiedBar [db ibar]
IBar
(make-drink [this drink]
(make-drink ibar drink)
(subtract-ingredients db drink)))
55 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
(reify IBar
(make-drink [this drink]
;; implementation goes here
))
interface JobRequirement {
boolean accept(Candidate c);
}
56 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
57 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
super(Arrays.asList(
new ClojureJobRequirement(),
new Experience10YearsRequirement()
));
}
}
58 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
;; abstraction
;; implementation
(defmulti accept :job)
Eve: Later, when new jobs are created, but requirements are not yet
developed and no accept method implementation for this type of job, we
fallback using adhoc hierarchy.
Pedro: Hm?
Eve: Assume someone created a new ::senior-java as a child of ::java
job.
Pedro: Oh, and if HR not provided accept implementation for dispatch
value ::senior-java, method with dispatch value ::java will be called,
yes?
Eve: You learning so fast.
Pedro: But is it real bridge pattern?
Eve: There is no bridge here, but abstraction and implementation can
live independently.
The End.
Cheatsheet
It is very confusing to understand patterns, which often presented in
object-oriented way with bunch of UML diagrams, fancy nouns and exist
to solve language-specific problems, so here is a revisited mini
59 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
▪ Command - function
▪ Strategy - function, which accepts function
▪ State - strategy, depends on state
▪ Visitor - multiple dispatch
▪ Template Method - strategy with defaults
▪ Iterator - sequence
▪ Memento - save and restore
▪ Prototype - immutability
▪ Mediator - reduce coupling
▪ Observer - function, which calls after another function
▪ Interpreter - set of functions to process a tree
▪ Flyweight - cache
▪ Builder - optional arguments
▪ Facade - single point of access
▪ Singleton - global variable
▪ Chain of Responsibility - function composition
▪ Composite - tree
▪ Factory Method - strategy for creating objects
▪ Abstract Factory - strategy for creating set of related objects
▪ Adapter - wrapper, same functionality, different type
▪ Decorator - wrapper, same type, new functionality
▪ Proxy - wrapper, function composition
▪ Bridge - separate abstraction and implementation
Cast
A long time ago in a galaxy far, far away…
With the lack of imagination all characters and names are just anagrams.
60 of 61 12/21/23, 11:04
Clojure Design Patterns https://mishadoff.com/blog/clojure-design-patterns/
P.S. I’ve started to write this article more than 2 years ago. Time has
passed, things have changed and even Java 8 was released.
Post
61 of 61 12/21/23, 11:04