Java设计模式及对应的函数式编程范式

创建型模式 – 关注对象创建的模式

单例模式 / Singleton Pattern

在涉及应用状态的地方(比如用于保存设置,或初始化资源的类),常常要求这些类只创建一个实例。

  • [Java] 通常会使用静态成员或静态方法的方式创建单例,为避免调用者通过Java的反射机制或序列化机制生成额外对象,Effective Java一书建议使用Enum单例:
    public enum SingletonEnum {
        INSTANCE;
    
        int value;
    
        public int getValue() {
            return value;
        }
    
        public void setValue(int value) {
            this.value = value;
        }
    }
    
  • [Clojure] Clojure数据结构具有不可变的特性,故定义的“变量”在实质上一定是Java的单例。但在需要时,在Clojure里,仍可以使用atom保存可变的状态。
    ;; 创建atom用于保存全局状态
    (defonce state (atom {}))
    ;; 修改状态
    (swap! state assoc :init? true)
    

工厂方法模式 / Factory Method Pattern

当相同类型存在不同的实现时,可使用工厂模式让调用者使用相同的接口创建不同类型的对象

  • [Java] 在工厂模式里,工厂类(Fac)提供方法(createAnimal)让调用者决定想要创建哪种类型的实例。创建的目标对象需要实现相同接口或继承同一抽象类(Animal)。
    interface Animal{
        String name();
    }
    
    enum AnimalType{
        DOG,
        CAT
    }
    
    class Dog implements Animal{
        @Override
        public String name() {
            return "Dog";
        }
    }
    
    class Cat implements Animal{
        @Override
        public String name() {
            return "Cat";
        }
    }
    
    public class Fac {
        public Animal createAnimal(AnimalType type){
            switch (type){
                case DOG:
                    return new Dog();
                case CAT:
                    return new Cat();
            }
            throw new IllegalArgumentException("Unknown supported AnimalType!");
        }
    }
    
  • [Clojure] 使用函数就能达到相近的目的:
    (defn make-dog []
      {:name "Dog"
       :type :dog})
    
    (defn make-cat []
      {:name "Cat"
       :type :cat})
    
    (defn create-animal [type]
      (case type
        :dog (make-dog)
        :cat (make-cat)))
    

Clojure是动态类型语言,但在运行时数据类型是确定的。在Clojure里,除使用Java类型外,通常还会

  1. 使用map的字段表示类型(示例里的:type字段),或
  2. 使用metadata表示类型。
  3. 使用Record

抽象工厂模式 / Abstract Factory Pattern

在工厂方法模式里,创建不同类型的对象是在同一个方法里完成的,我们要支持新的类型并不方便,而且如果需要创建的对象较复杂,或生成对象类型较多时,这个方法的实现就会变得太复杂。使用抽象工厂模式是更好的选择。

  • [Java] 在抽象工厂模式里,定义工厂接口,生成不同类型的对象使用不同的工厂类。
    abstract class AbsFac {
        public abstract Animal createAnimal();
    }
    
    class DogFactory extends AbsFac{
        @Override
        public Animal createAnimal() {
            return new Dog();
        }
    }
    
    class CatFatory extends AbsFac{
    
        @Override
        public Animal createAnimal() {
            return new Cat();
        }
    }
    
    public class AbsFacDemo{
        public static void main(String[] args) {
            Animal animal =  new DogFactory().createAnimal();
            System.out.println(animal.name());
        }
    }
    
  • [Clojure] 与工厂方式模式相似,只需要使用函数:
    (defn create-animal [creator]
      (creator))
    
    (defn dog-creator []
      {:name "Dog"
       :type :dog})
    
    (defn cat-creator []
      {:name "Cat"
       :type :cat})
    
    (create-animal dog-creator)
    ;; => {:name "Dog", :type :dog}
    

建造者模式 / Builder Pattern

建造者模式通常用于创建对象过程较复杂,或需要传入较多参数的场景。

与抽象工厂模式类似,在建造者模式里也可以使用抽象类或接口的形式,对不同类型使用不同的实现。

  • [Java] 在下面的例子中,MyRobotBuilder#build方法生成Robot实例并完成注册。如有必要,还可以在Builder中添加方法允许调用者传入参数进一步控制实例化的过程。
    public class Robot {
        private int numMotors;
        private boolean ir;
        private String brand;
        private long internalId;
    
        public Robot(int numMotors, boolean ir, String brand, long internalId) {
            this.numMotors = numMotors;
            this.ir = ir;
            this.brand = brand;
        }
    
        public void register() {
            // register robot to network
        }
    }
    
    
    interface RobotBuilder{
        Robot build();
    }
    
    public class MyRobotBuilder implements RobotBuilder{
        public Robot build(){
            Robot robot = new Robot(4, true, "MyRobot", System.currentTimeMillis());
            robot.register();
            return robot;
        }
    }
    
  • [Clojure] 初始化所需的一系列操作可以理解为对数据的一个个动作,所以函数依然是解决之道:
    (defn pre-register! [spec]
      ;; register robot to network
      (assoc spec :registered? true))
    
    (defn post-register! [spec]
      (assoc spec :ready? true))
    
    (defn make-robot [spec]
      (->> spec
           (pre-register!)
           (post-register!)))
    
    (defn myrobot-builder []
      (make-robot {:num-motors  4
                   :ir          true
                   :brand       "MyRobot"
                   :internal-id (System/currentTimeMillis)}))
    
    (myrobot-builder)
    ;; => {:num-motors 4, :ir true, :brand "MyRobot", :internal-id 160698888, :registered? true, :ready? true}
    

原型模式 / Prototype Pattern

原型模式通常用于根据已有实例创建新的实例。

例如,假设我们有一个非常复杂的配置需要调试,修改这个配置时,我们通常希望保留上次的配置,以便修改后能对比修改前后的效果或方便地撤消修改。

原型模式的另一种使用场景是对象的某些成员是通过耗资源操作(比如网络请求或数据库查询)获取的,新建对象时想重用这些成员。

  • [Java] 完全复制成员时,原型模式通常使用Java的Cloneable接口:
    public class NukeConfig implements Cloneable{
        private String c1;
        private String c2;
        // many more fields omitted
    
        public NukeConfig(){
            super();
        }
    
        public NukeConfig(NukeConfig conf){
            super();
            this.c1 = conf.c1;
            this.c2 = conf.c2;
            // copy all fields
        }
    
        @Override
        public NukeConfig clone() {
            return new NukeConfig(this);
        }
    }
    
  • [Clojure] 由于不可变数据结构的特性,想要复制一份数据并修改部分字段时非常简单明了:
    (defn loaded-from-db []
      ;; 从数据库读取配置
      )
    
    ;; 默认配置
    (def nuke-config {:c1 "config 1"
                      :c2 "config 2"
                      :c3 (loaded-from-db)
                      ;; more omitted
                      })
    
    ;; 修改并生成新的配置
    (assoc nuke-config :c1 "edited config 1")
    ;; => {:c1 "edited config 1", :c2 "config 2", :c3 nil}
    

