设计模式系列文章:https://zhum.in/blog/category/notes/design-pattern
Demo code:https://github.com/njZhuMin/BlogSampleCode
为什么要用设计模式
不用设计模式并非不可以,但是用好设计模式能帮助我们更好地解决实际问题。设计模式最重要的是解耦。有些设计模式其实我们每天都在用,但自己却没有明确的感知。学习设计模式就是学习如何总结经验的,把经验为自己所用,也是锻炼将业务需求转换技术实现的一种非常有效的方式。
Spring 就是一个把设计模式用得淋漓尽致的经典框架,举一些例子:
- 工厂模式:BeanFactory
- 装饰器模式:BeanWrapper
- 代理模式:AopProxy
- 委派模式:DispatcherServlet
- 策略模式:HandlerMapping
- 适配器模式:HandlerAdapter
- 模板模式:JdbcTemplate
- 观察者模式:ContextLoaderListener
简单工厂模式
简单工厂模式 (Simple Factory Pattern) 是指由一个工厂对象决定创建出哪一种产品类的实例。简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。
案例
举一个支付场景的案例。一开始我们需要支持支付宝和微信支付两种支付方式,于是我们可以写一个支付商家的接口:
public interface IMerchant {
String getMerchant();
int payBill();
}
为什么需要接口:接口定义了实现规范,为顶层模块提供了面向接口编程的条件。
public class AliPay implements IMerchant {
public AliPay() {
System.out.println("Merchant [" + this.getMerchant() +
"] is created successfully");
}
@Override
public String getMerchant() {
return "AliPay";
}
@Override
public int payBill() {
System.out.println("Payment succeeded by [" + this.getMerchant() + "].");
return 0;
}
}
public class WechatPay implements IMerchant {
public WechatPay() {
System.out.println("Merchant [" + this.getMerchant() +
"] is created successfully");
}
@Override
public String getMerchant() {
return "WechatPay";
}
@Override
public int payBill() {
System.out.println("Payment succeeded by [" + this.getMerchant() + "].");
return 0;
}
}
接下来实现一个简单的工厂类来负责创建各种支付方式:
public class SimplePaymentFactory {
// version 1: Client must know the params he passed
// Whenever a new merchant is added, must modify both this factory method and caller
public static IMerchant createMerchant(String merchantName) throws Exception {
switch (merchantName) {
case "AliPay":
return new AliPay();
case "WechatPay":
return new WechatPay();
default:
throw new Exception("Error creating merchant " + merchantName);
}
}
}
使用简单工厂类:
public class Client {
public static void main(String[] args) {
try {
IMerchant merchant = SimplePaymentFactory.createMerchant(ApplePay.class);
int payResult = merchant.payBill();
} catch (Exception e) {
e.printStackTrace();
}
}
}
改进
在这个案例中,虽然使用者不需要关心支付方式创建的细节,但仍然需要知道如何创建支付方式(传入"AliPay","WechatPay" 参数)。随着功能的扩展,比如需要加入银联支付,苹果支付的支持,这样的方式存在明显的问题:
- 客户端需要维护的依赖会持续膨胀(需要关系如何创建新的支付方式)。
- 工厂方法变得越来越复杂(需要更多的 select case)
第二个问题我们暂且可以先做一步改进,用泛型的方式来支持不同的参数:
public static IMerchant createMerchant(Class<? extends IMerchant> clazz) throws Exception {
if (null == clazz) {
throw new Exception("Error merchant for payment.");
}
return clazz.getDeclaredConstructor().newInstance();
}
小结
简单工厂模式的优点:
- 使用者只需要关心正确的参数,就可以获取到所需的对象,而不需要知道对象创建的细节。
缺点: - 工厂类的职责相对过重,扩展时需要修改工厂类逻辑,不符合开闭原则
- 不易于扩展过于复杂的产品结构
虽然在复杂场景下不适用,但是简单工厂模式在一些相对固定简单,且不太需要考虑扩展的场景下,还是很好用的。例如 JDK 中的 Calendar
类:
public static Calendar getInstance() {
Locale aLocale = Locale.getDefault(Category.FORMAT);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException var7) {
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
byte var6 = -1;
switch(caltype.hashCode()) {
// ...
}
switch(var6) {
case 0:
cal = new BuddhistCalendar(zone, aLocale);
break;
case 1:
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case 2:
cal = new GregorianCalendar(zone, aLocale);
}
}
}
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return (Calendar)cal;
}
工厂方法模式
工厂方法模式 (Fatory Method Pattern) 是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符合开闭原则。
工厂方法模式主要解决产品扩展的问题。在简单工厂中,随着产品链的丰富,如果每个课程的创建逻辑有区别的话,工厂的职责会变得越来越多,不便于维护。
根据单一职责原则我们将职能继续拆分,专人干专事。(通过接口定义规则,实现下放)
案例
例如现在我们需要继续添加新的支付方法,首先定义一个支付方式的工厂方法的接口:
public interface IMerchantFactory {
IMerchant createMerchant();
}
给每一种支付方式都创建一个工厂类来实现这个工厂类的规范:
public class AliPayFactory implements IMerchantFactory {
@Override
public IMerchant createMerchant() {
return new AliPay();
}
}
public class WechatPayFactory implements IMerchantFactory {
@Override
public IMerchant createMerchant() {
return new WechatPay();
}
}
这样当我们需要添加银联支付的时候,只需要新增一个工厂类来创建银联支付:
public class UnionPayFactory implements IMerchantFactory {
@Override
public IMerchant createMerchant() {
return new UnionPay();
}
}
客户端只需要选择使用对应的支付方式的工厂,就可以直接创建出所需的对象了:
public class Client {
public static void main(String[] args) {
IMerchantFactory merchantFactory = new AliPayFactory();
IMerchant merchant = merchantFactory.createMerchant();
merchant.payBill();
}
}
这样的优势很明显,在扩展新的支付方式的时候,不需要再改动工厂接口的逻辑了,而只需要提供对工厂接口新的实现。
小结
工厂方法适用于以下场景:
- 创建对象需要大量重复的代码,公共逻辑可以抽象到上层。
- 客户端不需要依赖产品类实例如何被创建、实现等细节。
- 一个类通过其子类来指定创建哪个对象。
缺点:
- 类的个数容易过多,增加复杂度(每个产品都要有自己对应的工厂)。
- 增加了系统的抽象性和理解难度。
抽象工厂模式
抽象工厂模式 (Abastract Factory Pattern) 是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。客户端不依赖于产品类实例如何被创建、实现等细节,强调的是一系列相关的产品对象一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
案例
还是以支付场景作为案例:
- 现在我们需要支持国内的支付宝,微信和银联支付,同时还需要支持国外的 Apple Pay 和 PayPal 。
- 每种支付方式都包含准备支付环境,完成支付动作,等等逻辑。
- 国内和国外的支付方式分别包含大量的相似逻辑。
在这样的场景下,我们可以考虑应用抽象工厂模式。先建立一个抽象的工厂类,定义了每一种支付方式的工厂类的规范:
public abstract class AbstractPaymentFactory {
public void init() {
System.out.println("Initiating some data in abstractPaymentFactory...");
}
protected abstract IMerchant createMerchant();
protected abstract IPreparePayment createPreparePayment();
}
通过继承
为每一个产品族扩展出新的工厂类:
public interface IMerchant {
void payBill();
}
public class ChinaMerchant implements IMerchant {
@Override
public void payBill() {
System.out.println("ChinaMerchant is paying bill...");
}
}
public class ChinaPreparePayment implements IPreparePayment {
@Override
public void preparePaymentEnvironment() {
System.out.println("Preparing payment environment for China merchants...");
}
}
public class ChinaMerchantFactory extends AbstractPaymentFactory {
@Override
protected IMerchant createMerchant() {
super.init();
return new ChinaMerchant();
}
@Override
protected IPreparePayment createPreparePayment() {
super.init();
return new ChinaPreparePayment();
}
}
public class ForeignMerchant implements IMerchant {
@Override
public void payBill() {
System.out.println("ForeignMerchant is paying bill...");
}
}
public class ForeignPreparePayment implements IPreparePayment {
@Override
public void preparePaymentEnvironment() {
System.out.println("Preparing payment environment for Foreign merchants...");
}
}
public class ForeignMerchantFactory extends AbstractPaymentFactory {
@Override
protected IMerchant createMerchant() {
super.init();
return new ForeignMerchant();
}
@Override
protected IPreparePayment createPreparePayment() {
super.init();
return new ForeignPreparePayment();
}
}
小结
在上述案例中,抽象工厂模式非常完美清晰地描述了复杂的层次关系。优点:
- 具体产品在应用层代码隔离,无须关心创建细节
- 一系列相关的产品可以放在一起创建
但是,如果我们再继续扩展产品等级,那么我们的代码从抽象工厂,到具体工厂要全部调整,显然也是不符合开闭原则。因此抽象工厂也是有缺点的:- 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
- 增加了系统的抽象性和理解难度。
在实际应用中,我们千万不能犯强迫症甚至有洁癖。在实际需求中产品等级结构升级是非常正常的一件事情。我们可以根据实际情况,只要不是频繁升级,可以不遵循开闭原则。代码每半年升级一次或者每年升级一次又有何不可呢?
抽象工厂模式的一个典型应用:Spring 中的
- AnnotationConfigApplicationContext
- ClassPathXmlApplicationContext
一个问题
工厂类一定需要将构造方法私有化吗?
不一定。如果需要将工厂类作为单例使用时,需要将构造方法私有化,否则不需要。
文章评论