【Java】初学 Java 设计模式(二):实战工厂方法模式

一、工厂方法模式介绍

1. 解决的问题

通过接口的选择,解决在不同条件下创建不同实例的问题。

2. 定义

工厂方法模式又称工厂模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

简单来说就是为了提供代码的扩展性,屏蔽每一个功能类的具体实现逻辑,让外部仅知道调用即可。这也是去掉多个 if else 的方式,当然也存在一些缺点,如需要实现的类过多,如何维护,降低开发成本。但这些问题可以通过与其他设计模式结合使用,逐步降低影响。

二、 工厂方法模式优缺点

1. 优点

  1. 可以避免创建者和具体产品之间的紧密耦合。
  2. 单一职责原则:可以将产品创建代码放在程序的单一位置,从而使代码更容易维护。
  3. 开闭原则:无需更改现有代码,就可以在程序中引入新的产品类型。

2. 缺点

  1. 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况使将该模式引入创建者类的现有层次结构中。

三、 工厂方法模式应用实例:文章、视频、直播内容输出生产场景

1. 实例场景

如今信息飞速发展的时代,文章、视频、直播各种形式的内容输出充斥着我们的生活,接下来,我们以UP发布文章、视频、发起直播的创建过程来作为模拟场景。

【Java】初学 Java 设计模式(二):实战工厂方法模式 「文章、视频、直播内容输出生产场景」

2. 用一坨坨代码实现

不考虑后续增加其他内容的扩展性,仅应对当前需求,目前的实例场景使用 if else 判断调用不同内容生产服务类即可满足需求。

public void createContent(int type, Map<String, Object> dataMap) throws InvocationTargetException, IllegalAccessException {

//初始化

if (type == 1) {

Article result = new Article();

} else if (type == 2) {

Video result = new Video();

} else if (type == 3) {

Live result = new Live();

} else {

return;

}

result.setId(UUID.randomUUID().toString());

//转换dataMap数据

BeanUtils.populate(result, dataMap);

return result;

}

仅考虑业务功能,上述代码完全满足需求,开发起来非常快,可以说是 if else 一把梭就完事了。

但如果后续存在迭代或扩展的需求,每次经过一次开发,都需要重新阅读这段代码,查看增加或删除的功能是否对其他类型产生影响,测试回归验证时间长,需要全面验证。

实际场景中并不存在毫无变动的需求,即使在初次开发过程中,往往也会由于各种问题会导致需求存在变动,那么,尤其是测试过程中,往往我们手中还会有其他的任务,一旦出现变动,往往会面临开发时间被压缩的情况,可想而知,if else 将无限累加,该段代码将越来越长,越来越难以阅读和维护。

3. 工厂模式优化代码

接下来使用工厂模式来对代码进行优化,加入了工厂模式后代码结构清晰、具备后续新增内容类别的扩展性。

3.1 工程结构

factory-pattern

└─ src

├─ main

│ └─ java

│ └─ org.design.pattern.factory

│ ├─ model

│ │ └─ Content.java

│ │ └─ content

│ │ ├─ Article.java

│ │ ├─ Live.java

│ │ └─ Video.java

│ ├─ service

│ │ └─ ContentService.java

│ | └─impl

│ | ├─ ArticleContentServiceImpl.java

│ | ├─ LiveContentServiceImpl.java

│ | └─ VideoContentServiceImpl.java

│ └─ ContentNum.java

│ └─ ContentServiceFactory.java

└─ test

└─ java

└─ org.design.pattern.factory.test

└─ ContentServiceFactoryTest.java

从上面的工程结构来看,代码看上去不是清晰了,分层也更容易扩展,几乎可以理解每个类的作用了。

3.2 代码实现

3.2.1 定义内容基础类

/**

* 内容基类

*/

public class Content {

/**

* 内容id

*/

private String id;

/**

* 用户id

*/

private String userId;

}

所有的内容都应该包含本身的内容 id 以及内容生产方即用户 id。

文章

/**

* 文章

*/

public class Article extends Content {

/**

* 标题

*/

private String title;

/**

* 内容

*/

private String content;

/**

* 文章分区id

*/

private int partitionId;

/**

* 文章分类

*/

private String articleType;

/**

* 文章所属话题列表

*/

private List<String> topics;

}

3.2.2 内容服务接口

/**

* 内容服务

*/

