热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计模式:工厂设计模式介绍及3种写法(简单工厂、工厂方法、抽象工厂)

0、介绍给一个背景:一个pizza订购项目,pizza本身的种类要便于扩展和维护,那么种类很多、制作过程也不少、还要完成订购的功能。按照一般的思路,类图设计如下:其中:Pizza类

0、介绍

给一个背景:一个pizza订购项目,pizza本身的种类要便于扩展和维护,那么种类很多、制作过程也不少、还要完成订购的功能

按照一般的思路,类图设计如下:


技术分享图片

其中:



  1. Pizza类按照面向对象的设计思路,制作过程对应的方法在其中实现;

  2. 由于需要不同的类型 Pizza,而在 prepare 步骤不同,所以 Pizza 做成抽象类,而两个不同类型的 Pizza 去继承 Pizza 类;

  3. 还需要一个的 OrderPizza 类,接受预定类型,完成对应的pizza类订购操作;

  4. 然后提供一个入口 PizzaStore 去调用 OrderPizza 类,相当于客户端。

代码如下:

/*
* Pizza抽象类,让其他的继承
*/
public abstract class Pizza {
protected String name;//pizza名
//不同pizza准备不同,所以留给实现类去实现
public abstract void prepare();
public void bake(){
System.out.println(name + " baking");
}
public void cut(){
System.out.println(name + " cutting");
}
public void box(){
System.out.println(name + " boxing");
}
public void setName(String name){
this.name = name;
}
}

/*
* 奶酪pizza
*/
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("制作奶酪pizza准备材料ing");
}
}

/*
* 希腊pizza
*/
public class GreekPizza extends Pizza {
@Override
public void prepare() {
System.out.println("给希腊pizza准备原材料ing");
}
}

/*
* pizza订购类
*/
public class OrderPizza {
//获取pizza订单
private String getType(){
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type");
String type = br.readLine();
return type;
}catch (IOException e){
e.printStackTrace();
return "";
}
}
//构造器
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购类型
do {
orderType = getType();
if (orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("希腊pizza");
}else if (orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("奶酪pizza");
}else{
break;
}
//方便调试,把制作过程打印
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
}

/*
* 客户端
*/
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza();
}
}

这样我们的一个 pizza 线上商店就全部完成了,似乎是一个很完美的过程呢。

优点
传统思路,便于理解
缺点
违反了在设计模式七大原则里面说过的 ocp 原则,即对扩展开放,对修改关闭

具体一些:Pizza 作为提供方,如果商店要增加一个种类,那么除了要增加一个 Pizza 类【提供方】之外,依赖的 Order 类也要增加对于新 Pizza 的处理【使用方】(甚至扩展起来,还有别的依赖于 Pizza 的使用方操作类,全部都要修改),而按照 Ocp 原则,使用方不应该修改,所以我们要避免这种情况。


一、简单工厂模式

对于上面的缺点,我们把创建 Pizza 的过程封装到一个类里,然后以这个类为中介进行创建,而不是再在Order里面直接创建。这样增加【提供方】 Pizza 的时候,修改这个类就可以了。

简单工厂模式(静态工厂模式)



  1. 属于创建型模式,是工厂模式的一种,由一个工厂对象来决定创造出哪一种产品类的实例。他是工厂模式家族里最简单实用的模式;

  2. 简单工厂模式定义了一个创建对象的类,由这个类来封装实例化对象的行为;

  3. 在实际开发中,如果会用到大量创建某种对象时,就会用到工厂模式。

/*
简单工厂类,根据类型创建pizza对象并返回
*/
public class SimpleFactory {
public Pizza createPizza(String orderType){
System.out.println("使用简单工厂模式");
Pizza pizza = null;
if (orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("希腊披萨");
}else if (orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
}else if (orderType.equals("pepper")){
pizza = new PepperPizza();
pizza.setName("胡椒披萨");
}
return pizza;
}
}

