如何在Clojure中实现策略模式?

时间:2017-03-30 15:09:48

标签: clojure

我有三个类似的函数,它们过滤了一个与键(列)匹配的映射集合,不区分大小写。

这是我想要干的原始代码:

;; gets a collection filtered by a column, exact match
(defn get-collection-by-equals [collection-name column-name column-value]
 (filter #(= (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))

;; gets a collection filtered by a column, with partial match, case-insensitive
(defn get-collection-by-like [collection-name column-name column-value]
 (filter #(string/includes? (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))

;; gets a collection filtered by a column, which starts with given value, case-insensitive
(defn get-collection-by-starts-with [collection-name column-name column-value]
 (filter #(string/starts-with? (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))

您可以看到代码有多相似,我只是在每种情况下使用不同的匹配策略,=includes?starts-with?

我的第一次尝试如下:

;; returns a function which does a case-insensitive match between given column and value for the given map
(defn matching-fn [match-fn column-name column-value]
  (fn [map] (match-fn (string/lower-case (column-name map)) (string/lower-case column-value))))

;; gets a collection filtered by a column, exact match
(defn get-collection-by-equals [collection-name column-name column-value]
 (filter #((matching-fn = column-name column-value) %) (cached-get-collection collection-name)))

;; gets a collection filtered by a column, with partial match, case-insensitive
(defn get-collection-by-like [collection-name column-name column-value]
 (filter #((matching-fn string/includes? column-name column-value) %) (cached-get-collection collection-name)))

;; gets a collection filtered by a column, which starts with given value, case-insensitive
(defn get-collection-by-starts-with [collection-name column-name column-value]
 (filter #((matching-fn string/starts-with? column-name column-value) %) (cached-get-collection collection-name)))

我不喜欢这个解决方案的可读性,我发现我只能传递匹配函数,而不是有一个返回函数的函数,我想出了这个:

;; gets a collection filtered by a column, using the given function, case-insensitive
(defn get-collection-by-filter [collection-name filter-fn column-name column-value]
 (filter #(filter-fn (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))

;; gets a collection filtered by a column, exact match, case-insensitive
(defn get-collection-by-equals [collection-name column-name column-value]
  (get-collection-by collection-name = column-name column-value))

;; gets a collection filtered by a column, with partial match, case-insensitive
(defn get-collection-by-like [collection-name column-name column-value]
 (get-collection-by collection-name string/includes? column-name column-value))

;; gets a collection filtered by a column, which starts with given value, case-insensitive
(defn get-collection-by-starts-with [collection-name column-name column-value]
 (get-collection-by collection-name string/starts-with? column-name column-value))

这是惯用的Clojure,还有其他(更好的)解决方案吗?

使用宏似乎有点矫枉过正。

2 个答案:

答案 0 :(得分:9)

与许多OO模式一样,在函数式语言中,整个模式归结为“使用带参数的函数”。除了变为新函数的小部分之外,提取除了作为该函数的参数而变化的部分。

(defn collection-comparator [cmp]
  (fn [collection-name column-name column-value]
    (let [lower-value (string/lower-case column-value)]
      (filter #(cmp (string/lower-case (column-name %)) 
                    lower-value)
              (cached-get-collection collection-name)))))

(def get-collection-by-equals (collection-comparator =))
(def get-collection-by-like (collection-comparator string/includes?))
(def get-collection-by-starts-with (collection-comparator string/starts-with?))

答案 1 :(得分:3)

我只是使用你的get-collection-by-filter而不再包装它 - 为什么为参数可能采用的每个值创建一个新函数?

除此之外:只需将描述设为文档字符串,然后对其进行格式化:

(defn get-collection-by-filter
  "gets a collection filtered by a column, using the given function,
   case-insensitive"
  [collection-name filter-fn column-name column-value]
  (filter #(filter-fn (string/lower-case (column-name %))
                      (string/lower-case column-value))
          (cached-get-collection collection-name)))