12

We usually use builder pattern in java, like this:

UserBuilder userBuilder = new UserBuilder();
User John = userBuiler.setName("John")
                      .setPassword("1234")
                      .isVip(true)
                      .visableByPublic(false)
                      .build();

Some of the attributes have default value, and some haven't.

Passing attributes in a map may be a solution, but it makes the argument really longer:

(def john (make-user {:name "John" :pass "1234" :vip true :visible false}))

So, my question is, is there a elegant way to achieve this?

qiuxiafei
  • 5,827
  • 5
  • 30
  • 43
  • 1
    The builder pattern is in my opinion really just a work around for a lack of named parameters. Initializing a set of fields distinguished only as positional arguments is extremely cumbersome and very hard to read later, hence the builder pattern. Map destructuring achieves the same goals in a single function call, as Ankur suggests split a map onto multiple lines to maintain readability. – Alex Stoddard Sep 28 '12 at 12:55

5 Answers5

10

If you want to construct some clojure structure, you can use destructuring pattern in function arguments. You will then achieve the similar thing you have already wrote.

(defn make-user [& {:keys [name pass vip visible]}]
  ; Here name, pass, vip and visible are regular variables
  ; Do what you want with them
)

(def user (make-user :name "Name" :pass "Pass" :vip false :visible true))

I doubt that you can do something in less code than this.

If you want to construct Java object (using its setters), you can use the approach Nicolas suggested.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • 2
    For completeness, answering about your default values, destructuring support defaults values with `or`: `(defn make-user [& {:keys [name pass vip visible] :or {vip true}}]` – DanLebrero Sep 28 '12 at 09:37
  • Yes, you're completely right. I would also add that if the variable default value is not set in `:or` map and it is not specified in the actual call, it becomes `nil`. – Vladimir Matveev Sep 28 '12 at 15:36
  • 1
    How do you imagine the thrush (`->`) would work in place of `doto`, since the return value of those method calls is not `this`? – Marko Topolnik Sep 30 '12 at 20:02
  • Yes, you're right. Somehow I hadn't remember that when I was writing, and also I (obviously mistakingly) thought that I saw somewhere about these macros interchangeability. Removed this notice. – Vladimir Matveev Sep 30 '12 at 20:26
4

I would normally pass attributes in via a map - there's no real issue with doing this since the attribute map is really just one argument to the make-user function. You can also do nice stuff inside make-user like merge in default attributes.

If you really want to construct such a map with a builder pattern, you can do it with a threading macro as follows:

(def john 
  (-> {}
    (assoc :name "John")
    (assoc :pass "1234")
    (assoc :vip true)
    (assoc :visible false)
    make-user))
mikera
  • 105,238
  • 25
  • 256
  • 415
4

Rewrite

(def john (make-user {:name "John" :pass "1234" :vip true :visible false}))

into multiple lines:

(def john (make-user {:name "John" 
                      :pass "1234" 
                      :vip true 
                      :visible false}))
Ankur
  • 33,367
  • 2
  • 46
  • 72
2

An easy way is to use the doto macro:

Here is an example to populate an array list with some values:

(def al (doto (java.util.ArrayList.) (.add 11) (.add 3)(.add 7)))

Stuart has some perfect examples on how to use doto with Swing. Here with a Panel:

(doto (JPanel.)
            (.setOpaque true)
            (.add label)
            (.add button))

Here with a Frame:

(doto (JFrame. "Counter App")
  (.setContentPane panel)
  (.setSize 300 100)
  (.setVisible true))
Nicolas Modrzyk
  • 13,961
  • 2
  • 36
  • 40
0

For completeness, no one mentioned defrecord which gives you "builder functions" automatically

(defrecord User [name pass vip visible])

(User. "John" "1234" true false)
;;=>#user.User{:name "John", :pass "1234", :vip true, :visible false}

(->User "John" "1234" true false)
;;=>#user.User{:name "John", :pass "1234", :vip true, :visible false}

(map->User {:name "John" :pass "1234" :vip true :visible false})
;;=>#user.User{:name "John", :pass "1234", :vip true, :visible false}
Scott
  • 1,648
  • 13
  • 21