在众多的设计原则中,有一条原则是这么说的:要针对接口编程,不要针对实现编程。
针对接口编程的话,可以使用不同的实现类来创建这个对象。比如需要一个List
:
List<String> list=new ArrayList<>();
而当我们需要使用LinkedList
的时候,我们需要做的只是将上面的new
关键字后面的类型变为LinkedList
即可:
List<String> list=new LinkedList<>();
而使用list
的其它地方的代码都不需要修改,因为我们使用list
的地方,使用的都是List
接口声明的方法,而不管是ArrayList
还是LinkedList
,都实现了那些方法。
这么做的好处就是降低了对象之间的耦合,在需求发生修改的时候对尽可能少的代码进行修改。
那么问题来了,对于需要使用List
对象的客户(我们可以这么说,因为List
是我们程序员通过new
关键字创造出来的,那么使用它的人就是客户),如果想通过一种方式控制需要的具体实现类型,比如提供一个String
类型的变量which
,来告诉我们需要new
一个哪种类型的List
的话,应该怎么做呢?
简单的办法就像下面这样检查这个which
是什么:
if(which.equals("ArrayList")) list=new ArrayList<>();
else if(which.equals("LinkedList")) list=new LinkedList<>();
这样的话,就通过一个变量来控制我们创建对象的实现类了。进一步,可以把这个过程封装成一个类的静态方法,比如:
//这里仅仅演示这个方法,对于泛型什么的细节就不考虑了
public class ListFactory {
public static List createList(String which) {
if(which.equals("ArrayList")) return new ArrayList();
else if(which.equals("LinkedList")) return new LinkedList();
else //oops, no Concrete List you want
}
}
这个静态的方法,由于具有根据需求产生一个对象的功能,就叫做静态工厂方法(Static Factory Method),用设计模式的行话来说,也是简单工厂模式(Simple Factory Pattern)。
从上面那个简单的例子可以看出来,这个模式其实挺简单的,就是一个静态方法(工厂),产生对象(产品),然后返回。
这和现实生活中的思想一样。想一想,我们生活中会需要各种家用电器(洗衣机、冰箱等),但是我们会自己买零件组装吗?毕竟这个产品(对象)的创建过程挺费劲的,我们只好把这个需求告诉工厂了(调用工厂方法),工厂知道需求后(根据我们的需求,比如上面的那个which
)创建具体的产品并返回。完活儿。
这个模式的一般性结构如下图:
从上图可以看到,这个简单的模式有三个角色:
1.工厂类(Factory):这是工厂方法模式的核心(还有几个工厂模式呢),生产产品的具体逻辑就在这里。在代码的其他地方如果需要对象(产品),就会调用这个类的方法创建对象,通常使用一个Java类来实现。
2.抽象产品(Product):说是抽象的,是因为工厂生产的产品可以划分为一个具体类别(比如汽车等)。但是这个名称太抽象了,没有具体指定是哪产品(比如化石燃料动力还是电动力等)。这个抽象产品的角色基本上使用一个Java接口或抽象类实现。
3.具体产品(Concrete Product): 这是工厂所生产的具体的产品(对象),通常是一个实现了抽象产品接口(或继承了抽象类)的具体Java类。
下面是这个模式的基本代码结构:
(1)工厂:
public class Factory {
//注意,这是一个静态方法
public static Product createProduct() {
//根据具体的逻辑创建相应的对象,并返回
return new ConcreteProduct();
}
}
(2)抽象产品
//基本上,我们是面向接口编程,利于松耦合
public interface Product {
}
(3)具体产品
//具体的产品实现了抽象产品的接口,可能会有多个实现,比如List接口的多个实现
public class ConcreteProduct implements Product {
}
当我们使用简单工厂模式创建对象并使用的时候,会是这样:
Product product = Factory.createProduct();
//使用product对象
上面给出的例子和结构都是很简单的,事实上现实中的例子都是复杂的,比如,产品可能会产生层状结构:
这个时候,可以扩展工厂类中的静态方法,根据逻辑创建各个产品。
从这里就可以看出来了,简单工厂模式的一个缺点,就是当产品增加或者发生变化时,工厂类中的静态方法也需要改变。确实是这样,这个问题后面再讨论。
还有,工厂类中可以有多个静态方法:
public class Factory {
//静态方法1
public static Product createProduct() {
//创建逻辑1
}
//静态方法2
public static Product createProduct1() {
//创建逻辑2
}
}
如果只有一个具体的产品类的话,那么也就没有抽象产品这个角色的必要了,这个时候工厂可以直接产生具体产品:
public class Factory {
public static ConcreteProduct createProduct() {
}
}
一些情况下,可以将工厂角色由抽象产品扮演,也就是说,这个抽象产品(这个时候,这个抽象产品就不是接口了,而是抽象类)可以创建具体的产品。其实,Java中的java.text.DateFormat
类就是这样的,下面会有具体的介绍。
更进一步,可以将三个角色合并到一个角色中,一个产品自己就可以创造自己:
public class ConcreteProduct {
public ConcreteProduct() {}
public static ConcreteProduct createProduct() {
return new ConcreteProduct();
}
}
上面已经提到过了,在Java中的java.text.DateFomat
类就使用了简单工厂模式。
DateFomat
类是一个抽象类,有一个子类是SimpleDateFormat
。但是查看DateFomat
的代码时,发现这里面有一些静态方法:
public final static DateFormat getTimeInstance();
public final static DateFormat getTimeInstance(int style);
public final static DateFormat getTimeInstance(int style, Locale aLocale);
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,
Locale aLocale);
这些静态方法返回的并不是通过构造函数创建的DateFormat
对象(其实抽象类也不能实例化),而是使用了多态性,返回的是继承了这个类的子类SimpleDateFormat
。这样,使用SimpleDateFormat
的代码就不需要了解这个对象是如何创建的了。
简单工厂模式的核心就是工厂类,为了创建符合需求的对象,这个类的方法需要有创建对象的具体逻辑。随着对象的复杂,这个逻辑也会很复杂,这样,使用对象的代码就不需要花费精力在创建对象上了。工厂负责对象的创建,客户代码仅仅提出需求,不用涉及对象的创建,只使用就可以了。这样,达到了责任的分割。
不过这个模式也有缺点。具体就是当出现新产品的时候,工厂类的静态方法需要修改。比如,在前面的例子中,如果多了个List
实现类MagicList
,如果客户代码需要这个对象的时候,工厂类的静态方法就需要修改了:
public class ListFactory {
public static List createList(String which) {
if(which.equals("ArrayList")) return new ArrayList();
else if(which.equals("LinkedList")) return new LinkedList();
else if(which.equals("MagicList")) return new MagicList();
else //oops, no Concrete List you want
}
}
如果只是这么一层层摞的话,想想就闹心(虽然可以使用switch
,但逻辑上还是一样)。
除此,还有一个缺点,就是这里的例子仅仅只有一种类型的产品,如果产品种类多了的话,只放在一个工厂中就不合适了,这个问题可以在工厂方法模式中得到解决。
这就是简单工厂模式,蛮简单的。
参考资料:《Java与模式》,阎宏