设计模式剖析——简单工厂模式Simple Factory Pattern
含义
- 工厂类负责生成产品类对象,即调用产品类构造器的活交给了工厂类,这样客户端程序员只需要调用产品类对象的方法即可。
- 工厂类中,返回产品类对象的方法是一种工具方法,因为这个方法通过不同参数返回所有可能的具体产品,所以该方法最好是静态方法。
解决的问题
原本客户端程序员需要做:
- 调用产品类构造器(如果这样,则必须import具体产品类)
- 对产品类对象调用其方法
但现在客户端只需要做第二步就可以了,虽然不需要做第一步,但需要知道产品类的接口(可能是一个抽象类abstract class、接口interface),还需要知道中间层也就是工厂类。
这样,客户端不用知道具体产品类就可以获得产品类对象,并调用其方法。因为这样避免了在客户端中显示指定具体产品,从而实现了解耦。
鲁迅曾经说过:
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
那么这个工厂类就是我们想要的中间层了。
模式组成
组成 | 关系 | 作用 |
---|---|---|
抽象产品(product) | 具体产品的父类 具体产品的接口 |
为具体产品提供父类 为客户端提供接口 |
具体产品(concrete product) | 抽象产品的子类 工厂创建的目标类型 |
为抽象产品提供具体实现 客户端可以通过接口调用实现 |
工厂(factory) | 作为中间层联系起客户端和产品类 | 访问具体产品类并生成实例 通过静态方法返回给客户端具体产品实例 |
UML图
代码
首先是产品类product,它放到了other包中。注意只有抽象类product才有public的访问权限,而其他的具体产品类都只有默认的包访问权限。
package other;
public abstract class Product{
public abstract void Show();
}
//具体产品类A
class ProductA extends Product{
@Override
public void Show() {
System.out.println("生产出了产品A");
}
}
//具体产品类B
class ProductB extends Product{
@Override
public void Show() {
System.out.println("生产出了产品B");
}
}
//具体产品类C
class ProductC extends Product{
@Override
public void Show() {
System.out.println("生产出了产品C");
}
}
然后是工厂类Factory,也是在other包中。其有public访问权限。静态方法通过字符串参数返回不同的具体产品类。由于Factory和Product在同一个包中,所以Factory既可以访问抽象产品类也可以访问具体产品类,这也是必须的。
package other;
public class Factory {
public static Product Manufacture(String ProductName){
//工厂类里用switch语句控制生产哪种商品;
//使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
switch (ProductName){
case "A":
return new ProductA();
case "B":
return new ProductB();
case "C":
return new ProductC();
default:
return null;
}
}
}
最后是测试类,也就是客户端程序员的代码,它应该和以上两个类在不同的包中,所以我把它放到了默认包中。
- 客户端里只需要
import other.Factory
,而不需要import具体产品类,说明实现了解耦。 - 客户端里需要对
other.Product
这个抽象类有访问权限,所以public abstract class Product
是public
的;但不需要对具体产品类有访问权限,所以class ProductA extends Product
的访问权限是默认的包访问权限。 - 能够调用show方法,也是因为
public abstract class Product
是public
的,如果去掉public,那么每个show方法处,将报错cant access show() in other.Product
。
import other.Factory;
//import other.Product; 加入此句会报警告,此import从未被用到
public class SimpleFactoryPattern {
public static void main(String[] args){
//客户要产品A
try {
//调用工厂类的静态方法 & 传入不同参数从而创建产品实例
Factory.Manufacture("A").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品B
try {
Factory.Manufacture("B").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品C
try {
Factory.Manufacture("C").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品D
try {
Factory.Manufacture("D").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
}
}/*output:
生产出了产品A
生产出了产品B
生产出了产品C
没有这一类产品
*/
这里再单独解答两个小疑点:
- 之所以不需要
import other.Product
且就算写了这句也会报警告unused import statement
,是因为:在测试类确实没有用到other.Product
这个名字(对返回的对象直接调用方法,而没有用引用去接这个对象),既然没有用到,那么就算import了也会告诉你没有用到。 - 针对上一点,你可以修改代码为
Product a = Factory.Manufacture("A"); a.Show();
,这时就需要import other.Product
了。 - 之所以要加try catch,之所以每个show处会报异常(此处可能引发空指针异常),是因为工厂类的switch逻辑中,default行为返回的是null。
优点
- 客户端不需要创建具体产品类的对象(将使用
new
关键字的任务交给了工厂类),只需要关心调用工厂类静态方法时的参数。 - 客户端不需要修改代码,修改代码的任务给了工厂类。比如,
ProductA
升级为了ProductAPlus
,那么只需要修改工厂类里的一行代码即可。
缺点
- 当具体产品类很多时,则工厂类的逻辑会显得很臃肿。
- 不符合【开放-关闭原则】,增加具体产品类时需要修改工厂类的静态方法。
应用场景
- 当需要隔离客户端代码和具体产品类时。(这一点是工厂方法的共性)
- 具体产品类的种类比较少时。