工厂设计模式

工厂设计模式

场景示例

比如现在需要对手机目录下的文档进行读写,考虑到不同的文档类型有不同的操作,于是抽象一个Document接口来抽象文档的读写过程,不同的文档实现这个Document接口,完成具体的读写过程。

Document接口定义如下:

public Interface Document {
	void read();

	void save();

	void edit();

	String path();
}

具体的文档类型定义示例如下:

// excel文档
public class Excel implements Document {
	void read() {
		// 读取excel内容的逻辑
	}

	void save() {
		// 保存excel内容的逻辑
	}

	void edit() {
		// 编辑excel内容的逻辑
	}

	String path() {
		return "excel file path"
	}
}

// 文本文件
public class Text implements Document {
	void read() {
		// 读取txt文本文件的逻辑
	}
	
	void save() {
		// 保存txt文件的逻辑
	}

	void edit() {
		// 编辑txt文本文件的逻辑
	}
	
	String path() {
		return "txt file path"
	}
}

// ... and so on 

由于文档的类型有多种,不同的文档读写方法也不尽相同,故设计了不同的子类来完成具体的读写操作。

在遍历到对应类型的文档类型时,我们需要实例化不同的子类来完成文档的读写。那么如何实例化这些子类呢?

我们可以将实例化的操作放在一个方法里,针对不同的文档类型实例化不同的子类并返回。伪代码如下:

public class DocumentFactory {
	// 创建文档对象
	public Document createDocument(int type) {
		Document document;
		if (type == TXT) {
				document = new Text();
		} else if (type == EXCEL) {
				document = new Excel();
		} else if (type == AUDIO) {
				document = new Audio();
		}
		
		return document;
	}
}

/**
 * 遍历文件夹的伪代码
 */
public void listFiles() {

	for (File f : fileList) {
		int type = f.type();
		Document document = DocumentFactory.createDocument(type)
		document.read();
	}
}

拿到对应的文档对象后,就可以直接调用读写的方法了。

这个就是简单工厂的设计模式了,createDocument就可以看成是工厂方法,根据类型生产一个个的产品(子类)。

在开发中,我们可能会遇到要创建本质相同,但表现又有所差异的对象来完成一些逻辑。(如此处的文档,但是各文档格式又不同)在设计了多个不同的子类后,为了方便的创建和获取子类,就可以采取工厂模式了。

记得在知乎有个答主用一句话描述了工厂模式的本质【工厂设计模式本质上是对获取对象过程的抽象】。个人觉得很贴切,也很好理解。为什么要对过程抽象呢?因为有些对象的创建过程并不容易,不是简单的通过new就可以创建的(此处的示例较为简略,只做说明之用)。结合此例,可以知道工厂的设计模式抽象了创建具体对象的过程,我们只提供类型,工厂方法帮我们生成对应的产品(子类对象),具体的生产过程我们不关心。

此时的工厂实现是一个具体的类,而不是接口或抽象类。如果我们需要增加子类对象的时候,我们就必须修改工厂方法,增加额外的if else判断来创建新的子类。但是这样就违背了开闭原则(设计模式中规定对功能扩展开放,对功能修改关闭,此处是直接修改了功能,故违背了开闭原则)。

对于类型不多的场景修改起来也没有太大的成本。但对于子类对象变更或新增频繁的场景,此类简单工厂设模式应付起来可能就捉襟见肘了。 那么,如何修改以便适应较多对象的创建过程呢?

在《程序员的自我修养》一书中有一句名言:

没有什么计算机问题是不能通过增加一层解决的。

我们可以通过增加一层逻辑层来处理这个问题。这个抽象的逻辑层就是工厂接口。

首先设计一个工厂接口,如下,抽象出生产产品的方法。

/**
 * 文档创建接口
 */
public interface IDocumentFactory {
	Document createDocument();
}

然后具体的产品生产放在具体的产品工厂中,伪代码如下:

public TxtFactory implements IDocumentFactory {
	Document createDocument() {
		// create txt document object
	}
}

其他的子类类同。此时,创建具体产品的逻辑过程就交给了具体的产品工厂了。 之前是在一个工厂方法中创建多个子类,现在是不同的子工厂生产不同的子产品,相当于是在整个过程中增加了一层来处理逻辑了,而整个增加的一层就是子工厂类。

在需要具体的产品时就可以调用具体的工厂生产具体的产品了。

IDocumentFactory factory = new TxtFactory();
Document txt = factory.createDocument();

/**
 * 遍历文件夹的伪代码
 */
public void listFiles() {
	IDocumentFactory factory;
	for (File f : fileList) {
		int type = f.type();
		if (type == TXT) {
				factory = new TxtFactory()
				document = factory.createDocument();
		} else if (type == EXCEL) {
				dofactory = new ExcelFactory()
				document = factory.createDocument();
    } else if (type == AUDIO) {
				dofactory = new AudioFactory()
				document = factory.createDocument();
		}
	}
}

从代码可以看出,之前是直接new对象,现在是通过工厂来创建对象,以此来屏蔽更多对象的生产细节,从而实现代码上的解耦。并且一个对象使用一个工厂来生产,这样就符合了设计模式的开闭原则了(新增产品创建时只需新增对应的工厂类即可)。

