抽象工厂模式是工厂方法模式的进一步推广。它的定义是:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
抽象工厂的使用场景如下:
客户端不关心产品类实例如何被创建、组合和表达的细节。这一条对于所有的工厂模式都是重要的;
这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品,这是抽象工厂的原始定义;
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
在本文举例中,会出现蒙牛牛奶、蒙牛酸奶,伊利牛奶、伊利酸奶等产品,而蒙牛的牛奶和酸奶就是一个产品族。蒙牛专卖店只消费蒙牛产品族而不会消费伊利的产品族。
抽象工厂的一般组成:
抽象工厂、具体工厂、产品族。
本文会演示抽象工厂的实例以及与其它工厂模式的对比,以及spring中如何应用工厂模式。
实例
业务场景
每一个奶制品专卖店都要进属于自己的产品族,例如蒙牛专卖店消费蒙牛产品族,伊利专卖店消费伊利产品族。
- 抽象牛奶接口
package com.faith.net;
/**
* 牛奶抽象接口
*/
public interface Milk {
/**
* 获取一个标准产品
* @return
*/
public String getName();
}
- 抽象酸奶接口
package com.faith.net;
/**
* 抽象酸奶接口
*/
public interface Yoghourt {
/**
* 获取一个标准产品
* @return
*/
public String getName();
}
- 蒙牛牛奶类
package com.faith.net;
/**
* 蒙牛牛奶
*/
public class MengniuMike implements Milk {
@Override
public String getName() {
return "蒙牛牛奶";
}
}
- 蒙牛酸奶类
package com.faith.net;
/**
* 蒙牛酸奶
*/
public class MengniuYoghourt implements Yoghourt {
@Override
public String getName() {
return "蒙牛酸奶";
}
}
- 伊利牛奶类
package com.faith.net;
import com.faith.net.Milk;
/**
* 伊利牛奶
*/
public class YiliMike implements Milk {
@Override
public String getName() {
return "伊利";
}
}
- 伊利酸奶类
package com.faith.net;
/**
* 伊利酸奶
*/
public class YiliYoghourt implements Yoghourt {
@Override
public String getName() {
return "伊利酸奶";
}
}
- 抽象工厂
抽象工厂中包含了具体工厂必须实现的方法定义。
package com.faith.net.abstr;
import com.faith.net.Milk;
import com.faith.net.Yoghourt;
/**
* 抽象工厂
*/
public abstract class AbstractFactory {
/**
* 获得牛奶
* @return
*/
public abstract Milk getMike();
/**
* 获得酸奶
* @return
*/
public abstract Yoghourt getYoghourt();
}
- 蒙牛工厂
package com.faith.net.abstr;
import com.faith.net.*;
/**
* 蒙牛工厂
*/
public class MengniuFactory extends AbstractFactory {
@Override
public Milk getMike() {
return new MengniuMike();
}
@Override
public Yoghourt getYoghourt() {
return new MengniuYoghourt();
}
}
- 伊利工厂
package com.faith.net.abstr;
import com.faith.net.*;
/**
* 伊利工厂
*/
public class YiliFactory extends AbstractFactory {
@Override
public Milk getMike() {
return new YiliMike();
}
@Override
public Yoghourt getYoghourt() {
return new YiliYoghourt();
}
}
- 客户端
package com.faith.net.abstr;
/**
* 客户端
*/
public class AbstractFactoryTest {
public static void main(String[] args) {
AbstractFactory factory = new MengniuFactory();
factory.getMike();
factory.getYoghourt();
}
}
当我们需要添加新的产品族时,例如特仑苏的牛奶和酸奶,只需要扩展AbstractFactory和Mike、Yoghourt就好。
抽象工厂与工厂方法
抽象工厂中,包含多个抽象产品类,每个抽象产品可以派生出多个具体产品类;工厂方法中只有一个可以派生出多个具体产品的抽象产品类。
抽象工厂中,每个具体工厂可以创建多个具体产品类的实例;工厂方法中只能创建一个具体产品类的实例。
spring 对抽象工厂的改进
根据抽象工厂的使用场景知道,一个项目只会一种产品族,所以这里的项目只会使用MengniuFactory或YiliFactory其中一种。
从上面的实现可以看出,客户端使用时需要明确指定要使用哪一个factory:
AbstractFactory factory = new MengniuFactory();
这样造成的问题是,假如代码中有100个地方都使用了MengniuFactory,而业务需求需要全部更改为YiliFactory时,则需要手动改100个地方为:
AbstractFactory factory = new YiliFactory();
这种方式的体验很不好,可以使用与简单工厂结合的方式实现:
package com.faith.net.abstr;
import com.faith.net.MengniuMike;
import com.faith.net.MengniuYoghourt;
import com.faith.net.Milk;
import com.faith.net.Yoghourt;
/**
* 获取奶制品工厂对象的工具类
*/
public class MilchigsFactory {
public static String FACTORY_NAME = "mengniu";
public static AbstractFactory getMilchigsFactory() {
AbstractFactory factory = null;
switch (FACTORY_NAME) {
case "mengniu":
factory = new MengniuFactory();
break;
case "yili":
factory = new YiliFactory();
break;
default:
throw new RuntimeException("wrong milchigsFactory name");
}
return factory;
}
}
添加这个工厂类,客户端使用时就可以直接使用:
MilchigsFactory.getMilchigsFactory();
而我们更改为YiliFactory只需要更改FACTORY_NAME的值就可以了。
但是这样,出现了简单工厂的巨大缺陷,就是当新增TelunsuFactory(特仑苏工厂)时,势必要修改case语句,违反开闭原则。这种情况可以通过配合反射来解决,我们把FACTORY_NAME的值放到专用的配置文件中,然后由程序读取配置文件,获得相应的factory对象。
例如,FACTORY_NAME在配置文件中的值可以类似如下:
FACTORY_NAME = com.faith.net.abstr.MengniuFactory
spring中数据库连接就是通过反射+抽象工厂,例如db连接可能为Mysql、Oracle等,如果使用Mysql,只要如下配置就好了:
driverClass=com.mysql.jdbc.Driver
这样,让程序更优雅、高效。