结构型模式 – 关注类的组合使用的模式

适配器模式 / Adapter Pattern

组合多个已有类或接口,并提供统一的接口。已有类的接口无法满足调用者接口要求时,我们可以创建新的类提供满足调用者要求的适配器类,这就是适配器模式。

  • [Java] 在下面的示例中,MsgHandler要求参数实现Msg接口,通过使用适配器模式,可以在不修改StringRequest的条件下满足要求:
    interface Msg{
        String getType();
        String getBody();
    }
    
    class StringRequest{
        private String rawInput;
    
        public StringRequest(String rawInput) {
            this.rawInput = rawInput;
        }
    
        public String getRawInput() {
            return rawInput;
        }
    }
    
    class StringRequestAdapter extends StringRequest implements Msg {
        public StringRequestAdapter(String rawInput) {
            super(rawInput);
        }
    
        @Override
        public String getType() {
            return "json";
        }
    
        @Override
        public String getBody() {
            return this.getRawInput();
        }
    }
    
    // our client class,
    class MsgHandler {
        // requires parameter class implements Msg interface.
        public void handle(Msg msg){
            if (msg.getType().equals("json")){
                // ...
            }
        }
    }
    
  • [Clojure] 只需添加一个新函数用于预处理不兼容的参数:
    (defn handle-json-msg [body]
      (println "handle json string"))
    
    (defn handle-xml-msg [body]
      (println "handle xml string"))
    
    (defn msg-handler [msg]
      (case (:type msg)
        :json (handle-json-msg (:body msg))
        :xml  (handle-xml-msg (:body msg))
        (throw (RuntimeException. (str "Unknown msg type: " (:type msg))))))
    
    (def json-string "{\"cmd\":\"send-file\",\"sender\":\"sam\"}")
    
    ;; this is our `adapter`
    (defn str->json-msg [body]
      {:type :json
       :body body})
    
    ;; usage
    (msg-handler (str->json-msg json-string))
    ;; or
    (->> json-string str->json-msg msg-handler)
    

组合模式 / Composite Pattern

组合模式用于需要表示层级结构的场景,比如企业与部门、员工,汽车与零件。

  • [Java] 使用组合模式,CompoundShape将简单的形状组合为复杂的整体:
    interface Drawable{
        void draw();
    }
    
    class Circle implements Drawable{
        private int radius;
    
        public Circle(int radius) {
            this.radius = radius;
        }
    
        public int getRadius() {
            return radius;
        }
    
        @Override
        public void draw() {
            System.out.println("Draw a circle with radius " + radius);
        }
    }
    
    class Rectangle implements Drawable{
        private int width;
        private int height;
    
        public Rectangle(int width, int height) {
            this.width = width;
            this.height = height;
        }
    
        public int getWidth() {
            return width;
        }
    
        public int getHeight() {
            return height;
        }
    
        @Override
        public void draw() {
            System.out.println("Draw a rectangle with width/height" + width  + "/" + height);
        }
    }
    
    class CompoundShape implements Drawable{
        private final List<Drawable> items = new LinkedList<>();
    
        public void addItem(Drawable item){
            this.items.add(item);
        }
    
        @Override
        public void draw() {
            items.forEach((item)->item.draw());
        }
    }
    
  • [Clojure] 与面象对象思想先关注对象再思考其附着的成员及方法不同,函数式思维先关注动作,在Clojure里,层级结构用vectormap表示,结合动态类型,在运行时对目标数据执行相应的动作:
    (defn draw-item [item]
      (println "Drawing" item))
    
    (defn draw [item-or-items]
      (if (sequential? item-or-items)
        (run! draw item-or-items)
        (draw-item item-or-items)))
    
    (defn make-circle [r]
      {:type   :circle
       :radius r})
    
    (defn make-rectangle [width height]
      {:type   :rectangle
       :width  width
       :height height})
    
    (def c1 (make-circle 8))
    (def c2 (make-rectangle 8 10))
    
    (draw [c1 c2])
    ;; Drawing {:type :circle, :radius 8}
    ;; Drawing {:type :circle, :width 8, :height 10}
    

draw-item的实现比较复杂,比如需要支持许多不同的元素,这时应该使用defmulti

装饰模式 / Decorator Pattern

