定义
define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate.
定义一个创建对象的接口或抽象类,但让子类决定实例化那个具体的类。
实列
生活中,有很多的企业、店铺、公司会为我们生产各种各样的商品,作为消费者的我们只关心如何使用这些产品,而不需要知道它们的制作过程。
比如说,包子店它会生产肉包、菜包、豆腐包等各式各样的包子,如果我们决定早上吃豆腐包中午吃豆沙包,都只需要告知店铺便可以获得生产好的包子,而不需要自己生产。
工厂模式中的工厂就好比上面的包子店,但在工厂模式中我们把这类相似的商品称为产品,它们不仅在形态上相似,而且创建过程也非常相似。
故事
前不久,我入职了一家开发射击类游戏的公司,并负责公共组件枪支库的开发。
枪支库中有手枪、冲锋枪、步枪等不同类型的枪支产品,它们都继承同一个抽象类Gun。
这个抽象类声明了三个操作,一个是shoot用于射击目标对象,一个是setBullet用于装子弹,还有一个是load用于上膛。
公司其它部门的同事,如果要将这些产品应用到不同的射击游戏中,那么他们需要先使用new实例化对象,然后给对象装上与之匹配的子弹并上膛,便可以调用shoot操作了。
伪代码如下:
public class Client {
public static void main(String[] args) {
String gunName = args[0];
String target = args[1];
if("HANDGUN".equals(gunName)){
Handgun handgun = new Handgun();
handgun.setBullet(new HandgunBullet());
handgun.load();
//射击目标
handgun.shoot(target);
}else if("SMG".equals(gunName)){
SMG smg = new SMG();
smg.setBullet(new SMGBullet());
handgun.load();
//射击目标
smg.shoot(target);
}else if("RIFLE".equals(gunName)){
Rifle smg = new Rifle();
smg.setBullet(new RifleBullet());
handgun.load();
//射击目标
smg.shoot(target);
}
}
}
问题
从上面的代码中我们可以看出,这款游戏会根据用户选择的枪支来射击目标对象。
但是,在客户端使用new关键字创建产品会存在几个问题。首先是扩展问题,如果后续射击游戏需要增加新的枪支,那么就要修改客户端代码。
其次是耦合问题,因为枪支是游戏的基础类,一旦它发生变化,那么所有的客户端都很可能都要跟着变化,比如说重命名某个枪支类的名称。
最后是重复问题,装弹、上膛是射击前的初始化操作,如果枪支库被用于多款射击游戏,那么创建对象的过程就会重复。
所以,有没有一种方式可以让客户端不操心产品是如何创建的?这便是工厂模式。
方案
工厂模式是一种创建型设计模式,它会将同类产品的创建以及初始化操作封装到独立的类即工厂类(Facotry)中。
工厂会向外暴露一个创建产品的操作,客户端只需调用该操作就可以获得指定的对象,而不需要知道产品是如何创建以及如何初始化的。
这样,工厂便解藕了客户端和产品之间的直接依赖关系,以及复用了初始化过程。
实现
工厂模式分为简单工厂模式和工厂方法模式以及抽象工厂模式,下面我们来看看前两种模式。
简单工厂模式
首先,我们创建一个名为简单工厂的类,将对象的创建以及初始化操作封装起来。
public class SimpleFactory {
public static Gun create(String gunName){
Gun gun;
if("HANDGUN".equals(gunName)){
gun = new Handgun();
gun.setBullet(new HandgunBullet());
}else if("SMG".equals(gunName)){
gun = new SMG();
gun.setBullet(new SMGBullet());
}else if("RIFLE".equals(gunName)){
gun = new Rifle();
gun.setBullet(new RifleBullet());
}
gun.load();
return gun;
}
}
然后,修改客户端代码,将创建对象的任务委托给简单工厂来创建,这样客户端就能多态地使用对象了。
public class Client1 {
public static void main(String[] args) {
String gunName = args[0];
String target = args[1];
Gun gun = SimpleFactory.create(gunName);
gun.shoot(target);
}
}
可以看出,简单工厂模式的实现非常的简单,但是它没有解决扩展的问题,当新增产品时还是需要修改简单工厂类。
工厂方法模式
在工厂方法模式中,不同的产品由不同的工厂类创建,而不是像简单工厂一样由一个工厂创建,工厂和产品之间通常是一对一的关系,这些工厂都继承自一个共同的抽象类。
下面我们看看工厂模式又是如何实现的:
首先,我们创建一个抽象工厂的类,定义产品的创建流程以及声明子类要实现的差异化操作。
/**抽象工厂类*/
public abstract class AbstractFactory {
/**定义创建对象的标准流程*/
public Gun create(){
Gun gun= doCreate();
//上膛
gun.load();
return gun;
}
/**子类差异化实现*/
protected abstract Gun doCreate();
}
然后,创建具体的工厂类实现差异的部分,比如,我们为手枪单独创建一个工厂。
/**手枪工厂*/
public class HandgunFactory extends AbstractFactory{
@Override
protected Gun doCreate() {
Handgun handgun = new Handgun();
//装子弹
handgun.setBullet(new HandgunBullet());
return handgun;
}
}
最后,我们在来看看客户端如何使用工厂方法模式创建产品。
public class Client {
public static void main(String[] args) {
String gunName = args[0];
String target = args[1];
//具体工厂的创建可以使用Java的反射技术
AbstractFactory factory ;
if("HANDGUN".equals(gunName)){
factory = new HandgunFactory();
}else if("SMG".equals(gunName)){
factory = new HandgunFactory();
}else if("RIFLE".equals(gunName)){
factory = new HandgunFactory();
}
Gun handgun = factory.create();
//射击目标
handgun.shoot(target);
}
}
很明显,工厂方法模式没有简单工厂模式那样简单,甚至看上去比不使用工厂还复杂。
但是,用一个工厂创建一种产品,能解决扩展性方面的问题。前提是具体工厂的创建得使用反射,这里我们就不再深入,读者可以自行了解一下。
结构
avatar产品(Product):这里的产品指的是继承或实现同一接口或父类的对象,它们同属一个家族。
抽象工厂(AbstractFactory):它是对创建对象行为的抽象,声明了可相互替代产品的创建接口。如果创建涉及对象初始化,那么可以在抽象类中定义共同的创建流程。
具体工厂(ConcreteFactory):它实现自抽象工厂,负责具体产品的创建。
总结
当程序需要根据不同的条件创建不同的产品,而且产品的数量会持续增加时,为了避免客户端和具体产品的耦合,那么我们应该考虑使用工厂方法模式。
工厂方法模式可以解决简单工厂解决不了的问题:扩展性问题,它可以在增加新的产品时不修改客户端代码,做到插件式地动态扩展产品。
网友评论