Clojure 如何表达 OOP 设计模式
近几年的工作都是写 Clojure ,大多数时间是用函数式编程风格。 OOP 的设计模式在函数式语言里显得非常轻量,有时根本意识不到有些用法是设计模式。简单聊一聊,常用的设计模式在 Clojure 里可能长什么样子。由于语言类型不同,一些和语言构件相关的模式在 Clojure 里不太需要,比如我在写 Clojure 时就很少需要用迭代器模式。
对象是 OOP 编程语言中的一等公民,在函数式语言中函数是编程中的一等公民,所以下面有些地方我会直接用函数来代替对象,如工厂模式用生产函数来代替生产对象。
1. 策略模式
定义:策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
(defn quack [] (println "quack")) (defn squeak [] (println "squeak")) (defn make-sound [duck?] (let [sound-f (if duck? quack squeak)] (sound-f))) (make-sound true)
小辨析:
策略模式:在 context 对象主动指定哪个策略对象来执行动作。
状态模式:在 context 对象中的状态决定使用哪一个 state 状态对象来操作,state 状态对象在执行操作中反过来会改变 context 对象中的状态。
2. 单件模式
定义:确保一个类只有一个实例,并提供一个全局访问点。
(let [conn (delay (create-conn))] (defn get-instance [] @conn)) (get-instance)
3. 工厂模式
定义: 定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。
(defn create-adder [n] (fn [x] (+ n x))) (map (fn [x] (create-adder x)) (range 10))
抽象工厂模式在 Clojure 中似乎和工厂模式没有太多区别。
4. 装饰者模式
定义:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
Clojure 类型继承很弱,依然以函数来实现。
(defn origin [n] n) (defn decorator "add 5" [f] (fn [n] (+ (f n) 5))) (def new-f (decorator origin)) (new-f 3)
小辨析:
装饰者:装饰与被装饰者实现同一个接口,在同一个接口内增强功能。
适配器:适配与被适配者实现的是两个不同的接口。意图是改变接口,满足客户的期望。
外观模式:用一个接口实现子系统的一群接口。意图是,提供子系统的一个简化接口。
5. 观察者模式
定义:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
(def subject (atom nil)) (defn subscribe [key ref old-state new-state] (print "Current val is " new-state)) (add-watch subject :sub subscribe) (reset! subject {:foo "bar"}) (remove-watch subject :sub)
6. 命令模式
定义:将"请求"封装成对象,调用方可以直接调用对象的 execute。命令模式也支持可撤销的操作。
(def command1 (fn [] (print "command1"))) (def command2 (fn [] (print "command2"))) (doseq [cmd [command1 command2]] (cmd))
小辨析:
策略模式:在调用方需要传入一致的参数.
命令模式:将不同命令的参数封装在命令对象里,只留一个 execute 方法。
7. 适配器模式
定义:适配器模式,将一个类的接口,转换成客户期望的另一个接口。
(defn add [x y] (+ x y)) (defn adopter [x y] (add (Integer/parseInt x) (Integer/parseInt y))) (adopter "1" "2")
小辨析:
装饰者:装饰与被装饰者实现同一个接口,在同一个接口内增强功能。
适配器:适配与被适配者实现的是两个不同的接口。意图是改变接口,满足客户的期望。
外观模式:用一个接口实现子系统的一群接口。意图是,提供子系统的一个简化接口。