正文
工厂模式是Java中最常用的设计模式。工厂模式提供很好的创建对象的方式,属于创建型模式。
使用工厂模式创建对象是不向使用者暴露创建细节,并且可以通过统一的接口引用对象。
工厂模式的作用
首先,工厂模式是为了解耦:把对象的创建和使用的过程分开。就是Class A 想调用 Class B ,那么A只是调用B的方法,而至于B的实例化,就交给工厂类。
其次,工厂模式可以降低代码重复。如果创建对象B的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。我们可以这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的创建过程的修改维护。(当然,我个人觉得也可以把这些创建过程的代码放到类的构造函数里,同样可以降低重复率,而且构造函数本身的作用也是初始化对象。不过,这样也会导致构造函数过于复杂,做的事太多,不符合java 的设计原则。)
由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。同理,想把所有调用B的地方改成B的子类B1,只需要在对应生产B的工厂中或者工厂的方法中修改其生产的对象为B1即可,而不需要找到所有的new B()改为new B1()。
另外,因为工厂管理了对象的创建逻辑,使用者并不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。
举些例子:
1)一个数据库工厂:可以返回一个数据库实例,可以是mysql,oracle等。
这个工厂就可以把数据库连接需要的用户名,地址,密码等封装好,直接返回对应的数据库对象就好。不需要调用者自己初始化,减少了写错密码等等这些错误。调用者只负责使用,不需要管怎么去创建、初始化对象。
2)对象的创建职责和使用职责。
在Java语言中,我们通常有以下几种创建对象的方式:
(1) 使用new关键字直接创建对象;
(2) 通过反射机制创建对象;
(3) 通过clone()方法创建对象;
(4) 通过工厂类创建对象。
毫无疑问,在客户端代码中直接使用new关键字是最简单的一种创建对象的方式,但是它的灵活性较差,下面通过一个简单的示例来加以说明:
class LoginAction {
private UserDAO udao;
public LoginAction() {
udao = new JDBCUserDAO(); //创建对象
}
public void execute() {
//其他代码
udao.findUserById(); //使用对象
//其他代码
}
}
下面我们来分析一下LoginAction和UserDAO之间的关系,LoginAction类负责创建了一个UserDAO子类的对象并使用UserDAO的方法来完成相应的业务处理,也就是说LoginAction即负责udao的创建又负责udao的使用,创建对象和使用对象的职责耦合在一起。
这样的设计会导致一个很严重的问题:如果在LoginAction中希望能够使用UserDAO的另一个子类如HibernateUserDAO类型的对象,必须修改LoginAction类的源代码,违反了“开闭原则”。
最常用的一种解决方法是将udao对象的创建职责从LoginAction类中移除,在LoginAction类之外创建对象,那么谁来负责创建UserDAO对象呢?答案是:工厂类。通过引入工厂类,客户类(如LoginAction)不涉及对象的创建,对象的创建者也不会涉及对象的使用。
对象的创建和使用分离好处
1)单一职责原则:在所有的工厂模式中,我们都强调一点:两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。将对象的创建和使用分离,也使得系统更加符合“单一职责原则”,有利于对功能的复用和系统的维护。
2)防止用来实例化一个类的数据和代码在多个类中到处都是,可以将有关创建的知识搬移到一个工厂类中。
因为有时候我们创建一个对象不只是简单调用其构造函数,还需要设置一些参数,可能还需要配置环境,如果将这些代码散落在每一个创建对象的客户类中,势必会出现代码重复、创建蔓延的问题,而这些客户类其实无须承担对象的创建工作,它们只需使用已创建好的对象就可以了。
此时,可以引入工厂类来封装对象的创建逻辑和客户代码的实例化/配置选项。
3)使用工厂类还有一个“不是特别明显的”优点,一个类可能拥有多个构造函数,而在Java、C#等语言中构造函数名字都与类名相同,客户端只能通过传入不同的参数来调用不同的构造函数创建对象,从构造函数和参数列表中也许大家根本不了解不同构造函数所构造的产品的差异。
但如果将对象的创建过程封装在工厂类中,我们可以提供一系列名字完全不同的工厂方法,每一个工厂方法对应一个构造函数,客户端可以以一种更加可读、易懂的方式来创建对象,而且,从一组工厂方法中选择一个意义明确的工厂方法,比从一组名称相同参数不同的构造函数中选择一个构造函数要方便很多。
工厂模式适用的一些场景(不仅限于以下场景):
1.对象的创建过程/实例化准备工作很复杂,需要初始化很多参数、查询数据库等。
2.类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。
工厂模式四个分类
1、静态工厂模式
2、简单工厂模式
3、工厂方法模式
4、抽象工厂模式
一、静态工厂模式
很最常见,比如项目中使用的静态辅助类,StringUtis.isEmpty()等,类+静态方法。
二、简单工厂模式
简单工厂模式是工厂模式中最简单的一种,他可以用比较简单的方式隐藏创建对象的细节,一般只需要告诉工厂类所需要的类型,工厂类就会返回需要的产品类,但客户端看到的只是产品的抽象对象,无需关心到底是返回了哪个子类。客户端唯一需要知道的具体子类就是工厂子类。除了这点,基本是达到了依赖倒转原则的要求。
假如,我们不用工厂类,只用AbstractProduct和它的子类,那客户端每次使用不同的子类的时候都需要知道到底是用哪一个子类,当类比较少的时候还没什么问题,但是当类比较多的时候,管理起来就非常的麻烦了,就必须要做大量的替换,一个不小心就会发生错误。
使用了工厂类之后,就不会有这样的问题,不管里面多少个类,我只需要知道类型号即可。不过,这里还有一个疑问,那就是如果我每次用工厂类创建的类型都不相同,这样修改起来的时候还是会出现问题,还是需要大量的替换。所以简单工厂模式一般应该于程序中大部分地方都只使用其中一种产品,工厂类也不用频繁创建产品类的情况。这样修改的时候只需要修改有限的几个地方即可。
简单工厂模式其实不是一个设计模式,反而比较像一种编程习惯。主要我们定义一个非常简单的类主要负责帮我们生产不同的产品。类图如下:
客户端不关心产品生产的过程,只需要提出自己的需求,然后交由简单工厂来生产出具体产品A,具体产品B,具体产品C。
下面我们通过一个例子来讲解:卖pizza
pizzaStore 店 相当于客户端
在使用简单工厂模式前
package factory.demo1;
/**
* 简单工厂设计模式 (未使用)
* 披萨店
*/
public class PizzStore {
/**
* type 披萨口味
* 根据披萨口味type,售卖不同种类披萨
*/
public Pizz sellPizz(String type){
Pizz pizz = null;
if (type.equals("甜味")) {
pizz = new PizzA();
} else if (type.equals("辣味")) {
pizz = new PizzB();
}
pizz.hot();//加热披萨
pizz.pack();//包装披萨
return pizz;
}
}
PizzStore售卖的是披萨,那么需要一个Pizz 披萨 抽象类
package factory.demo1;
/**
* 简单工厂设计模式
* 披萨抽象实体类
*/
public abstract class Pizz {
protected String name;
/**
* 加热披萨
*/
public void hot()
{
System.out.println("加热披萨");
}
/**
* 包装披萨
*/
public void pack()
{
System.out.println("包装披萨");
}
}
Pizz 披萨 具有多种口味的
package factory.demo1;
/**
* 简单工厂设计模式
* 甜味-披萨实体类
*/
public class PizzA extends Pizz {
public PizzA(){
this.name ="这个是一个甜味-披萨";
}
}
package factory.demo1;
/**
* 简单工厂设计模式
* 辣味-披萨实体类
*/
public class PizzB extends Pizz {
public PizzB(){
this.name ="这个是一个辣味-披萨";
}
}
现在这样的设计,虽然可以实现披萨店售卖披萨,但是生产披萨过程和披萨店关联太多,或者说是耦合度太高了。
比如披萨店想要多增加几种口味的披萨,或者下架某种口味的披萨,那么久需要改动sellPizz()中方法。
那么为何不建立一个简单工厂,把生产各种口味披萨的需求全部交给简单工厂,披萨店只需要提出自己的需求,需要哪些口味的披萨即可。
建立SimplePizzFactory 简单工厂
package factory.demo1;
/**
* 简单工厂设计模式
* 简单工厂类
*/
public class SimplePizzFactory {
/**
* 根据传入的type参数,返回相应的pizza
* @param type
* @return
*/
public Pizz createPizz(String type) { //一般这个方法为static
Pizz pizz = null;
if (type.equals("甜味")) {
pizz = new PizzA();
} else if (type.equals("辣味")) {
pizz = new PizzB();
}
return pizz;
}
}
创建PizzStore 点售卖披萨
package factory.demo1;
/**
* 简单工厂设计模式 (使用)
* 披萨店
*/
public class PizzStore {
SimplePizzFactory factory;
public PizzStore(SimplePizzFactory factory) {
this.factory = factory;
}
/**
* 未使用简单工厂模式
* type 披萨口味
* 根据披萨口味type,售卖不同种类披萨
*/
/*public Pizz sellPizz(String type){
Pizz pizz = null;
if (type.equals("甜味")) {
pizz = new PizzA();
} else if (type.equals("辣味")) {
pizz = new PizzB();
}
pizz.hot();//加热披萨
pizz.pack();//包装披萨
return pizz;
}*/
/**
* 使用了简单工厂模式
* type 披萨口味
* 根据披萨口味type,售卖不同种类披萨
*/
public Pizz sellPizz(String type){
Pizz pizz = factory.createPizz(type);
pizz.hot();//加热披萨
pizz.pack();//包装披萨
return pizz;
}
}
现在我们新建一个测试类来看下结果,发现披萨店只需要提出(甜味)的需求即可,简单工厂会复制披萨生产过程。
好了,现在你随便添加什么种类的披萨,删除什么种类的披萨都和Store无关了,就是么, 这就是简单工厂模式。
package factory.demo1;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
SimplePizzFactory factory = new SimplePizzFactory();
PizzStore pizzStore = new PizzStore(factory);
Pizz pizz = pizzStore.sellPizz("甜味");
System.out.println(pizz.name);
}
}
常用的场景
例如部署多种数据库的情况,可能在不同的地方要使用不同的数据库,此时只需要在配置文件中设定数据库的类型,每次再根据类型生成实例,这样,不管下面的数据库类型怎么变化,在客户端看来都是只有一个AbstractProduct,使用的时候根本无需修改代码。提供的类型也可以用比较便于识别的字符串,这样不用记很长的类名,还可以保存为配置文件。
这样,每次只需要修改配置文件和添加新的产品子类即可。
所以简单工厂模式一般应用于多种同类型类的情况,将这些类隐藏起来,再提供统一的接口,便于维护和修改。
优点
1.隐藏了对象创建的细节,将产品的实例化推迟到子类中实现。
2.客户端基本不用关心使用的是哪个产品,只需要知道用哪个工厂就行了,提供的类型也可以用比较便于识别的字符串。
3.方便添加新的产品子类,每次只需要修改工厂类传递的类型值就行了。
4.遵循了依赖倒转原则。
缺点
1.要求产品子类的类型差不多,使用的方法名都相同,如果类比较多,而所有的类又必须要添加一种方法,则会是非常麻烦的事情。或者是一种类另一种类有几种方法不相同,客户端无法知道是哪一个产品子类,也就无法调用这几个不相同的方法。
2.每添加一个产品子类,都必须在工厂类中添加一个判断分支,这违背了开放-封闭原则。
三、工厂方法模式
工厂模式基本与简单工厂模式差不多,上面也说了,每次添加一个产品子类都必须在工厂类中添加一个判断分支,这样违背了开放-封闭原则,因此,工厂模式就是为了解决这个问题而产生的。
既然每次都要判断,那我就把这些判断都生成一个工厂子类,这样,每次添加产品子类的时候,只需再添加一个工厂子类就可以了。这样就完美的遵循了开放-封闭原则。但这其实也有问题,如果产品数量足够多,要维护的量就会增加,好在一般工厂子类只用来生成产品类,只要产品子类的名称不发生变化,那么基本工厂子类就不需要修改,每次只需要修改产品子类就可以了。
同样工厂模式一般应该于程序中大部分地方都只使用其中一种产品,工厂类也不用频繁创建产品类的情况。这样修改的时候只需要修改有限的几个地方即可。
常用的场景
基本与简单工厂模式一致,只不过是改进了简单工厂模式中的开放-封闭原则的缺陷,使得模式更具有弹性。将实例化的过程推迟到子类中,由子类来决定实例化哪个。
优点
基本与简单工厂模式一致,多的一点优点就是遵循了开放-封闭原则,使得模式的灵活性更强。
缺点
与简单工厂模式差不多。
建立SimplePizzFactory 抽象工厂
package factory.demo1;
/**
* 工厂方法设计模式
* 抽象工厂类
*/
public interface SimplePizzFactory {
public Pizz createPizz() ;
}
建立PizzAFactory 工厂实现类
package factory.demo1;
/**
* 工厂方法设计模式
* 工厂实现类
*/
public interface PizzAFactory implements SimplePizzFactory{
@Override
public Pizz createPizz() {
return new PizzA();
}
}
建立PizzBFactory 工厂实现类
package factory.demo1;
/**
* 工厂方法设计模式
* 工厂实现类
*/
public interface PizzBFactory implements SimplePizzFactory{
@Override
public Pizz createPizz() {
return new PizzB();
}
}
package factory.demo1;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
SimplePizzFactory factory = new PizzAFactory ();
Pizz pizz = factory.createPizz();
}
}
四、抽象工厂模式
抽象工厂:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例
工厂方法模式: 一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类只能创建一个具体产品类的实例。
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
如果你觉得本篇文章对你有所帮助的话,麻烦请点击头像右边的关注按钮,谢谢!
技术在交流中进步,知识在分享中传播