创建型模式 – 关注对象创建的模式
单例模式 / 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类型外,通常还会
抽象工厂模式 / 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里,层级结构用
vector
或map
表示,结合动态类型,在运行时对目标数据执行相应的动作:(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
访问。需要添加或修改Shape
或Color
类时,不会影响调用者使用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
抽象类为HttpRequest
,WebsocketReqeust
提供了公用的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] 用
history
与app
两个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