创建型设计模式:工厂方法(factory method)
情况:各子公司合作的物流公司不同
接着简单工厂与物流公司合作的例子。公司在多个地区和物流公司都有合作,但各个地区合作的物流公司有所不同,并且有些公司还是当地的物流。
简单工厂解决方案
结合简单工厂模式,我们可以创建各个地区的简单工厂。这样做先得定义简单工厂的接口:
public interface SimpleExpressFactory {
Express createExpress(String expressType);
}
深圳,上海地区的物流简单工厂模式可以分别实现。
ShenzhenExpressFactory:
public class ShenzhenExpressFactory implements SimpleExpressFactory{
public Express createExpress(String expressType) {
Express express;
switch(expressType) {
case "SHUNFEN":
express = new ShunFengExpress();
break;
case "YUNDA":
express = new YunDaExpress();
break;
case "SHEN_ZHEN_LOCAL":
express = new ShenzhenLocalExpress();
break;
default:
express = new ShunFengExpress();
}
return express;
}
}
ShanghaiExpressFactory:
public class ShanghaiExpressFactory implements SimpleExpressFactory{
public Express createExpress(String expressType) {
Express express;
switch(expressType) {
case "SHUNFEN":
express = new ShunFengExpress();
break;
case "ZHONG_TONG":
express = new ZhongTongaExpress();
break;
case "SHANG_HAI_LOCAL":
express = new ShanghaiLocalExpress();
break;
default:
express = new ShunFengExpress();
}
return express;
}
}
货物发送调用的方式,在不同地区调用地区所属的物流简单工厂:
//深圳
SimpleExpressFactory factory = new ShenzhenExpressFactory();
GoodsSender sender = GoodsSender(factory);
//上海
SimpleExpressFactory factory = new ShanghaiExpressFactory();
GoodsSender sender = GoodsSender(factory);
情况:有些地区和物流公司的合作方式有所不同
在一些偏远的地方,物流公司不提供上门打包的服务,需要寄送方把货物运送到站点再打包。这种情况下,修改GoodsSender的发送流程方法,原标准发货流程:
public void send(String expressType, Goods goods) {
Express express = expressFactory.createExpress(expressType);
express.placeOrder(goods);
//需要在此处为特定expressType定制合作流程
express.pickup();
express.delivery();
}
问题:违反开闭原则
在上面代码的修改处,我们需要为特定地区的特定物流公司做修改,这样做合适,代码优雅吗?如果有多个特定的定制需求,那send
方法就要不断修改。是否想到违反哪些开发原则呢?
是的,上述做法违反了代码的开闭原则,对修改封闭。
有什么方法既能保持大部分合作物流公司的标准流程,又能够对特定流程的定制有一定的弹性空间呢?
方案:定义发送货物的标准流程框架
既然部分地区公司有定制物流公司合作流程的需求,有一种方法就是定义发货标准流程的框架的父类GoodsSender
,由地区公司子类继承,这样就允许地区公司对发货流程方法send()进行修改。
因为每个地区合作的物流都要定制,那么把原来由SimpleExpressFactory的创建方法createExpress
移到框架父类GoodsSender
中,声明为抽象方法,由地区公司子类实现。
public abstract GoodsSender {
public void send(String expressType, Goods goods) {
Express express = createExpress(expressType);
express.placeOrder(goods);
express.pickup();
express.delivery();
}
//定义创建物流的抽象方法
abstract Express createExpress(expressType);
}
GoodsSender
做了以下修改:
- 移除了简单工厂SimpleExpressFactory引用。
- 定义了用于创建物流的抽象方法createExpress,由子类实现。
修改后的GoodsSender更具弹性:
- 定义了物流发送的标准流程框架,用于大部分地区公司。
- 支持地区公司定制合作的物流公司。
- 允许特定发货流程的定制。
偏远地区公司的物流发送:
public AFarawayZoneGoodsSender extends GoodsSender {
public void send(String expressType, Goods goods) {
Express express = createExpress(expressType);
express.placeOrder(goods);
//修改地方
transfGoodsToExpress(express);
express.delivery();
}
//定义创建物流的抽象方法
Express createExpress(expressType) {
Express express;
switch(expressType) {
case "A_LOCAL":
express = new ALocalExpress();
break;
default:
express = new ALocalExpress();
}
return express;
}
private void transfGoodsToExpress(Express express){
//......
}
}
对于原Shenzhen修改如下,把原有ShenzhenSimpleExpressFactory的创建方法移到ShenzhenGoodsSender
中:
public ShenzhenGoodsSender extends GoodsSender {
Express createExpress(String expressType) {
Express express;
switch(expressType) {
case "SHUNFEN":
express = new ShunFengExpress();
break;
case "YUNDA":
express = new YunDaExpress();
break;
case "SHEN_ZHEN_LOCAL":
express = new ShenzhenLocalExpress();
break;
default:
express = new ShunFengExpress();
}
return express;
}
}
工厂方法模式
上面的重构后的代码有一个正式的名称:工厂方法。
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
工厂方法是创建型设计模式,类图如下:
<figure class="image"></figure>### Creator:创建者接口
之所以叫工厂方法,它的一个特征是在父类定义一个用于对象创建的抽象方法,由子类实现:
abstract Product createProduct(String type)
- abstract:定义为抽象,目的是让子类实现。
- Product:返回的是定义产品的接口
- createProduct:工厂方法。
- type:参数type是可选的
ConcreteCreator:具体创建者类
覆盖工厂方法,返回不同类型的产品。请注意,工厂方法不必一直创建新实例。 它还可以从缓存、对象池或其他源返回现有对象。
Product:产品接口
定义工厂方法一个限制条件就是,具体的产品需要有共同的产品接口,这里说的不一定是interface,可以是类。
ConcreteProduct:具体产品类
具体产品是产品接口的不同实现。
工厂方法的优劣
- 解耦了产品的创建和具体产品绑定
- 符合单一职责原则,可以将产品创建代码放在一个位置,使代码更易于维护。
- 符合开闭原则,可以在不破坏现有代码的情况下新增产品。
简单工厂 VS 工厂方法
既然工厂方法相对简单工厂更具弹性,要不要在所有用到简单工厂的地方替换为工厂方法呢?在代码结构上,简单工厂和调用方是组合方式,工厂方法是通过继承的方式实现,代码结构相对复杂。
有一个KISS(Keep it simple)原则,如果简单工厂能满足要求,则建议使用简单工厂模式。