狗好看の世界

Less words, more attempting.

从OOP的设计模式到FP

Table of Contents

用一百个函数来操作一种数据结构, 优于用十个函数来操作十种数据结构.
                     -- 艾伦·J·佩利

用更多的处理数据的方式来处理数据, 而不是定义更多的数据类型. 这样可以让系统间更容易融合, 也可以让编写代码时更简单, 关注的东西更少. 对于Clojure来讲, 由于相对于OOP减少了数据的类型(绝大部分的数据都可以看作是字典和列表的组合), 使得写什么样的程序, 处理的都是比较熟悉的数据结构. 对于设计模式中的很多东西, 在Clojure中并不是必要的, 这里是策略模式, 适配器模式和模板方法模式在Clojure中的实现.

Strategy

策略模式. OOP中, 执行一个操作或者进行一个运算的时候, 如果有多种方案, 我们可以选择策略模式. 首先执行者提供一个策略接口, 然后针对不同的策略, 提供不同的策略实现类. 下面是Clojure中的实现:

;; 先做原型再写程序
(defn protocol-to-program [demand] ...)
;; 先做样例再完善
(defn demo-to-program [demand] ...)
;; 直接写程序
(defn write-program-directly [demand] ...)

;; 选择一种写程序的策略
(defn choose-program-technique [team]
  (cond
    (= 1 team) write-program-directly
    (big-team? team) protocol-to-program
    :else demo-to-program))

(defn program [demand team]
  ((choose-program-technique team) demand))

首先假设我们有三种写程序的方法, 然后当我们要写程序(program方法)的时候, 我们根据team的大小来决定用什么方式来实现需求(demand). 这里调用了choose-program-technique, 如果team只有1人, 选择草率的直接写. 如果是一个大的team就从原型开始, 否则就是从demo开始. choose-program-technique会直接返回一个函数, 然后我们直接调用返回的函数就可以. 由于我们实际需要的就是函数, 所以没有必要为了函数再封装成对象并提供接口.

Adapter

适配器模式, 在面向对象中, 表示了同样数据的对象, 往往不能应用在所有的场合. 特别是在使用三方库的时候, 因为不同的库中对于同样数据可能都有自己的一套封装. 所以为了让三方库可以替换, 我们需要提供对象与工具类之间的适配.

(defprotocol BarkingDog
  "这是一只正在叫的狗"
  (bark [this] "dog should bark"))

(extend-protocol BarkingDog
  clojure.lang.IPersistentVector
  (bark [v] (conj v "bark!")))

(def a-vector [1 2 3 4])

(bark a-vector)
;; => [1 2 3 4 "bark!"]

在Clojure中, 通过protocol来代替接口, 通过extend-protocol来适配接口和数据. 这使得在Clojure中, 如果有数据和函数不能适配的情况, 可以直接使用这种方法, 来告诉函数对应该数据应该怎么做. 免去写适配器接口和适配器类的麻烦.

Template Method

模板方法模式, 用于在面向对象中, 有些类中的方法的处理逻辑实际上并不由本类所提供. 一个逻辑可能有多个步骤组成, 而这个逻辑类并不关心每一步的具体实现是如何. 只是负责把所有的环节串联起来. 在OOP中会为每一个环节提供一个接口, 然后通过对象的注入来为每一个环节提供特定的逻辑.

(defn update-account-status
  [account-id get-fn status save-fn]
  (let [account (get-fn account-id)]
    (when (not= status
            (:status account))
      (save-fn (assoc account :status status)))))

(defn get-account-from-db ...)
(defn get-account-from-datomic ...)
(defn get-account-from-http-service ...)

(defn save-account-to-db ...)
(defn save-account-to-datomic ...)
(defn save-account-to-http-service ...)

在Clojure中, 通过高阶函数来实现. 直接传入需要的函数, 来填补某个部分所需要的逻辑.

Comments