public interface ContentService {

/**

* 生产内容

* @param taskId 任务id

* @param userId 用户id

* @param dataMap 内容数据

* @return 内容

*/

Content createContent(String taskId, String userId, Map<String, Object> dataMap) throws InvocationTargetException, IllegalAccessException;

}

  • 内容服务的提供都通过实现此接口来处理,以保证最终入参出参的统一。
  • 接口入参:任务 id、用户 id、内容数据 map(用于匹配不同的内容属性)。

文章内容服务实现类

/**

* 文章内容服务实现类

*/

public class ArticleContentServiceImpl implements ContentService {

private final Logger log = LoggerFactory.getLogger(ArticleContentServiceImpl.class);

@Override

public Content createContent(String taskId, String userId, Map<String, Object> dataMap) throws InvocationTargetException, IllegalAccessException {

log.info("task {} => start", taskId);

log.info("task {} => article data is {}", taskId, JSON.toJSON(dataMap));

//初始化

Article result = new Article();

result.setId(UUID.randomUUID().toString());

result.setUserId(userId);

//转换dataMap数据

BeanUtils.populate(result, dataMap);

log.info("task {} => create article success, id:{}, title:{}", taskId, result.getId(), result.getTitle());

return result;

}

}

3.2.3 创建内容服务工厂

内容枚举类

/**

* 内容枚举类

*/

public enum ContentNum {

ARTICLE("article"),

VIDEO("video"),

LIVE("live");

ContentNum(String type) {

this.type = type;

}

/**

* 内容类型

*/

private String type;

}

内容服务工厂

/**

* 内容服务工厂

*/

public class ContentServiceFactory {

private static final Map<String, ContentService> factoryMap = new ConcurrentHashMap<String, ContentService>();

static {

factoryMap.put(ContentNum.ARTICLE.getType(), new ArticleContentServiceImpl());

factoryMap.put(ContentNum.VIDEO.getType(), new VideoContentServiceImpl());

factoryMap.put(ContentNum.LIVE.getType(), new LiveContentServiceImpl());

}

/**

* 获取内容服务

* @param type 内容类型

* @return 内容服务实例

*/

public ContentService getContentService(String type) {

return factoryMap.get(type);

}

}

我们定义了一个枚举类来支持各种类型内容的定义,此外在内容服务工厂类中通过 map 的形式来配置各类内容服务的选择,只需要调用内容服务工厂的 getContentService() ,传入类型,即可获取相应的内容服务实现类,不需要关心内容服务的具体实现。

3.3 测试验证

3.3.1 编写测试类

public class ContentServiceFactoryTest {

private final ContentServiceFactory contentServiceFactory = new ContentServiceFactory();

@Test

public void createArticle() throws InvocationTargetException, IllegalAccessException {

String taskId = UUID.randomUUID().toString();

String userId = "yiyufxst";

Map<String, Object> dataMap = new HashMap<>();

dataMap.put("title", "初学设计模式:实战工厂模式");

dataMap.put("content", "文章:「文章、视频、直播内容输出生产场景」");

dataMap.put("partitionId", 1);

dataMap.put("articleType", "技术");

dataMap.put("topics", new ArrayList<String>(){

{

this.add("Java");

this.add("设计模式");

}

});

this.contentServiceFactory.getContentService(ContentNum.ARTICLE.getType()).createContent(taskId, userId, dataMap);

}

@Test

public void createVideo() throws InvocationTargetException, IllegalAccessException {

String taskId = UUID.randomUUID().toString();

String userId = "yiyufxst";

Map<String, Object> dataMap = new HashMap<>();

dataMap.put("title", "初学设计模式:实战工厂模式");

dataMap.put("introduction", "视频:「文章、视频、直播多种内容输出生产场景」");

dataMap.put("videoUrl", "//yiyufxst.me/av/1");

dataMap.put("coverUrl", "//yiyufxst.me/images/1");

dataMap.put("partitionId", 1);

this.contentServiceFactory.getContentService(ContentNum.VIDEO.getType()).createContent(taskId, userId, dataMap);

}

@Test

public void createLive() throws InvocationTargetException, IllegalAccessException {

String taskId = UUID.randomUUID().toString();

String userId = "yiyufxst";

Map<String, Object> dataMap = new HashMap<>();

dataMap.put("title", "初学设计模式:实战工厂模式");

dataMap.put("introduction", "直播:「文章、视频、直播多种内容输出生产场景」");

dataMap.put("roomId", 1);

dataMap.put("coverUrl", "//yiyufxst.me/images/2");

dataMap.put("partitionId", 1);

this.contentServiceFactory.getContentService(ContentNum.LIVE.getType()).createContent(taskId, userId, dataMap);

}

}

