本篇Blog继续学习创建型模式,了解如何更优雅的创建对象,本篇学习的是工厂方法模式。由于学习的都是设计模式,所有系列文章都遵循如下的目录:
- 模式档案:包含模式的定义、模式的特点、解决什么问题、优缺点、使用场景
- 模式示例:包含模式的实现方式代码举例
- 模式实践:如果工作中或开源项目用到了该模式,就将使用过程贴到这里,并且客观讨论使用的是否恰当
接下来所有设计模式的介绍都暂且遵循此基本行文逻辑吗,对于模式实践有则增加,没有就不需要了。
模式档案
模式定义:工厂方法模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂方法模式中,创建对象不会对调用者暴露创建逻辑,而是通过使用接口来指向新创建的对象。
模式特点:工厂方法模式的主要特点是:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂方法模式使其创建过程延迟到子类进行,在编写工厂类时,不需要知道实际创建的产品类是哪一个,而是选择了哪个具体的工厂类就决定了实际创建的产品类是哪一个。
解决什么问题:在面向对象编程中,最常用的创建对象的方法是通过new操作符构造一个对象实例。但是在一些情况下, new操作符直接生成对象会带来一些问题。举例来说,许多类型对象的创造需要一系列的步骤: 可能需要计算获取对象的初始设置; 可能需要选择创建哪个对象的子对象实例; 可能在创建需要的对象前必须先创建一些辅助功能的对象。 在这些情况对象的创建就是一个 【过程】,不仅是一个【操作】,像一部大机器中的一个个齿轮传动,使调用者能轻松方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程
优点: 一个调用者想创建一个对象,只要知道其名称就可以了,细节不必关注; 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以;屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖
使用场景: 当一种产品实现有多种不同的呈现方式或表现形式时。例如:日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口
模式示例
其实依据复杂度的不同,又分为简单模式和工厂方法模式。
非工厂模式
非工厂方法模式就是不使用工厂方法模式,它的特点就是:只有多个具体的产品类,没有工厂类。我们先来看看不使用工厂方法模式来创建对象是怎样的过程:
public class Rectangle {
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square {
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle {
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
打印各种对象的创建结果:
public class FactoryPatternDemo {
public static void main(String[] args) {
Circle circle= new Circle();
circle.draw();
Rectangle rectangle = new Rectangle();
rectangle.draw();
Square square= new Square();
square.draw();
}
}
打印结果:
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
可以看到,调用者需要自己去创建不同的对象才能调用对象方法,而这三个对象都有相同的执行方法,只不过执行具体内容不同。这样调用者其实就需要关心对象的创建过程了。当然这个示例比较简单,对应的产品对象构造相对简单,
简单工厂模式
简单工厂模式又称为静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。它的特点就是:有一个抽象产品类和多个具体的产品类,只有一个具体的工厂类,一个具体的工厂类可以创建多个具体产品类的实例 先来看看它的组成:
- 具体工厂角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。
静态工厂方法模式结构如下: 实现方式也比较简单:
抽象产品角色
package com.example.designpattern.factory;
public interface Shape {
void draw();
}
具体产品角色
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
具体工厂角色
public class ShapeFactory {
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
客户端调用代码知道它所需要的是哪种图形,通过具体工厂获取产品并打印结果如下:
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
Shape shape2 = shapeFactory.getShape("RECTANGLE");
shape2.draw();
Shape shape3 = shapeFactory.getShape("SQUARE");
shape3.draw();
}
}
打印结果:
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
工厂方法模式
相对于简单的工厂模式,它的特点就是:有一个抽象产品类和多个具体的产品类,有一个抽象的工厂类和多个具体的工厂类,每个具体工厂类只能创建一个具体产品类的实例。为什么需要工厂方法模式呢?静态工厂方法模式不够用么?
我们从开闭原则(对扩展开放,对修改封闭)上来分析下简单工厂模式。当我们想要对产品进行扩展时。对产品部分来说,它是符合开闭原则的,只需再增加一个新的具体产品类即可;但是工厂部分好像不太理想,因为每增加一个产品,都要在工厂类中增加相应的创建业务逻辑(需要新增case),这显然是违背开闭原则的。所以对于新产品的加入,工厂类是很被动的。所以我们需要进一步解耦
所以我们需要将工厂类也进行抽象。相比于简单工厂模式,工厂方法模式将对产品的创建方法进行抽象,它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担
- 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在Java中它由抽象类或接口来实现。
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在Java中一般由抽象类或者接口来实现。
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在Java中由具体的类来实现。
抽象产品角色
package com.example.designpattern.factory;
public interface Shape {
void draw();
}
具体产品角色
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
抽象工厂角色
package com.example.designpattern.factory;
public interface Factory {
Shape getShape();
}
具体工厂角色
public class RectangleFactory implements Factory {
@Override
public Rectangle getShape() {
return new Rectangle();
}
}
public class SquareFactory implements Factory {
@Override
public Square getShape() {
return new Square ();
}
}
public class CircleFactory implements Factory {
@Override
public Shape getShape() {
return new Circle();
}
}
客户端调用代码知道它所需要的是哪种图形,通过具体工厂获取产品并打印结果如下:
public class FactoryPatternDemo {
public static void main(String[] args) {
RectangleFactory rectangleFactory = new RectangleFactory();
Shape shape1 = rectangleFactory.getShape();
shape1.draw();
SquareFactory squareFactory= new SquareFactory();
Shape shape2 = squareFactory.getShape();
shape2.draw();
CircleFactory circleFactory= new CircleFactory();
Shape shape3 = circleFactory.getShape();
shape3.draw();
}
}
打印结果:
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
到这里可能会有人问,那我new一个具体的工厂和我直接new一个产品类有什么区别?这里来看其实区别不大,但是如果考虑一种情况:一个产品类的创建过程非常复杂,逻辑有30行,并且有30个客户端创建了这个产品类,这就是900行代码,而且如果创建过程后续有过变动,那么这900行代码都得改。
那又会问了,那我为什么不直接把创建过程抽象成一个方法,不也照样能做到一个地方修改么。封装为一个方法当然可以,那就是创建该调用对象的方法,那么当这样有类似同样行为但是实际内容又不相同的对象多的时候,没办法进行统一管理。所以我们才需要一个工厂来统一管理对象的创建,其实这个要封装的方法就是工厂方法。
模式实践
依据上述模式进行一些简单的模式实践.在工作中其实经常会遇到这种场景,就拿一个同步组织的实现来说吧。同步组织有统一的通用方法,但是不同的业务实现的时候有不同的实现方式:
抽象产品角色
public interface OrgHandler {
void syncOrg(SettleContractInfo settleContractInfo);
void createCompanyOrgFromBatch(SaasHrTreeDetail waitingCreateCompanyInfo);
void createStoreOrgFromBatch(SaasHrTreeDetail waitingCreateStoreInfo);
}
具体产品角色
@Service
public class CommercialEstateOrgHandler implements OrgHandler{
@PostConstruct
public void registerToFactory() {
OrgHandlerFactory.register(BizLineEnum.COMMERCIAL.getCode(), this);
}
@Override
public void syncOrg(SettleContractInfo settleContractInfo) {
}
@Override
public void createCompanyOrgFromBatch(SaasHrTreeDetail waitingCreateCompanyInfo) {
}
@Override
public void createStoreOrgFromBatch(SaasHrTreeDetail waitingCreateStoreInfo) {
}
}
另一个具体的产品角色:
@Service
@Slf4j
public class BeiJiaOrgHandler implements OrgHandler {
@PostConstruct
public void registerToFactory() {
OrgHandlerFactory.register(BizLineEnum.TEST.getCode(), this);
}
@Override
public void syncOrg(SettleContractInfo settleContractInfo) {
}
public void syncOrgBackDoor(SettleContractInfo settleContractInfo) {
}
}
具体工厂方法
public class OrgHandlerFactory {
private static final Map<Integer, OrgHandler> orgHandlerMap = new ConcurrentHashMap<>();
public static OrgHandler getOrgHandler(Integer bizLineCode) {
OrgHandler orgHandler = orgHandlerMap.get(bizLineCode);
if (Objects.isNull(orgHandler)) {
throw new BusinessRuntimeException(ErrorNo.PARAM_ERROR);
}
return orgHandler;
}
public static void register(Integer bizLineCode, OrgHandler orgHandler) {
orgHandlerMap.put(bizLineCode, orgHandler);
}
}
可以看到实际使用时我们会用Map来存储type类型分别使用,不使用僵化的case 或者if else 。调用时逻辑如下:
private void synchronizationCompany() {
List<SaasHrTreeDetail> waitingConfirmCompanyInfo = saasHrTreeDetailBizService.getWaitingConfirmCompanyInfo();
for (SaasHrTreeDetail saasHrTreeDetail : waitingConfirmCompanyInfo) {
try {
OrgHandler orgHandler = OrgHandlerFactory.getOrgHandler(saasHrTreeDetail.getBizLineCode());
orgHandler.createCompanyOrgFromBatch(saasHrTreeDetail);
} catch (Exception e) {
LOGGER.error("syncCompanyOrg failed,saasHrTreeDetail:{}", saasHrTreeDetail, e);
}
}
}
这里有个容易理解混乱的地方:当客户端调用代码明确知道自己要创建哪个类时很好理解:
简单工厂
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
工厂方法
RectangleFactory rectangleFactory = new RectangleFactory();
Shape shape1 = rectangleFactory.getShape();
shape1.draw();
但是当客户端调用代码自己也不知道要创建哪个类,而是通过某项参数决定时,似乎工厂方法方式还需要依据条件进行不同的具体工厂选择,而简单工厂可以直接将条件作为参数传入,为满足这种情况,工厂方法也需要创建类似的工厂类选择器。其实这里是看问题的维度不清晰决定的,我们说工厂模式指的是客户端调用代码在明确自己要创建的对象情况下和创建对象行为解耦。实际场景中客户端调用可能确实不知道自己要创建哪个类,但这是另一个维度的问题,可以通过别的方案实现调用代码不变,类似简单工厂用于选择具体的工厂类+工厂方法的组合方式。工厂方法模式是一个内聚的概念,其相比于简单工厂就是用不同的具体工厂类来更解耦的控制创建具体产品对象并实现该对象行为。
总结一下
其实工厂模式应用非常广泛,工作中经常会遇到,因为面向接口编程,在很多场景下会有多个类拥有同一种行为的情况,无论这种行为来自实现接口还是继承父类。但是它们各自又有不同的实现内容,也就是多态。在使用这些类进行某种行为时我们希望不关心创建过程直接获取自己需要的类并进行个性化行为,此时工厂方法模式就是最佳选择,工厂方法模式可以让我们的客户端调用代码与被调用对象的创建解耦,无论被调用对象的创建过程如何变化,客户端调用代码都无需改变。其实大多数情况下简单工厂就够用了,只有当类的创建过程复杂、待创建类的种类非常多对工厂扩展不利时才考虑工厂方法模式。
|