为现有类动态添加新的功能或修改部分实现时,可以使用装饰模式。与使用继承相比,装饰模式有更在的灵活性。

  • [Java] 下面的SmsSender类在BaseSender类基础上添加了新功能,并提供了相同的接口:
    interface Sender{
        void send(Msg msg);
    }
    
    class BaseSender implements Sender{
        @Override
        public void send(Msg msg) {
            System.out.println("Sending msg by email");
        }
    }
    
    class SmsSender implements Sender{
        private Sender baseSender;
    
        public SmsSender(Sender baseSender) {
            this.baseSender = baseSender;
        }
    
        private void sendBySms(Msg msg){
            System.out.println("Send msg by SMS");
        }
    
        @Override
        public void send(Msg msg) {
            baseSender.send(msg);
            sendBySms(msg);
        }
    }
    
    public class FacadePatternDemo {
        public static void main(String[] args) {
            boolean smsEnabled = true;
            Msg msg = null;
            Sender sender = new BaseSender();
            if (smsEnabled){
                sender = new SmsSender(sender);
            }
            sender.send(msg);
        }
    }
    
  • [Clojure] 利用函数是一等公民的特点,将函数作为数据的一部分(例如当作参数或加入配置里)使用。想要修改消息发送send的操作,只需在config里增删需要的具体发送操作就可以了。与上面Java实现相比,更灵活的地方是send函数还允许调用者使用自己传入的函数发送消息。
    (defn email-sender [msg]
      (println "Sending by email:" msg))
    
    (defn sms-sender [msg]
      (println "Sending by sms:" msg))
    
    (defonce config {:x-senders [email-sender sms-sender]})
    
    (defn send
      ([msg]
       (send msg (:x-senders config)))
      ([msg x-senders]
       (run! #(% msg) x-senders)))
    
    (send "hello")
    ;; Sending by email: hello
    ;; Sending by sms: hello
    

代理模式 / Proxy Pattern

为现有类提供代理,让调用者通过代理间接使用这个类,目的是对现有类的访问进行控制

  • [Java] 下面我们使用CachedFeedsProxy代理访问InternetFeeds类,代理类增加了缓存并限制了Feeds来源(source):
    class Feed{
        private final String id;
        private final String title;
        private final String content;
    
        public Feed(String id, String title, String content) {
            this.id = id;
            this.title = title;
            this.content = content;
        }
    
        public String getId() {
            return id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public String getContent() {
            return content;
        }
    }
    
    interface Feeds{
        public List<Feed> loadFeeds();
    }
    
    class InternetFeeds implements Feeds{
        private String source;
    
        public InternetFeeds(String source) {
            this.source = source;
        }
    
        public List<Feed> loadFeeds(){
            System.out.println("Load feeds from URL: " + source);
            return new ArrayList<>();
        }
    }
    
    class CachedFeedsProxy implements Feeds{
        private InternetFeeds internetFeeds;
        private final String feedsUrl = "https://myfeeds.com/xxx";
    
        private final List<Feed> feeds = new ArrayList();
    
        public CachedFeedsProxy() {
            this.internetFeeds = new InternetFeeds(feedsUrl);
        }
    
        public void clearCache(){
            synchronized (feeds){
                feeds.clear();
            }
        }
    
        @Override
        public List<Feed> loadFeeds() {
            if (feeds.isEmpty()){
                synchronized (feeds){
                    if (feeds.isEmpty()){
                        feeds.addAll(internetFeeds.loadFeeds());
                    }
                }
            }
            return feeds;
        }
    }
    
  • [Clojure] 需要限制对已有函数的访问时,只需要创建新函数并在其中添加控制逻辑。下面的feeds函数在load-feeds函数基础上添加了缓存并使用了固定的Feeds来源URL:
    (defn load-feeds [url]
      (println "load feeds from network")
      [])
    
    (defonce feeds-store (atom nil))
    
    (defn feeds []
      (or @feeds-store
          (reset! feeds-store (load-feeds "https://myfeeds.com/xxxx"))))
    
    (defn clear-feeds! []
      (reset! feeds-store nil))
    
    (feeds)
    ;; load feeds from network
    (feeds)
    ;; feeds loaded from cache
    

亨元 / Flyweight Pattern

Flyweight表示拳击比赛中蝇量级选手,即次轻量级选手(拳击手体重必须介于47.63与50.8公斤之间)。

享元模式通过共享对象的方式减少对象的创建,并节约内存消耗。

  • [Java] Cloud含有大量Dot对象,在DotAttr数量较少的情况下,重用DotAttr对象可以缩减内存需求:
    class DotAttr {
        private int radius;
        private String color;
        private boolean filled;
    
        public DotAttr(int radius, String color, boolean filled) {
            this.radius = radius;
            this.color = color;
            this.filled = filled;
        }
    
        public int getRadius() {
            return radius;
        }
    
        public String getColor() {
            return color;
        }
    
        public boolean isFilled() {
            return filled;
        }
    }
    
    class Dot {
        private int x;
        private int y;
        private DotAttr dotAttr;
    
        public Dot(int x, int y, DotAttr dotAttr) {
            this.x = x;
            this.y = y;
            this.dotAttr = dotAttr;
        }
    
        public DotAttr getBaseCircle() {
            return dotAttr;
        }
    
        public int getX() {
            return x;
        }
    
        public int getY() {
            return y;
        }
    
        public void draw(){
            System.out.println("Plot dot at " + x + "/" + y);
        }
    }
    
    class DotAttrFactory {
        private static final Map<Integer, DotAttr> baseCircleMap = new HashMap();
        public static DotAttr getBaseCircle(int radius, String color, boolean filled){
            int key = new StringBuilder()
                    .append(radius)
                    .append(color)
                    .append(filled)
                    .toString()
                    .hashCode();
    
            DotAttr dotAttr = baseCircleMap.get(key);
            if (Objects.isNull(dotAttr)){
                dotAttr = new DotAttr(radius, color, filled);
            }
            return dotAttr;
        }
    }
    
    class Cloud{
        List<Dot> dots = new LinkedList<>();
        public void addDot(int x, int y, int radius, String color, boolean filled){
            // reuse DotAttr objects to conserve memory usage
            DotAttr dotAttr = DotAttrFactory.getBaseCircle(radius, color, filled);
            dots.add(new Dot(x, y, dotAttr));
        }
        public void draw(){
            dots.forEach(dot-> dot.draw());
        }
    }
    
  • [Clojure] get-attr函数使用atom缓存实现数据重用。make-cloud函数使用transient是为了避免生成大量的不可变中间数据(即loop里的数据)引起的资源消耗。
    ;; Assuming the number of unique (radius, color, filled?) attributes is much
    ;; smaller than number of unique (x, y) attributes
    (defonce dot-cache (atom {}))
    
    (defn get-attr [radius color filled?]
      (let [id (hash [radius color filled?])]
        (or (get @dot-cache id)
            (let [attr {:radius  radius
                        :color   color
                        :filled? filled?}]
              (swap! dot-cache assoc id attr)
              attr))))
    
    (defn make-dot [x y radius color filled?]
      {:x    x
       :y    y
       :attr (get-attr radius color filled?)})
    
    ;; create a cloud of dots
    (def cloud-size 10000)
    (defn make-cloud []
      (loop[i     0
            cloud (transient [])]
        (if (> i cloud-size)
          (persistent! cloud)
          (let [dot (make-dot (rand-int 10000) (rand-int 10000) (rand-int 10) (rand-nth ["red" "blue" "green"]) (rand-nth [true false]))]
            (recur (inc i) (conj! cloud dot))))))
    
    ;; (make-cloud)
    

外观模式 / Facade Pattern

创建一个类,提供一个接口用于访问其它多个类。

  • [Java] NotificationFacade类提供notifyWithTarget方法用于访问SmsNotifier, EmailNotifier的方法:
    interface Notifier{
        void sendNotification(Event event);
    }
    
    class SmsNotifier implements Notifier{
        @Override
        public void sendNotification(Event event) {
    
        }
    }
    
    class EmailNotifier implements Notifier{
    
        @Override
        public void sendNotification(Event event) {
    
        }
    }
    
    enum NotifyTarget{
        EMAIL,
        SMS
    }
    
    class NotificationFacade {
        public void notifyWithTarget(Event event, NotifyTarget target){
            if (!shouldNotify(event)){
                return;
            }
            Notifier notifier = null;
            if (NotifyTarget.EMAIL.equals(target)){
                notifier = new EmailNotifier();
            }else if (NotifyTarget.SMS.equals(target)){
                notifier = new SmsNotifier();
            }else{
                throw new IllegalArgumentException("Unsupported notification target: " + target);
            }
            notifier.sendNotification(event);
        }
    
        private boolean shouldNotify(Event event) {
            // check if we should ignore this message
            return true;
        }
    }
    
  • [Clojure] notify函数作为入口提供了对email-notifier, sms-notifier的调用:
    (defn email-notifier [event]
      (println "Notify by email:" event))
    
    (defn sms-notifier [event]
      (println "Notify by sms:" event))
    
    (defn- should-notify? [event]
      true)
    
    (defn notify [event target]
      {:pre [(#{:sms :email} target)]}
      (when (should-notify? event)
        (case target
          :email (email-notifier event)
          :sms   (sms-notifier event))))
    
    (notify {:time "2020-10-10 11:23:32" :msg "File received."} :sms)
    ;; Notify by sms: {:time 2020-10-10 11:23:32, :msg File received.}
    

桥接模式 / Bridge Pattern

将可独立变化的两种类分离或抽象出来,并通过桥接类提供访问。

  • [Java] 将Shape类与Color类独立,通过ColorfulShape访问。需要添加或修改ShapeColor类时,不会影响调用者使用ColorfulShape类:
    interface Shape{
    
    }
    
    class Triangle implements Shape{
    
    }
    
    class Rectangle implements Shape{
    
    }
    
    class Polygon implements Shape{
    
    }
    
    class Color implements Shape{
    
    }
    
    class ColorfulShape {
        private Shape shape;
        private Color color;
    
        public ColorfulShape(Shape shape, Color color) {
            this.shape = shape;
            this.color = color;
        }
    
        public Shape getShape() {
            return shape;
        }
    
        public Color getColor() {
            return color;
        }
    }
    
  • [Clojure] 使用函数:
    (defn make-cirlce [radius]
      {:type   :circle
       :radius radius})
    
    (defn make-rectangle [wdith height]
      {:type   :rectangle
       :width  width
       :height height})
    
    (defn make-color [color]
      {:color color})
    
    ;; You can pass shape data to function,
    (defn make-colorful-shape [shape color]
      (assoc shape :color color))
    
    ;; Alternatively, you can also pass shape creator
    (defn make-colorfule-shape [shape-creator color]
      (assoc (shape-creator) :color color))
    

行为型模式:关注对象行为的模式

行为型模式关注对象的行为,即对象的方法。

面象对象语言无法将独立于对象使用函数,可以说几乎所有的行为模式存在的原因皆源于此限制。

模板模式 / Template Pattern

模板定义了多个类共有的行为。

如果多个类存在明显重复的代码,此时可以把这些重复代码提取出来,作为方法加入到新建的抽象类(即模板类)中。

  • [Java] Request抽象类为HttpRequestWebsocketReqeust提供了公用的asyncSend方法:
    interface ResponseHandler{
        boolean handle(Response response);
    }
    
    abstract class Request{
        abstract Response send();
        CompletableFuture<Boolean> asyncSend(ResponseHandler handler){
            return CompletableFuture.supplyAsync(this::send)
                    .thenApply(handler::handle);
        }
    }
    
    class HttpRequest extends Request{
        @Override
        Response send() {
            // todo send http request and get response
            return null;
        }
    }
    
    
    class WebsocketRequest extends Request{
        @Override
        Response send() {
            // todo send websocket request and get response
            return null;
        }
    }
    
  • [Clojure] 只需把重复的代码提取为函数。对于必须在编译时重复使用的代码,还可以使用宏/Macro

解释器模式 / Interpreter Pattern

对于同类问题,定义文法并实现解释器接口,以便供调用者能通过此接口方便地解决同类问题。

  • [Java] 下面使用解释器模式实现了一个忽略优先级的简单计算器,将字符串分解为Token后,解析为Symbol进行运算:
    import java.util.*;
    
    interface Symbol{
        double interpret();
    }
    
    class DoubleSymbol implements Symbol{
        private double value;
    
        public DoubleSymbol(double value) {
            this.value = value;
        }
    
        @Override
        public double interpret() {
            return value;
        }
    
        @Override
        public String toString() {
            return "DoubleSymbol{" +
                    "value=" + value +
                    '}';
        }
    }
    
    class AddSymbol implements Symbol{
        private List<Symbol> operands;
    
        public AddSymbol(List<Symbol> operands) {
            this.operands = operands;
        }
    
        @Override
        public double interpret() {
            double r = operands.stream().mapToDouble(Symbol::interpret).sum();
            return r;
        }
    }
    
    class MultiplySymbol implements Symbol{
        private List<Symbol> operands;
    
        public MultiplySymbol(List<Symbol> operands) {
            this.operands = operands;
        }
    
        @Override
        public double interpret() {
            return operands.stream()
                    .mapToDouble(Symbol::interpret)
                    .reduce((a, b)-> a*b)
                    .orElseThrow();
        }
    }
    
    class MinusSymbol implements Symbol{
        private List<Symbol> operands;
    
        public MinusSymbol(List<Symbol> operands) {
            this.operands = operands;
        }
        @Override
        public double interpret() {
            return operands.stream()
                    .mapToDouble(Symbol::interpret)
                    .reduce((a, b)-> a-b)
                    .orElseThrow();
        }
    }
    
    class DivideSymbol implements Symbol{
        private List<Symbol> operands;
    
        public DivideSymbol(List<Symbol> operands) {
            this.operands = operands;
        }
        @Override
        public double interpret() {
            return operands.stream()
                    .mapToDouble(Symbol::interpret)
                    .reduce((a, b)-> a/b)
                    .orElseThrow();
        }
    }
    
    
    class ExpressionParser {
        private List<Symbol> operands = new LinkedList();
        private Symbol operator;
        private String expression;
    
        public ExpressionParser(String expression) {
            this.expression = expression;
        }
    
        public double parse(){
            String[] tokens = expression.split("\\s+");
            Arrays.stream(tokens)
                    .forEach((token)->{
                        if (isOperator(token)){
                            operator = operatorFromToken(token);
                        }else{
                            operands.add(new DoubleSymbol(Double.valueOf(token)));
                            if (!Objects.isNull(operator)){
                                double r = operator.interpret();
                                operands.clear();
                                operands.add(new DoubleSymbol(r));
                                operator = null;
                            }
                        }
                    });
            return operands.get(0).interpret();
        }
    
        private Symbol operatorFromToken(String token) {
            switch (token){
                case "+":
                    return new AddSymbol(operands);
                case "-":
                    return new MinusSymbol(operands);
                case "*":
                    return new MultiplySymbol(operands);
                case "/":
                    return new DivideSymbol(operands);
            }
            throw new IllegalArgumentException("Unknown token " + token);
        }
    
    
        private boolean isOperator(String s) {
            return Objects.equals("+", s)
                    || Objects.equals(s, "-")
                    || Objects.equals(s, "*")
                    || Objects.equals(s, "/");
        }
    }
    
    public class InterpreterPatternDemo {
        public static void main(String[] args) {
            String exp = "5 + 3.2 / 1 - 3.2";
            double v = new ExpressionParser(exp).parse();
            System.out.println(String.format("%s = %f", exp, v));
        }
    }
    
  • [Clojure] 解释器模式本质上是对拆分的token进行不同的操作,下面是使用loop的实现:
    (def operators {"+" +
                    "-" -
                    "*" *
                    "/" /})
    
    (defn calculate[s]
      (let [tokens (s/split s #"\s+")]
        (loop [tokens   tokens
               operand  nil
               operator nil]
          (if-let [f (first tokens)]
            (if-let [op (operators f)]
              (recur (rest tokens) operand op)
              (if operator
                (recur (rest tokens) (operator operand (Double/valueOf f)) nil)
                (recur (rest tokens) (Double/valueOf f) operator)))
            operand))))
    
    (calculate "5 + 3.2 / 1 - 3")
    ;; => 5.199999999999999
    

策略模式 / Strategy Pattern

将同类功能的不同实现提取出来,作为一系列可以互换的独立类。

  • [Java] AlipayPayment, WechatPayment 均实现了PaymentStrategy策略,由调用者决定使用何种支付策略:
    interface PaymentStrategy{
        boolean pay(long amount);
    }
    
    class AlipayPayment implements PaymentStrategy{
        @Override
        public boolean pay(long amount) {
            return false;
        }
    }
    
    class WechatPayment implements PaymentStrategy{
    
        @Override
        public boolean pay(long amount) {
            return false;
        }
    }
    
    enum PaymentType{
        WECHAT,
        ALIPAY
    }
    
    class Order {
        private PaymentType paymentType;
        private long amount;
    
        public boolean pay(){
            PaymentStrategy strategy = null;
            switch (paymentType){
                case WECHAT:
                    strategy = new WechatPayment();
                    break;
                case ALIPAY:
                    strategy = new AlipayPayment();
                    break;
                default:
                    System.out.println("Invalid payment type!");
                    return false;
            }
    
            return strategy.pay(amount);
        }
    
        public PaymentType getPaymentType() {
            return paymentType;
        }
    
        public long getAmount() {
            return amount;
        }
    
        public Order(PaymentType paymentType, long amount) {
            this.paymentType = paymentType;
            this.amount = amount;
        }
    }
    
  • [Clojure] 在函数式语言里行为以函数表示,而函数又是可作为数据使用的一等公民,所以只需为不同的策略定义不同的函数,调用者可自由决定使用哪种策略:
    (defn pay-by-aplipay [amount]
      ;; making payments
      false)
    
    (defn pay-by-wechat [amount]
      ;; making payments
      false)
    
    (def channel->payment-method
      {:alipay pay-by-aplipay
       :wechat pay-by-wechat})
    
    (defn pay [{:keys [amount channel]}]
      ((channel->payment-method channel) amount))
    

状态模式 / State Pattern

对象的行为随其状态变化而改变。

  • [Java] Page类对象的行为依据其内部的State状态而变(这其实是一个有限状态机):
    abstract class State{
        Page page;
    
        public State(Page page) {
            this.page = page;
        }
    
        public void changeState(State state){
            this.page.setState(state);
        }
    
        abstract void onLeft();
        abstract void onRight();
        abstract void onUp();
        abstract void onDown();
        abstract void onEsc();
        abstract void onOk();
    }
    
    class ToolState extends State{
    
        public ToolState(Page page) {
            super(page);
            System.out.println("Opening tools page");
        }
    
        @Override
        void onLeft() {
            this.changeState(new HomeState(page));
        }
    
        @Override
        void onRight() {
            // switch tap on the right side
        }
    
        @Override
        void onUp() {
    
        }
    
        @Override
        void onDown() {
    
        }
    
        @Override
        void onEsc() {
            this.changeState(new HomeState(page));
        }
    
        @Override
        void onOk() {
            // open state for sub-itmes under Tools
        }
    }
    
    class HomeState extends State{
    
        public HomeState(Page page) {
            super(page);
            System.out.println("Opening home page");
        }
    
        @Override
        public void onLeft() {
    
        }
    
        @Override
        public void onRight() {
            this.changeState(new ToolState(page));
        }
    
        @Override
        public void onUp() {
    
        }
    
        @Override
        public void onDown() {
    
        }
    
        @Override
        public void onEsc() {
            this.changeState(new PowerOffState(page));
        }
    
        @Override
        public void onOk() {
    
        }
    }
    
    class Page {
        private State state;
    
        public State getState() {
            return state;
        }
    
        public void setState(State state) {
            this.state = state;
        }
    
        public Page() {
            this.state = new HomeState(this);
        }
    
        public void left(){
            this.getState().onLeft();
        }
    
        public void right(){
            this.getState().onRight();
        }
    
        public void down(){
            this.getState().onDown();
        }
    
        public void up(){
            this.getState().onUp();
        }
    
        public void ok(){
            this.getState().onOk();
        }
    
        public void esc(){
            this.getState().onEsc();
        }
    }
    
    public class StatePatternDemo {
        public static void main(String[] args) {
            Page page = new Page();
            page.right();
            page.esc();
            page.esc();
            page.ok();
        }
    }
    
  • [Clojure] ui-fsm 模拟UI事件的有限状态机,on-event函数将界面、事件及事件处理函数关联起来并分发调用:
    ;; Actions which will be attached to specific UI states
    (defn open-item-at-home []
      (println "opening item at home page"))
    
    (defn open-item-at-tools []
      (println "opening item at tools page"))
    
    (defn next-item-at-home []
      (println "select next item at home page"))
    
    (defn next-item-at-tools []
      (println "select next item at tools page"))
    
    (defn home-page-renderer []
      (println "Home page ui"))
    
    (defn tools-page-renderer []
      (println "Tools page ui"))
    
    (defn shutdown []
      (println "shutting down"))
    
    (defn power-off-popup-renderer []
      (println "showing power off popup"))
    
    (defn close-popup []
      (swap! app dissoc :popup)
      (println "closing popup"))
    
    ;; Our global state
    (def app (atom {:view :home-page}))
    
    
    ;; creating our FSM
    (def ui-fsm
      {:home-page       {:type  :view
                         :down  {:x-func [next-item-at-home]}
                         :right {:view :tools-page}
                         :esc   {:popup :power-off-popup}
                         :ok    {:x-func [open-item-at-home]}
                         :view  home-page-renderer
                         :title "Home"}
       :power-off-popup {:type :popup
                         :view power-off-popup-renderer
                         :ok   {:x-func [shutdown]}
                         :esc  {:x-func [close-popup]}}
       :tools-page      {:type  :view
                         :down  {:x-func [next-item-at-tools]}
                         :right {:view :files-page}
                         :esc   {:view :home-page}
                         :ok    {:x-func [open-item-at-tools]}
                         :view  tools-page-renderer
                         :title "Tools"} })
    
    (defn event->action [event]
      (let [element (or (:popup @app) (:view @app))]
        (get-in ui-fsm [element event])))
    
    ;; Event handler
    (defn on-event [event]
      (when-let [{:keys [view popup x-func]} (event->action event)]
        (when view
          (swap! app assoc :view view))
        (when popup
          (swap! app assoc :popup popup))
        (when-let [render (get-in ui-fsm [(or view popup) :view])]
          (render))
        (doseq [f x-func]
          (f))))
    
    ;; demo
    (run! on-event [:right :esc :esc :ok])
    ;; Tools page ui
    ;; Home page ui
    ;; showing power off popup
    ;; shutting down
    

观察者模式 / Observer Pattern

观察者模式的使用场景是当一个事件发生时(即一个类的状态改变时),需要将此事件通知另一个或多个类。

  • [Java] EventManager在收到事件时通过onReceive方法调用观察者evtHandles处理事件:
    interface Event{
    }
    
    interface EvtHandler {
        // Event handler: must not throw exception
        void handle(Event event);
    }
    
    class EventManager{
        List<EvtHandler> evtHandlers = new LinkedList<>();
    
        // Add a event handle
        void registerHandler(int type, EvtHandler handler){
            this.evtHandlers.add(handler);
        }
    
        void onReceive(Event event){
            // Handle event using registered handlers
           evtHandlers.forEach((handler)->handler.handle(event));
        }
    }
    
  • [Clojure] 使用函数可以简单直观地实现观察者模式:
    (defonce event-handlers (atom []))
    
    (defn register-handler [handler]
      (swap! event-handlers conj handler))
    
    (defn on-event [event]
      (run! #(% event) @event-handlers))
    
    ;; usage
    (defn print-handler [event]
      (println "Recv"event))
    
    (register-handler print-handler)
    
    (on-event {:type :text :msg "hello"})
    ;; Recv {:type :text, :msg hello}
    

备忘录模式 / Memento Pattern

备忘录模式作为保存对象历史状态的一种模式。

  • [Java] Memento对象对应Page类的历史状态,History类用于添加状态或回退到历史状态。注意这里的实现非线程安全。
    class History{
        private LinkedList<Memento> mementoList = new LinkedList<>();
        public void push(Memento memento){
            this.mementoList.add(memento);
        }
    
        public boolean isEmpty(){
            return this.mementoList.isEmpty();
        }
    
        public Memento pop(){
            return this.mementoList.removeLast();
        }
    }
    
    class Memento{
        private State state;
        private Page page;
    
        public Memento(State state, Page page) {
            this.state = state;
            this.page = page;
        }
    
        public void restore(){
            page.setState(state);
        }
    }
    
    class MyApp{
        private Page page;
        private History history;
    
        public MyApp() {
            this.page = new Page();
            this.history = new History();
        }
    
        void toState(State state){
            this.page.setState(state);
            history.push(new Memento(state, page));
        }
    
        void back(){
            if (!history.isEmpty()){
                history.pop();
            }
        }
    }
    
  • [Clojure] 用historyapp 两个atom表示历史状态与当前状态:
    (defonce history (atom []))
    (defonce app (atom {:state :home}))
    
    (defn display [state]
      (println "on page:" state))
    
    (defn to-state [state]
      (swap! history conj (:state @app))
      (swap! app assoc :state state)
      (display state))
    
    (defn back []
      (when-let [state (peek @history)]
        (swap! history pop)
        (display state)))
    
    (to-state :tools)
    ;; on page: :tools
    (to-state :files)
    ;; on page: :files
    (back)
    ;; on page: :tools
    (back)
    ;; on page: :home
    

中介者模式 / Mediator Pattern

对象与对象之间存在大量耦合关系时,使用中介者模式处理对象之间的交互,让耦合的对象解耦。在组件越来越来多时,中介者类会趋于复杂化。

  • [Java] FeedbackPage 作为中介者类,处理不同元素,即 Component 对象间的事件交互。
    interface Mediator{
        void submit();
        void handleContentChange(String content);
        void handleEmailChange(String email);
    }
    
    abstract class Component{
        protected Mediator mediator;
    
        public Component(Mediator mediator) {
            this.mediator = mediator;
        }
    }
    
    
    class ButtonComponent extends Component{
        private String label;
    
        public ButtonComponent(Mediator mediator) {
            super(mediator);
        }
    
        public void onClick() {
            this.mediator.submit();
        }
    
        public String getLabel() {
            return label;
        }
    
        public void setLabel(String label) {
            this.label = label;
        }
    }
    
    class EmailComponent extends Component{
        private String label;
        private String value;
    
        public EmailComponent(Mediator mediator) {
            super(mediator);
        }
    
        public String getLabel() {
            return label;
        }
    
        public void setLabel(String label) {
            this.label = label;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return value;
        }
    
        public void onChange(String newEmail){
            this.value  = newEmail;
            this.mediator.handleEmailChange(newEmail);
        }
    }
    
    class TextfieldComponent extends Component{
        private String label;
        private String value;
    
        public TextfieldComponent(Mediator mediator) {
            super(mediator);
        }
    
        public void onChange(String newValue){
            this.value = newValue;
            this.mediator.handleContentChange(newValue);
        }
    
    
        public String getLabel() {
            return label;
        }
    
        public void setLabel(String label) {
            this.label = label;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    }
    
    class HintComponent extends Component{
        private String label;
        private boolean hidden;
    
    
        public HintComponent(Mediator mediator) {
            super(mediator);
            this.label = "";
            this.hidden = true;
        }
    
        public void setLabel(String label){
            this.label = label;
        }
    
        public boolean isHidden() {
            return hidden;
        }
    
        public void setHidden(boolean hidden) {
            this.hidden = hidden;
        }
    }
    
    class FeedbackPage implements Mediator {
        private final int MAX_FEEDBACK_LENGTH = 3000;
    
        private final HintComponent errorHint;
    
    
        private final ButtonComponent submitButton;
        private final EmailComponent emailInput;
        private final TextfieldComponent feedbackContent;
    
        public FeedbackPage() {
            this.submitButton = new ButtonComponent(this);
            this.submitButton.setLabel("Submit");
    
            this.emailInput = new EmailComponent(this);
            this.emailInput.setLabel("Email");
            this.feedbackContent = new TextfieldComponent(this);
            this.feedbackContent.setLabel("Content");
            this.errorHint = new HintComponent(this);
        }
    
        public boolean validateContent(String content){
            if (StringUtils.isNullOrEmpty(content)){
                errorHint.setLabel("Please enter feedback content");
                errorHint.setHidden(false);
                return false;
            }
            if (content.length() > MAX_FEEDBACK_LENGTH){
                errorHint.setLabel(String.format("Please enter limit your feedback to %d characters", MAX_FEEDBACK_LENGTH) );
                errorHint.setHidden(false);
                return false;
            }
            errorHint.setHidden(true);
            return true;
        }
    
        public void handleContentChange(String content){
            validateContent(content);
        }
    
        @Override
        public void handleEmailChange(String email) {
            validateEmail(email);
        }
    
    
        public void submit(){
            if (validateEmail(emailInput.getValue()) && validateContent(feedbackContent.getValue())) {
                // submit this feedback
                System.out.println("Submitting feedback");
            }else{
                System.err.println("Invalid feedback params");
            }
        }
    
        private boolean validateEmail(String value) {
            if (StringUtils.isNullOrEmpty(value)){
                errorHint.setLabel("Invalid email");
                errorHint.setHidden(false);
                return false;
            }
            errorHint.setHidden(true);
            return true;
        }
    }
    
  • [Clojure] 下面的例子以 reagent 为例。利用函数即数据特点,状态、界面、事件处理函数有清晰的区分,之间的调用也十分容易:
    ;; states
    (defonce app-store (atom {:email   ""
                              :content ""
                              :error   nil}))
    
    (def max-allowed-content-length 3000)
    
    ;; validators
    (defn- email-validator [val]
      (cond
        (s/blank? val) {:error "Email can not be empty"}
        ;; you may add other email validation
        :else          nil))
    
    (defn- content-validator [val]
      (cond
        (s/blank? val)                             {:error "Please enter feedback content"}
        (> (count val) max-allowed-content-length) {:error (format "Content should be less than %d characters" max-allowed-content-length)}
        :else                                      nil))
    
    (def feedback-valdiators
      {:email   email-valdiator
       :content content-validator})
    
    ;; event handlers
    (defn set-val [ky]
      (fn [e]
        (let [val (-> e .-target .-value)]
          (swap! app-store assoc ky val)
          (when-let [validator (get feedback-valdiators ky)]
            (when-let [{:keys [error]} (validator val)]
              (swap! app-store assoc :error error))))))
    
    ;; ui rendering
    (defn email-field []
      [:div
       [:label "Email"]
       [:input {:type      :text
                :value     (:email @app-store)
                :on-change (set-val :email)}]])
    
    (defn content-field []
      [:div
       [:label "Content"]
       [:input {:type      :text
                :value     (:content @app-store)
                :on-change (set-val :content)}]])
    
    (defn feedback-page []
      [:div
       [email-field]
       [content-field]
       (when-let [error (:error @app-store)]
         [:div.error error])])
    

命令模式 / Command Pattern

将行为以命令的方式封装在对象中,使用其与这些命令的发起者解耦。

  • [Java] 使用命令模式,Form里组件的行为通过Command接口实现,对于比较简单的组件可以利用Java 8的Lambda表达式减少代码量:
    interface Command{
        void execute();
    }
    
    class Button {
        private String label;
        private Command command;
    
        public Button(String label, Command command) {
            this.label = label;
            this.command = command;
        }
    
        public void onClick(){
            this.command.execute();
        }
    }
    
    class Input{
        private String label;
        private String value;
        private Command command;
    
        public Input(String label, Command command) {
            this.label = label;
            this.value = "";
            this.command = command;
        }
    
        public void onChange(String value){
            this.value = value;
            command.execute();
        }
    
        public void reset() {
            this.value = "";
        }
    
        public String getValue() {
            return value;
        }
    }
    
    class ChangeCommand implements Command{
        private Form form;
        public ChangeCommand(Form form) {
            this.form= form;
        }
    
        @Override
        public void execute() {
            System.out.println("Input value changed to " + form.getInput().getValue());
            // you may perform other action here, eg., validation
        }
    }
    
    class Form{
        private Button submitButton;
        private Button cancelButton;
        private Input input;
    
        public Form() {
            init();
        }
    
        public void init(){
            this.input = new Input("Enter a value: ", new ChangeCommand(this));
            // if the command is simple, you can use lambda expression
            this.submitButton = new Button("Submit", this::submit);
            this.cancelButton = new Button("Reset", this::reset);
        }
    
        private void submit(){
            String value = input.getValue();
            System.out.println("Submitting value: " + value);
        }
    
        private void reset(){
            input.reset();
        }
    
        public Button getSubmitButton() {
            return submitButton;
        }
    
        public Button getCancelButton() {
            return cancelButton;
        }
    
        public Input getInput() {
            return input;
        }
    }
    
  • [Clojure] 由于Java里的方法不能独立于对象存在,所以才需要使用命令模式。在函数可作为数据对象使用的语言里,只需把函数当作数据使用即可满足需求。 如下面的on-click, on-change属性:
    (defonce val-store (atom ""))
    
    (defn make-form []
      (let [submit    #(println "submit value" @val-store)
            on-change #(reset! val-store %)]
        [:div
         [:button {:value    "Submit"
                   :on-click submit}]
         [:input {:value     (or @val-store "")
                  :on-change on-change}]]))
    

访问者模式 / Visitor Pattern

需要在现有类上增加行为,且不能修改这些类或这些类有多个时,可以使用访问者模式,将需要增加的行为加入新建的访问者类中。

  • [Java] 使用访问者模式,TranslateVisitor提供了移动一组图形坐标原点的功能:
    class Circle implements Shape{
        private int x;
        private int y;
        private int radius;
    
        public Circle(int x, int y, int radius) {
            this.x = x;
            this.y = y;
            this.radius = radius;
        }
    
        public int getX() {
            return x;
        }
    
        public void setX(int x) {
            this.x = x;
        }
    
        public int getY() {
            return y;
        }
    
        public void setY(int y) {
            this.y = y;
        }
    
        public int getRadius() {
            return radius;
        }
    
        public void setRadius(int radius) {
            this.radius = radius;
        }
    }
    
    class Rectangle implements Shape{
        private int x;
        private int y;
        private int w;
        private int h;
    
        public Rectangle(int x, int y, int w, int h) {
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
        }
    
        public int getX() {
            return x;
        }
    
        public void setX(int x) {
            this.x = x;
        }
    
        public int getY() {
            return y;
        }
    
        public void setY(int y) {
            this.y = y;
        }
    
        public int getW() {
            return w;
        }
    
        public void setW(int w) {
            this.w = w;
        }
    
        public int getH() {
            return h;
        }
    
        public void setH(int h) {
            this.h = h;
        }
    }
    
    interface Visitor{
        void visitCircle(Circle circle);
        void visitRectangle(Rectangle rectangle);
    }
    
    class TranslateVisitor implements Visitor{
        private int x;
        private int y;
    
        public TranslateVisitor(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public void visitCircle(Circle circle) {
            circle.setX(circle.getX() + x);
            circle.setY(circle.getY() + y);
        }
    
        @Override
        public void visitRectangle(Rectangle rectangle) {
            rectangle.setX(rectangle.getX() + x);
            rectangle.setY(rectangle.getY() + y);
        }
    }
    
    
    public class VisitorPatterDemo {
        public static void main(String[] args) {
            Circle circle = new Circle(0, 0, 10);
            Rectangle rectangle = new Rectangle(10, 10, 20, 10);
    
            // translate the origin
            TranslateVisitor translateVisitor = new TranslateVisitor(50, 50);
            translateVisitor.visitCircle(circle);
            translateVisitor.visitRectangle(rectangle);
        }
    }
    
  • [Clojure] 新的行为对应函数,translate 函数移动图形的原点,trans-50 函数向右上角移动图形原点50个单位:
    (defn make-circle [x y r]
      {:type   :circle
       :x      x
       :y      y
       :radius r})
    
    (defn make-rectangle [x y width height]
      {:type   :rectangle
       :x      x
       :y      y
       :width  width
       :height height})
    
    (defn translate [shape cx cy]
      (-> shape
          (update :x (partial + cx))
          (update :y (partial + cy))))
    
    (def c1 (make-circle 0 0 8))
    ;; {:type :circle, :x 0, :y 0, :radius 8}
    
    (def r1 (make-rectangle 10 10 8 10))
    ;; {:type :rectangle, :x 10, :y 10, :width 8, :height 10}
    
    (defn trans-50 [shape]
      (translate shape 50 50))
    
    (println (trans-50 c1))
    ;; {:type :circle, :x 50, :y 50, :radius 8}
    
    (println (trans-50 r1))
    ;; {:type :rectangle, :x 60, :y 60, :width 8, :height 10}
    

责任链模式 / Chain of Responsibility Pattern

将请求的发送者和接收者解耦,允许多个请求者依序处理请求。

  • [Java] RequestHandler 的实例用于链式处理请求:
    abstract class RequestHandler{
        private RequestHandler next;
    
        public RequestHandler link(RequestHandler next){
            this.next = next;
            return next;
        }
    
        public abstract boolean handle(Request request);
    
        protected boolean handleNext(Request request) {
            if (next == null) {
                return true;
            }
            return next.handle(request);
        }
    }
    
    class LoggingHanlder extends RequestHandler {
    
        @Override
        public boolean handle(Request request) {
            System.out.println("Request received: " + request);
            return this.handleNext(request);
        }
    }
    
    class AuthHanlder extends RequestHandler {
    
        @Override
        public boolean handle(Request request) {
            // add logic to check user session
            // ...
            System.out.println("Checking authentication in request ...");
            return this.handleNext(request);
        }
    }
    
    class BizHanlder extends RequestHandler {
    
        @Override
        public boolean handle(Request request) {
            // add logic to handle request biz
            // ...
            System.out.println("Handling business in request ...");
            return this.handleNext(request);
        }
    }
    
    
    public class ChainOfResponsibilityPatternDemo {
        public static void main(String[] args) {
            LoggingHanlder loggingHanlder = new LoggingHanlder();
            loggingHanlder
                    .link(new AuthHanlder())
                    .link(new BizHanlder());
            Request request = new HttpRequest();
            loggingHanlder.handle(request);
        }
    }
    
    // Output:
    // Request received: patterns.HttpRequest@2c9f9fb0
    // Checking authentication in request ...
    // Handling business in request ...
    
  • [Clojure] 使用高阶函数(higher order function)可以很容易得实现责任链的需求。例如下面的log-wrapper, auth-wrapper 均为处理请求的链式操作函数。更多细节可参考Clojure Web库Ring的设计理念
    (defn log-wrapper [handler]
      (fn [request]
        (println "Logging request:" request)
        (handler request)))
    
    (defn- make-response [status payload]
      {:type :response :status status :payload payload})
    
    (defn- auth? [request]
      (:secret request))
    
    (defn auth-wrapper [handler]
      (fn [request]
        (println "Auth check")
        (if (auth? request)
          (handler request)
          (make-response -1 "denied"))))
    
    (defn biz-handler [request]
      (println "Handling biz logic")
      (make-response 0 "ok"))
    
    (def my-app (->> biz-handler auth-wrapper log-wrapper))
    
    (my-app {:type :request :payload "hello, patterns"})
    ;; Logging request: {:type :request, :payload hello, patterns}
    ;; Auth check
    ;; => {:type :response, :status -1, :payload "denied"}
    
    (my-app {:type :request :secret "ascecret" :payload "hello, patterns"})
    ;; Logging request: {:type :request, :secret ascecret, :payload hello, patterns}
    ;; Auth check
    ;; Handling biz logic
    ;; => {:type :response, :status 0, :payload "ok"}
    

迭代器模式 / Iterator Pattern

迭代器模式按顺序访问集合元素,不关心元素的具体实现。迭代器只能被消费一次,Java里迭代器通常不会单独出现,而是配合集合或Stream使用,例如Java的集合可以通过iterator()方法转换为迭代器。

  • [Java] TeamMembersIter 是一个迭代器实现:
    import java.util.Iterator;
    import java.util.NoSuchElementException;
    
    class TeamMembersIter implements Iterator<String> {
        private final String[] members = {"Sam", "Olivia", "Freya", "Emily"};
        private int index = 0;
    
        @Override
        public boolean hasNext() {
            return index != members.length;
        }
    
        @Override
        public String next() {
            if (index>= members.length){
                throw new NoSuchElementException();
            }
            String member = members[index];
            index ++;
            return member;
        }
    }
    
    
    public class IteratorPatternDemo {
        public static void main(String[] args) {
            TeamMembersIter members = new TeamMembersIter();
            members.forEachRemaining(System.out::println);
        }
    }
    
  • [Clojure] 函数式语言里有许多处理集合的函数,不需要再构建迭代器模式:
    (let [members ["Sam" "Olivia" "Freya" "Emily"]]
      (run! (fn [m] (println "hi," m)) members))
    ;; hi, Sam
    ;; hi, Olivia
    ;; hi, Freya
    ;; hi, Emily