As far as the boilerplate is concerned, you can write some macro to write all that boilerplate for you. On the other hand, you could have a second look at your design here.
What we have here is 3 things (types): A
, B
and IExample
. And then we have 2 relationships between these things: 1) a-to-example : A -> IExample
2) b-to-a : B -> A
and from this we can get 3rd relationship by using composition i.e compose b-to-a with a-to-example : B -> IExample
. Now if we try to move this design to protocols we will find that it is not a simple translation because protocols won't directly compose as discussed in the above design, instead we can use an intermediate protocol IToExample
like shown below:
(defprotocol IExample
(foo [this] "do something")
(bar [this] "do something else"))
(defprotocol IToExample
(to-example [this] "convert to IExample"))
(defrecord A [field1 field2]
IExample
(foo [this]
(+ field1 field2))
(bar [this]
(- field1 field2))
IToExample
(to-example [this] this))
(deftype B [])
(defn b-to-a [b] (A. ....))
(extend B
IToExample {:to-example b-to-a})
What we did that we represented the -> IExample
in our design as the IToExample
protocol with one function. So we got:
a-to-example : A -> IExample
by implementing IToExample for Ab-to-a : B -> A
by a normal functioncompose b-to-a with a-to-example : B -> IExample
by implementing IToExample for B and using b-to-a.