/*
* pizza订购类
*/
public class OrderPizza {
SimpleFactory factory;
Pizza pizza = null;
//构造器
public OrderPizza(SimpleFactory factory) {
setFactory(factory);
}
//创建过程改为调用工厂类
public void setFactory(SimpleFactory factory){
String orderType = "";
this.factory = factory;
do {
orderType = getType();
pizza = this.factory.createPizza(orderType);
//打印制作过程
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else{
System.out.println("订购失败");
break;
}
}while (true);
}
//获取pizza种类
private String getType() {
//和前面一样
}
}

/*
* 客户端
*/
public class PizzaStore {
public static void main(String[] args) {
// new OrderPizza();
//使用简单那工厂模式
new OrderPizza(new SimpleFactory());
System.out.println("结束");
}
}

类图变成了这样:


技术分享图片

可以看到,复杂的依赖关系基本消失,工厂类 负责了所有对象的产生,因此他和使用方OrderPizza 的关系是聚合关系。

这样一来,如果有新增的 Pizza 类,除了类的创建之外,就只用修改工厂类里面的新建方法,而对于使用方,OrderPizza、或者即将使用 Pizza 类的其他增加进来的使用方,代码都不用修改,因为他们都是调用工厂类进行对象的创建。

(其实就是为了方便扩展,多抽象出来一层的思想)

补充:

简单工厂模式之所以又叫静态工厂模式,就是因为可以直接把工厂类的 create 方法写成静态,这样的话,就不用 new 出工厂对象,而是只需要调用它的静态方法就可以。

public OrderPizzaviaStatic() {
String orderType = "";
do {
orderType = getType();
pizza = SimpleFactory.createPizza(orderType);//静态方法
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else{
System.out.println("订购失败");
break;
}
}while (true);
}

使用静态与否,根据具体情况来修改


二、工厂方法模式

工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类

思考这个问题

如果新增了一个需求,对于pizza,每次要新加地域属性,比如现在还是3种pizza,但是有3个地方,各有这三种pizza。客户点单的时候也会输入地域属性。



  • 解决方法:创建的时候利用简单工厂模式,新建不同地域的工厂,并且每个工厂里带上地域属性,其他不变,就可以实现扩展。

  • 但是这种方法不是最优,如果不同工厂很多的话,会需要多个工厂类。

其实把工厂类单独作为普通类来考虑,我们能想到的改进思路就是,在他们基础上,再提取公共接口或者抽象类。事实上,工厂方法模式的确是基于这种思路的。

使用工厂方法模式,将实例化功能抽象成抽象方法(而不是像简单工厂模式那样直接实现),在不同地域点餐子类中实现抽象方法


技术分享图片

根据这个类图很容易看得出来,其实只是对于Order部分做了一个抽象,其他的和之前的一般写法没有区别。

虽然没有出现factory字样,但是其实两个Order子类充当了Factory的作用



  • Order 根据不同的 type 去调用不同的子 Order,做 create 的工作;

  • 每个 Pizza 类里什么也不做。

所以和上面简单工厂的区别就是,这个根据地域分别写了 Order 类,将 OrderPizza 作为抽象类去组织这几个。

因此这一版代码,一个抽象 Pizza 类和4个子 Pizza 类完全没有变化,剩下三个类的写法如下:

/*
orderPizza类,根据type调用不同的工厂
*/
public abstract class OrderPizza {
//抽象方法,具体根据地域不同,让工厂子类去实现
abstract Pizza createPizza(String orderType);
//构造器
public OrderPizza(){
Pizza pizza = null;
String orderType;
do {
orderType = getType();
pizza = createPizza(orderType);//调用的是抽象方法
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else {
System.out.println("订购失败");
break;
}
}while (true);
}
private String getType() {
//和之前一样
}
}

/*
北京pizza工厂类
*/
public class BJOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")){
pizza = new BJCheesePizza();
pizza.setName("北京奶酪pizza");
}else if (orderType.equals("pepper")){
pizza = new BJPepperPizza();
pizza.setName("北京胡椒pizza");
}
return pizza;
}
}

