为了理解 GoF(Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人组)的设计模式原理和历史,我制作了一个 10 分钟的短视频。(作为 PluralSight 作者试录的)
视频: https://youtu.be/vq9zkZBjWkw
我将使用自己的示例来帮助大家理解设计模式。你可以下载这些代码来,看看是否对你理解设计模式有所帮助。 每个模式之后都有一些简短的代码片段,以便你能快速的尝试。请收藏这篇文章,这样在你需要重温的时候就能迅速找到。事不宜迟,我们马上进入观察者模式。
观察者模式,顾名思义,它的使用场景是,当某个位置(Subject,主题)的状态发生变化时,其它多个点(Observers,观察者)需要随之更新。每个 Observer 都必须分别注册到这个 Subject。Subject 也会提供一些方法注销观察者。已注册的观察者会在主题状态发生变化的时候收到由“通知”方法发出的通知。通常是这样。
这里我们提出一个示例,StockBroker 应用,它涉及维护各种类型的金融信息。Subject 是应用程序中的接口,它为 Observred 类提供样板。StockData 是 Subject 的一个具体实现,它实现了 addObserver()、removeObserver() 和 otifyObservers()。另外,它还维护着一个已注册观察者的列表。IncomeHandler, InvestmentHandler 和 PortfolioHandler 包含各种观察者,分别用于维护指定 StockBroker 的收入、投资和证券。
所有这些都取决于不断波动的股票价值。它们特别关心每支股票的 stockSymbol、stockValue 和 stockUnits。每个观察者都实现 Observer 接口,这个接口约定了 update() 方法,由每个具体的类实现。
下面的代码片段只展示了核心概念。你可以下载 示例代码 来获得完全的代码/应用程序。
package com.sumsoft.design.patterns.observer; /* * @author Sumith Puri */ public interface Observer { public void update(String stockSymbol, Float stockValue, Integer stockUnits); }
package com.sumsoft.design.patterns.observer; /* * @author Sumith Puri */ public class IncomeHandler implements Observer { Subject stockData = null; public IncomeHandler(Subject stockData) { this.stockData = stockData; stockData.addObserver(this); } @Override public void update(String stockSymbol, Float stockValue, Integer stockUnits) { System.out.println("IncomeHandler received changes... "); } }
package com.sumsoft.design.patterns.observer; /* * @author Sumith Puri */ public interface Subject { public void addObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
package com.sumsoft.design.patterns.observer; import java.util.ArrayList; import java.util.List; /* * @author Sumith Puri */ public class StockData implements Subject { private String stockSymbol = null; private Float stockValue = null; private Integer stockUnits = null; private List<Observer> observers = null; public StockData() { observers = new ArrayList<Observer>(); } @Override public void addObserver(Observer o) { observers.add(o); } @Override public void notifyObservers() { for(Observer o: observers) { o.update(stockSymbol, stockValue, stockUnits); } } @Override public void removeObserver(Observer o) { observers.remove(o); } public void setStockData(String stockSymbol, Float stockValue, Integer stockUnits) { // In real-time, this method might be invoked with values from a live web service at regular intervals. this.stockSymbol = stockSymbol; this.stockValue = stockValue; this.stockUnits = stockUnits; setDataChanged(); } private void setDataChanged() { notifyObservers(); } }
使用 StockBroker.java 来运行应用程序。你可以试着在应用中加入自己的 Observer,这样你可以尝试从 Web 服务中获得这些值并据此写一个自己的观察者。
装饰者模式以简洁的方式增强组合功能,它期望结果直接依赖于组合物和组成品。链关系(通过组合)或装饰最终会在运行时实现所需要的输出。实际上,某一产品的功能将由一个基础产品和不同的其它相关子产品或设置构成时,我们就可以使用装饰者模式。
对此的示例是 Pizza(比萨) 应用。这里,商店中的比萨使用各种饼底和配料组合而成,这是一个使用装饰者模式的典型案例。Pizza 是一个抽象的基类,每种比萨都从其继承并实现。Havaiian(夏威夷)、Italian(意大利) 和 Mexican(墨西哥) 是 Pizza 的具体实现,而 Mushroom(蘑菇)、Onion(洋葱) 和 Chicken(鸡肉) 是 ToppingDecorator 的具体实现。所有这些配料封装在一个 Pizza 实例中。这个实例在运行时会加入其它配料或者比萨饼底实例。最终,在计算整个比萨价值的时候可以看到装饰者模式的价值,只需要一个电话就能算出整个账单。
下面的代码段只提供了核心内容。你可以下载 示例代码 来获得完整代码/应用。
package com.sumsoft.design.patterns.decorator; /* * @author Sumith Puri */ public abstract class ToppingDecorator extends Pizza { public abstract String getDescription(); }
package com.sumsoft.design.patterns.decorator; /* * @author Sumith Puri */ public class Mushroom extends ToppingDecorator { Pizza pizza; public Mushroom(Pizza pizza) { this.pizza = pizza; } @Override public String getDescription() { return pizza.getDescription() + ", Mushroom"; } @Override public double cost() { return 0.25 + pizza.cost(); } }
package com.sumsoft.design.patterns.decorator; /* * @author Sumith Puri */ public abstract class Pizza { protected String description = null; public String getDescription() { return description; } public abstract double cost(); }
package com.sumsoft.design.patterns.decorator; /* * @author Sumith Puri */ public class Italian extends Pizza { public Italian(String description) { this.description = description + ", Italian"; } @Override public double cost() { return 1.20; } }
package com.sumsoft.design.patterns.decorator; /* * @author Sumith Puri */ public class PizzaWorld { public static void main(String args[]) { Pizza pizza = new Hawaiian("Veg Exotica"); pizza = new Mushroom(pizza); pizza = new Mushroom(pizza); pizza = new Onion(pizza); Pizza pizza1 = new Italian("Non-Veg Supreme"); pizza1 = new Chicken(pizza1); pizza1 = new Chicken(pizza1); pizza1 = new Onion(pizza1); pizza1 = new Onion(pizza1); System.out.println("Pizza World"); System.out.println("==========="); System.out.println(""); System.out.println(pizza.getDescription() + " " + pizza.cost()); System.out.println(pizza1.getDescription() + " " + pizza1.cost()); } }
PizzaWorld 是主类。尝试添加更多装饰者和比萨饼基类看看你是否真的搞清楚了装饰者模式。
单例模式整个程序/应用中只维护某个类的唯一实例,并提供了统一的访问方式。实现这个模式有很多种方法。我在这里向大家详解最常见的三种:
最简单的单例 ( 这里下载示例代码 ) 是在类加载的时候创建实现并保存在一个静态变量中。在需要的时候通过一个静态的 getter 方法来获取示例。对象早于第一次使用实例化,这种方法并不值得推荐。
在示例中,MediaContract(主线程)使用 ProductionHouse(单例)的实例。这个单例在类加载的时候实例化,并由一个私有静态变量维护。ProductionHouse 的 getInstance() 方法用于取得这个实例。
package com.sumsoft.design.patterns.singleton.eager; /* * @author Sumith Puri */ public class ProductionHouse { private static ProductionHouse productionHouse = new ProductionHouse(); private ProductionHouse() { } public static synchronized ProductionHouse getInstance() { return productionHouse; } }
为了客户上述缺点,推荐的方法是在对象第一次使用的时候进行实例化,同时保证线程安全( 下载示例代码 ),避免在并发线程中(多次)实例化。因为方法定义成 synchronized 的,其缺点是性能较差。
像之前的示例一样,实现的是 MediaContract(主线程)类和ProductionHouse(单例)类。getInstance() 方法是 synchronized 的,实例化只在它为 null 的时候发生。
package com.sumsoft.design.patterns.singleton.threadsafe; /* * @author Sumith Puri */ public class ProductionHouse { private static ProductionHouse productionHouse = null; private ProductionHouse() { } public static synchronized ProductionHouse getInstance() { if(productionHouse == null) { productionHouse = new ProductionHouse(); } return productionHouse; } }
上面提到的缺点对于在应用中高频访问的对象来说是至关重要的。为了改善这个问题,应该将同步块缩减到仅在第一次访问的时候进行,但这带来另外的不足。
下面的示例与上面的基本相同,不同之处在于将同步范围缩小到 getInstance() 方法内——它只在第一次访问时同步,不会在以后的访问中同步。你可以 在这里下载示例代码 。
package com.sumsoft.design.patterns.singleton.doublechecked; /* * @author Sumith Puri */ public class ProductionHouse { private static ProductionHouse productionHouse = null; private ProductionHouse() { } public static ProductionHouse getInstance() { if(productionHouse == null) { synchronized(ProductionHouse.class) { if(productionHouse == null) { productionHouse = new ProductionHouse(); } } } return productionHouse; } }
对于上述三个部分的示例,你可以运行下面的代码来了解三种实例化单例的不同之处。
package com.sumsoft.design.patterns.singleton.doublechecked; /* * @author Sumith Puri */ public class MediaContract extends Thread { public void run() { getProductionHouse(); } public void getProductionHouse() { ProductionHouse productionHouse = ProductionHouse.getInstance(); System.out.println(productionHouse.toString()); } public static void main(String args[]) { MediaContract thread01 = new MediaContract(); thread01.start(); MediaContract thread02 = new MediaContract(); thread02.start(); } }
某些情形下,我们需要创建一系列的行为(或操作),然后在特定的时间点执行它们,那么我们可以选择使用命令模式。虽然它与观察者模式非常类似,但它们在用法上不同。命令(行为)只会在单独调用,而不会像观察者一样全部被调用。
我们来看一个拍卖行的例子,那里有各种拍卖品。它们的抽象基类是 AuctionItem。具体类中需要实现的方法是 sell()。AuctionVase、AuctionFurniture 和 AuctionJewel 都是 AuctionItem 的具体实现。它们在 AuctionControl 中创建和设置(通过 itemKey 映射)。AuctionControl 可以看作是用于远程控制各项呈现于 AuctionStore。只要 AuctionControl 类的 presentItem() 被调用,就会根据传入的 itemKey 选择对应的 AuctionItem 实例,并调用其 sell() 方法。
下面只提供了核心代码,完全的代码/应用在这个 示例代码 中。
package com.sumsoft.design.patterns.command; /* * @author Sumith Puri */ public abstract class AuctionItem { public void sell() { } }
package com.sumsoft.design.patterns.command; /* * @author Sumith Puri */ public class AuctionFurniture extends AuctionItem { public void sell() { System.out.println("Sold Furniture Item"); } }
package com.sumsoft.design.patterns.command; import java.util.HashMap; import java.util.Map; /* * @author Sumith Puri */ public class AuctionControl { Map<String, AuctionItem> auctionItems = new HashMap<String, AuctionItem>(); public void setAuctionItem(String itemKey, AuctionItem auctionItem) { auctionItems.put(itemKey, auctionItem); } public void presentItem(String itemKey) { AuctionItem auctionItem = auctionItems.get(itemKey); auctionItem.sell(); } }
我相信工厂模式是在仅次于单例模式在软件工程中广泛使用的模式。单例模式只是在单个类上的创建性模式,使用工厂模式需要更大的规模。工厂模式用于产生类型相似的对象,并根据一定的条件或请求对象的类型集中的产生这些对象。工厂模式有很多变种,我在下面列出了三种。
简单工厂模式 基于一定条件创建(实例化)指定类型的产品(对象)。指定类型的对象可以由单个工厂创建,并且它们实现同一个接口。
对应的示例中,工厂根据操作系统用来实例化一个特定的类型。所有具体的系统实现 System 接口。 工厂类 SystemFactory 提供了 create() 方法,它需要一个 type 参数。type 参数决定工厂具体实例化哪一个。
package com.sumsoft.design.patterns.factory.simple; /* * @author Sumith Puri */ public interface System { public void provision(); public void update(); public void restart(); }
package com.sumsoft.design.patterns.factory.simple; /* * @author Sumith Puri */ public class UnixSystem implements System { @Override public void provision() { // TODO Auto-generated method stub } @Override public void restart() { // TODO Auto-generated method stub } @Override public void update() { // TODO Auto-generated method stub } }
package com.sumsoft.design.patterns.factory.simple; /* * @author Sumith Puri */ public class SystemFactory { public System createSystem(String type) { System system = null; if(type.equals("WIN")) { system = new WindowsSystem(); } else if (type.equals("LIN")) { system = new LinuxSystem(); } else if (type.equals("SOL")) { system = new SolarisSystem(); } else if (type.equals("MAC")) { system = new MacosSystem(); } else { system = new UnixSystem(); } return system; } }