这便是工厂模式,与简单的工厂模式不同,在创建对象时使用专有的工厂类来创建。但是在产品类较多的情况下,工厂模式会大大增加类的个数(因为每个对象都需要一个对应的工厂类),一定程度上增加了系统的复杂性。

在工厂模式中,工厂方法中就不存在if else判断了,因为生产的都是具体的产品。 (但在此例中,只是将if else方法移到了业务层了。 如何在业务调用层也减少if - else的判断,可以参看其他网络博客,主要有反射、使用map、枚举等方式,此处不在赘述)。

此处示例只有简单几种产品对象,在涉及产品族(某个大类产品下,又具体细分几个子类产品)的情况下,则可以使用抽象工厂方法

此时,需要对工厂方法进行一定的抽象。一个工厂方法可以创建同大类不同子类的对象,然后对不同的工厂抽象出相同的部分,得到顶层的抽象工厂。 此处的文档示例可能不太合适,故参考此篇博客。 在此基础上理解,感谢原作者分享。

如,创建车,车有品牌和类型属性,比如品牌有benz和telsa,类型有跑车和商务类型车,此时两两组合就有了四种类型了。

  1. telsa 跑车
  2. telsa 商务车
  3. benz 跑车
  4. benz 商务车

首先,我们抽象出车这个概念,然后衍生出telsa车和benz车,继而衍生出telsa跑车和telsa商务车,同理benz车。 伪代码如下:

// 车类接口
public interface ICar {
	
}

public class TelsaCar implements ICar {

}

public class BenzCar implements ICar {

}

// 再次拓展

public class TelsaSportCar extends TelsaCar {
	
}

public class TelsaBusinessCar extends TelsaCar {

}

public class BenzSportCar extends BenzCar {
	
}

public class BenzBusinessCar extends BenzCar {

}

有了这么多类型,就需要有不同的工厂类来生产这些对象或产品。抽象出如下的工厂类

public class SportFactory {
		getBenzCar()
		getTelsaCar()
}

public class BusinessFactory {
	getBenzCar();
	getTelsaCar();
}

(也可以设计TelsaFactory和BusinessFactory,原理类同)

此时,可以看到这两个工厂都有相同的方法,可以将这些相同或公共的方法,再抽象一层出来,作为顶层的抽象工厂,就可以得到更加灵活的结构。伪代码如下:

public abstract class AbstractCarFactory {
	getBenzCar();
	getTelsaCar();
}

// 然后将上面的工厂修改为扩展该抽象工厂类。

通过抽象工厂,定义生产不同产品大类的方法,不同的子类产品再通过对应的具体工厂来生产产品。

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。

分析

从上面的分析过程来看,工厂模式其实是一个不停的对创建对象过程或逻辑进行包装的过程,每包装一层代码逻辑在一定程度上进行了解耦,比如以前要访问对象A,并且对象A在版本不断迭代的过程中接口或逻辑会不断变化,那么访问对象A的这个对象可能就要被迫修改代码。但如果将A包装一层,在访问对象A时就变成访问对象A的包装层,而包装层的变动肯定比对象A变动的几率小,所以此时即使对象A修改对象属性或行为,只要包装层不变动,其他的代码也就不需要变动。

对于对象的参数是不固定的,推荐使用Builder创建模式。

关于工厂模式,让我想到了电视剧《楚汉骄雄》中刘邦问韩信的一段话,当时刘邦准备封被萧何追回来的韩信为大将军。刘邦问他,他如何能指挥十万大军?韩信从容不迫的说,很容易,我只需要十个听话的将军即可。多么霸气的一段话,只需十个将军即可。

这个过程感觉和工厂模式有点类似,即我只抓最顶层,底层的实现我是不关心的。只要抓住了顶层,底层在怎么变化也不会影响整体。

类图

以下图片copy自知乎,能直观的查看几个工厂方法之间的差异,仅做笔记查看用,见具体链接。感谢原作者分享。

  • 简单工厂模式
    在这里插入图片描述

  • 工厂模式

在这里插入图片描述

  • 抽象工厂模式
    在这里插入图片描述

总结

模式优点缺点适用场景
简单工厂模式1、屏蔽产品的具体生产细节,调用者只关心生产产品的接口。
2、一个工厂类生产所有的产品,实现简单。
1、增加产品时需修改工厂类,不符合设计原则种的开放-封闭原则。
2、在工厂类集中了所有的对象创建逻辑,违反了高内聚的原则。
1、产品类较少且产品类不易变动的场景。
2、调用者不关心产品对象的创建,只知道传入工厂的类型参数。
工厂模式1、继承了简单工厂模式的优点。2、符合开放-封闭的设计原则。1、增加产品时需要增加额外的工厂类,一定程度增加了系统的复杂性。1、产品类型容易变更的场景。
2、调用者不关心对象的创建,只关心产品对象。
抽象工厂模式1、继承工厂模式的优点。
2、增加或替换产品组方便。
1、增加一类具体的子产品时较繁琐,需要创建不同类型产品类、修改现有工厂方法以增加对新产品支持。1. 系统不应依赖于产品类实例如何被创建、组合和表达的细节。
2、系统不应依赖于产品类示例如何被创建、组合和表达的细节。

摘抄一段知乎《阿里巴巴大淘宝技术》答主总结

  • 简单工厂:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。
  • 工厂方法:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。
  • 抽象工厂:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了工厂子类的数量。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值