/*
上海pizza工厂类
*/
public class SHOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")){
pizza = new SHCheesePizza();
pizza.setName("上海奶酪pizza");
}else if (orderType.equals("pepper")){
pizza = new SHPepperPizza();
pizza.setName("上海胡椒pizza");
}
return pizza;
}
}

最后,我们还是用一个 PizzaStore 类模拟一下客户端,接受不同的输入,来调用不同的 Order 类就可以。


三、抽象工厂模式

抽象工厂模式:



  1. 定义一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类

  2. 将简单工厂模式和工厂方法模式进行整合,其实相当于将简单工厂模式再网上抽象一层

  3. 包含AbstractFactory(抽象工厂)和具体实现工厂子类,根据需要的类型使用对应的子类,这样简单工厂类变成了工厂簇,更有助于代码维护

这种实现方式的类图是这样的:


技术分享图片

相比上面的工厂方法模式,抽象模式抽象出来一个Factory接口,两个实现类分别作为具体的工厂去实现接口的方法,然后聚合到Order类上,整体思路和上一种是一样的。

代码层面:

所有Pizza的内容都一样,BJFactory 和 SHFactory 和上面所说的,用工厂方法模式写的代码也一样,不过改成实现接口 AbstractFactory,而不是实现order抽象类。

AbstractFactory:

/*
抽象工厂模式的抽象层:接口
*/
public interface AbstractFactory {
//让下面子类实现具体
public Pizza createPizza(String orderType);
}

OrderPizza 类和第二种模式的 OrderPizza 类基本是一样的;
区别就在于,不是根据传入的 Pizza 的 type 直接 create,而是去调用 Factory 实现类,所以是和第一种模式代码一样。

/*
聚合的order类,和工厂接口交互
*/
public class OrderPizza {
AbstractFactory factory;
//构造器
public OrderPizza(AbstractFactory factory){
setFactory(factory);
}
//根据不同实现类进行构造
private void setFactory(AbstractFactory factory){
Pizza pizza = null;
String orderType = "";
this.factory = factory;
do {
orderType = getType();
//factory是接受的不同factory,取决于不同实现类
pizza = factory.createPizza(orderType);
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else{
System.out.println("订购失败");
break;
}
}while (true);
}
private String getType() {
}
}

最后,我们写上 Store类就可以,根据输入的不同,new出 Order 类,并且传入不同地域的工厂参数。

总结一下可以发现,这三种工厂模式,大同小异,就是对于简单工厂模式的不同程度的抽象。


四、工厂模式在JDK的应用

jdk 的 Canlendar类中,使用的就是 简单工厂模式

当我们写下这行代码:

Calendar calendar = Calendar.getInstance();//静态方法

的时候,getInstance 方法去调用了


技术分享图片

createCalendar 方法,返回一个 Canlendar 实例。

而在创建的方法里面如下:


技术分享图片

做一系列判断,看里面获取的各种参数,相当于这个工厂根据不同的情况去决定选择哪一种(具体在这个类里是时区、地理位置之类的),最后将创建好的 calendar 返回。

也就是对应我们说的工厂模式第一种,简单工厂模式。


五、总结

工厂模式的意义

将实例化对象的代码提取出来,放到一个类里面统一管理和维护,达到各种依赖关系的解耦,从而提高项目的可扩展性和可维护性。(最后是达到设计模式的 ocp 原则)

工厂模式分为三种:



  1. 简单工厂模式:如果要创建一种对象的行为比较多,将所有的工作放到一个工厂类里,每次调用这个类的方法就可以。

  2. 工厂方法模式:如果创建的对象种类要增加,那就分类多写几个工厂类,让他们实现一个抽象方法,这样能够做了区分后调用不同的工厂类。

  3. 抽象工厂模式:再加一层抽象工厂层,那么操作层面就只和固定的抽象工厂类交互,不用管有几种工厂实现类。

工厂模式体现的编码技巧:



  • 创建对象实例的时候不要直接 new 类,而是要把这个动作交给一个工厂的方法里,并且返回。

  • 不要让类直接继承具体的类,而是继承抽象类或者实现接口。

  • 不要覆盖基类中已经实现的方法。


推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
author-avatar
臭小子
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有