3.3.2 结果

17:39:02.236 [main] INFO  o.d.p.f.s.i.VideoContentServiceImpl - task e4c3be6f-b023-49ed-9643-272d5bb58d2b => start

17:39:02.288 [main] INFO o.d.p.f.s.i.VideoContentServiceImpl - task e4c3be6f-b023-49ed-9643-272d5bb58d2b => video data is {"coverUrl":"//yiyufxst.me/images/1","videoUrl":"//yiyufxst.me/av/1","title":"初学设计模式:实战工厂模式","partitionId":1,"introduction":"视频:「文章、视频、直播多种内容输出生产场景」"}

17:39:02.321 [main] INFO o.d.p.f.s.i.VideoContentServiceImpl - task e4c3be6f-b023-49ed-9643-272d5bb58d2b => create video success, id:df8b1b07-7adf-4f67-98c5-df96ec27e081, title:初学设计模式:实战工厂模式

17:39:02.322 [main] INFO o.d.p.f.s.i.LiveContentServiceImpl - task e41e3d1d-af41-4e16-8bfb-545018a9f196 => start

17:39:02.322 [main] INFO o.d.p.f.s.i.LiveContentServiceImpl - task e41e3d1d-af41-4e16-8bfb-545018a9f196 => live data is {"coverUrl":"//yiyufxst.me/images/2","title":"初学设计模式:实战工厂模式","partitionId":1,"introduction":"直播:「文章、视频、直播多种内容输出生产场景」","roomId":1}

17:39:02.323 [main] INFO o.d.p.f.s.i.LiveContentServiceImpl - task e41e3d1d-af41-4e16-8bfb-545018a9f196 => create live success, id:4e7a9bad-f3be-43f0-9385-d1016220c797, title:初学设计模式:实战工厂模式

17:39:02.324 [main] INFO o.d.p.f.s.i.ArticleContentServiceImpl - task 6d7884b9-493d-4d31-8f08-cace7bc61f2c => start

17:39:02.326 [main] INFO o.d.p.f.s.i.ArticleContentServiceImpl - task 6d7884b9-493d-4d31-8f08-cace7bc61f2c => article data is {"title":"初学设计模式:实战工厂模式","partitionId":1,"articleType":"技术","topics":["Java","设计模式"],"content":"文章:「文章、视频、直播内容输出生产场景」"}

17:39:02.330 [main] INFO o.d.p.f.s.i.ArticleContentServiceImpl - task 6d7884b9-493d-4d31-8f08-cace7bc61f2c => create article success, id:a526b893-a237-4685-9234-a196e834fe7f, title:初学设计模式:实战工厂模式

Process finished with exit code 0

运行结果正常,在满足产品业务需求的同时,提升了代码的可阅读性和扩展性,有利于后续的拓展和维护。

四、工厂方法模式结构

根据实例可以总结初一个工厂方法的模式结构图:

【Java】初学 Java 设计模式(二):实战工厂方法模式 「文章、视频、直播内容输出生产场景」

  1. 产品(Product)定义产品的基本条件。对于所有由创建者及其子类构建的对象,基础父类是通用的。
  2. 具体产品(Concrete Products)是产品的不同子类。
  3. 创建者(Creator)类声明产品对象的工厂方法。该方法的返回对象类型必须与产品基础父类匹配。
  4. 具体创建者(Concrete Creators)将实现创建者的方法,使其返回不同类型的产品。

注意

  1. 产品(Product)和创建者(Creator)既可为父类,要求子类重写方法,也可为接口,要求子类实现方法,只需满足约定由具体创建者来创建相应的产品即为工厂方法模式。
  2. 每次调用工厂方法不一定都会创建新的实例,工厂方法也可以返回缓存、对象池或其他来源的已有对象。

设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想区优化代码,就能构建出更合理的代码。

以上是 【Java】初学 Java 设计模式(二):实战工厂方法模式 的全部内容, 来源链接: utcz.com/a/111156.html

回到顶部