工厂方法模式定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 工厂方法模式的通用类图如下
在工厂方法模式中,抽象产品类Product负责定义产品的共性,事项对事物最抽象的定义,Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。
代码示例:
抽象产品类
/**
* 抽象产品类
*/
public abstract class Product {
//产品类的公共方法
public void method1(){}
//抽象方法
public abstract void method2();
}
具体产品类 具体的产品类可以有多个,都继承于抽象产品。
/**
* 具体产品类1
*/
public class ConcreteProduct1 extends Product {
@Override
public void method2() {
//业务逻辑处理
}
}
/**
* 具体产品类2
*/
public class ConcreteProduct2 extends Product {
@Override
public void method2() {
//业务逻辑处理
}
}
抽象工厂类负责定义产品对象的产生。
/**
* 抽象工厂类
*/
public abstract class Creator {
// 创建一个产品对象,其输入参数类型可以自行设置
public abstract <T extends Product> T createProduct(Class<T> tClass) ;
}
具体如何产生一个产品的对象,是由具体产的工厂类实现的。
/**
* 具体工厂类
*/
public class ConcreteCreator extends Creator {
@Override
public <T extends Product> T createProduct(Class<T> tClass) {
Product product = null;
try {
product = (Product) Class.forName(tClass.getName()).newInstance();
} catch (Exception e) {
//异常处理
}
return (T)product;
}
}
具体场景类
/**
* 具体场景类
*/
public class FactoryClient {
public static void main(String[] args){
Creator creator = new ConcreteCreator();
creator.createProduct(ConcreteProduct1.class);
}
}
上诉代码是工厂方法模式通用代码,是一个比较实用、易扩展的框架。
工厂方法模式的优点
- 良好的封装性,代码结构清晰。一个对象创建是由条件约束的,如一个调用者需要一个具体产品对象,只要知道这个产品的类名(或者约束字符串)就可以了,不用知道创建对象的过程,降低了模块间的耦合。
- 具有很好的扩展性,在增加产品类的情况下,只要适当的修改具体的工厂类或扩展一个工厂类,就可以完成拥抱变化,
- 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,它只需要关系产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。
- 工厂方法是典型的解耦框架,高层模块只需要知道产品的抽象类,其他的实现都不需要关心,符合迪米特法则,我不需要就不要去交流,也符合依赖倒置原则,只依赖产品的抽象类,也符合里氏替换原则,使用子类替换父类。
工厂方法模式具体实现
女娲造人,类图如下
类图比较简单,AbstractHumanFactory是一个抽象类,定义了八卦炉具有的整体功能,HumanFactory为实现类,完成具体的任务---创造人类,Human接口人类的总称,其三个实现类分别为三类人,NvWa类是一个场景类,负责模拟这个场景,执行相关任务。
具体实现代码如下: 人类接口
/**
* 人类接口
*/
public interface Human {
/**
* 不同的肤色
*/
public void getColor();
/**
* 不同的语言
*/
public void getLanguage();
}
接口Human是对人类的总称,每个人种都至少具有两个方法
黄种人
/**
* 黄种人
*/
public class YellowHuman implements Human {
@Override
public void getColor() {
System.out.println("黄种人皮肤是黄色的");
}
@Override
public void getLanguage() {
System.out.println("黄种人说汉语");
}
}
白种人
/**
* 白种人
*/
public class WhiteHuman implements Human {
@Override
public void getColor() {
System.out.println("白种人皮肤是白色的");
}
@Override
public void getLanguage() {
System.out.println("白种人说英语");
}
}
黑种人
/**
* 黑种人
*/
public class BlackHuman implements Human {
@Override
public void getColor() {
System.out.println("黑种人皮肤是黑色的");
}
@Override
public void getLanguage() {
System.out.println("黑种人说南非语");
}
}
定义八卦炉,抽象人类创建工厂
/**
* 抽象人类创建工厂
*/
public abstract class AbstractHumanFactory {
public abstract <T extends Human> T createHuman(Class<T> tClass) ;
}
上述代码采用了泛型,通过定义泛型对createHuman的输入参数,产生了俩层限制
- 必须是Class类型
- 必须是Human的实现类
定义具体人类创建工厂
/**
* 人类创建工厂类
*/
@SuppressWarnings("unchecked")
public class HumanFactory extends AbstractHumanFactory {
@Override
public <T extends Human> T createHuman(Class<T> tClass) {
//定义一个生产的人中
T human = null;
try {
human = (T) Class.forName(tClass.getName()).newInstance();
} catch (Exception e) {
System.out.println("人类创建失败");
}
return human;
}
}
场景类
/**
* 场景类
*/
public class NvWa {
public static void main(String[] args){
AbstractHumanFactory factory = new HumanFactory();
//女娲第一次造人,火候不足,于是白种人诞生了
System.out.println("--造出的第一批人是白种人--");
Human whiteHuman = factory.createHuman(WhiteHuman.class) ;
whiteHuman.getColor();
whiteHuman.getLanguage();
//女娲第二次造人,火候过足,于是黑种人诞生了
System.out.println("--造出的第二批人是黑种人--");
Human blackHuman = factory.createHuman(BlackHuman.class) ;
blackHuman.getColor();
blackHuman.getLanguage();
//女娲第三次造人,火候刚刚好,于是黄种人诞生了
System.out.println("--造出的第三批人是黄种人--");
Human yellowHuman = factory.createHuman(YellowHuman.class) ;
yellowHuman.getColor();
yellowHuman.getLanguage();
}
}
结果:
--造出的第一批人是白种人--
白种人皮肤是白色的
白种人说英语
--造出的第二批人是黑种人--
黑种人皮肤是黑色的
黑种人说南非语
--造出的第三批人是黄种人--
黄种人皮肤是黄色的
黄种人说汉语
人类的产生过程展现出来了,黑种人,白种人,黄种人都通过八卦炉创建出来,以上运用的就是工厂方法模式。
工厂方法模式的扩展
简单工厂模式
一个模块仅需要一个工厂类,没有必要把它生产处理啊,使用就静态方法就可以了。
根据这一要求,我们修改上面的例子。
修改之后的工厂类
/**
* 人类创建工厂类
*/
@SuppressWarnings("unchecked")
public class HumanFactory {
public static <T extends Human> T createHuman(Class<T> tClass) {
//定义一个生产的人中
T human = null;
try {
human = (T) Class.forName(tClass.getName()).newInstance();
} catch (Exception e) {
System.out.println("人类创建失败");
}
return human;
}
}
修改之后的场景类
/**
* 场景类
*/
public class NvWa {
public static void main(String[] args){
//女娲第一次造人,火候不足,于是白种人诞生了
System.out.println("--造出的第一批人是白种人--");
Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class) ;
whiteHuman.getColor();
whiteHuman.getLanguage();
//女娲第二次造人,火候过足,于是黑种人诞生了
System.out.println("--造出的第二批人是黑种人--");
Human blackHuman = HumanFactory.createHuman(BlackHuman.class) ;
blackHuman.getColor();
blackHuman.getLanguage();
//女娲第三次造人,火候刚刚好,于是黄种人诞生了
System.out.println("--造出的第三批人是黄种人--");
Human yellowHuman = HumanFactory.createHuman(YellowHuman.class) ;
yellowHuman.getColor();
yellowHuman.getLanguage();
}
}
运行结果没有发生变化,但是我们的类图变得简单了,而且调用者也比较简单,该模式是工厂方法模式的弱化,因为简单,所以称为简单工厂模式,也叫静态工厂模式,在实际项目中,采用该方法的案例还是比较多的,其缺点是工厂类的扩展比较困难,不符合开闭原则。
升级为多个工厂类
当我们在做一个比较复杂的项目时,经常会遇到初始化一个对象很耗费精力的情况,所有的产品类都放到一个工厂方法中进行初始化就变得结构不清晰,例如:一个产品类有五个具体实现,每个实现类的初始化的方法都不同,如果写在一个工厂方法中,势必会导致该方法巨大无比。考虑到需要结构清晰,我们就为每个产品定义一个创造者,然后由调用者自己去选择与哪个工厂方法关联。修改上面的例子如下:
每个人种都对应了一个创建者,每个创建者都独立负责对应的产品对象,非常符合单一职责原则。 代码示例:
抽象工厂类
/**
* 抽象人工创建工厂
*/
public abstract class AbstractHumanFactory {
public abstract Human createHuman() ;
}
白种人创建工厂实现类
/**
* 白种人创建工厂实现类
*/
public class WhiteHumanFactory extends AbstractHumanFactory {
@Override
public Human createHuman() {
return new WhiteHuman();
}
}
黑种人创建工厂实现类
/**
* 黑种人创建工厂实现类
*/
public class BlackHumanFactory extends AbstractHumanFactory {
@Override
public Human createHuman() {
return new BlackHuman();
}
}
黄种人创建工厂实现类
/**
* 黄种人创建工厂实现类
*/
public class YellowHumanFactory extends AbstractHumanFactory {
@Override
public Human createHuman() {
return new YellowHuman();
}
}
场景类
/**
* 场景类
*/
public class NvWa {
public static void main(String[] args){
//女娲第一次造人,火候不足,于是白种人诞生了
System.out.println("--造出的第一批人是白种人--");
Human whiteHuman = new WhiteHumanFactory().createHuman() ;
whiteHuman.getColor();
whiteHuman.getLanguage();
//女娲第二次造人,火候过足,于是黑种人诞生了
System.out.println("--造出的第二批人是黑种人--");
Human blackHuman = new BlackHumanFactory().createHuman() ;
blackHuman.getColor();
blackHuman.getLanguage();
//女娲第三次造人,火候刚刚好,于是黄种人诞生了
System.out.println("--造出的第三批人是黄种人--");
Human yellowHuman = new YellowHumanFactory().createHuman() ;
yellowHuman.getColor();
yellowHuman.getLanguage();
}
}
运行结果还是相同,每一个产品类都对应一个创建类,好处就是创建类的职责清晰,而且结构简单,但是给可扩展性和可维护性带来了一定的影响,如果要扩展一个产品类,就需要建立一个相应的工厂类,这样就增加了扩展的难度,因为工厂类和产品类的数量相同,维护时就需要考虑两个对象之间的关系。当然,在复杂的应用中一般采取多工厂的方法,然后在增加一个协调类,避免调用者与各个子工厂直接交流,协调类的作用是封装工厂子类,对高层模块提供统一的访问接口。
代替单例模式
单例模式的核心要求就是内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象
类图如下:
类图比较简单,Singleton 定义了一个private午餐构造函数,目的是不允许通过new的方式创建一个对象。 代码示例:
单例类
/**
* 单例类
*/
public class Singleton {
private Singleton(){}
public void doSomething(){
//业务处理
}
}
单例工厂类
/**
* 单例工厂类
*/
public class SingletonFactory {
private static Singleton mSingleton;
static {
try {
Class cl = Class.forName(Singleton.class.getName());
// 获取无参构造函数
Constructor constructor = cl.getDeclaredConstructor();
//设置无参构造是可以访问的
constructor.setAccessible(true);
//产生一个实例
mSingleton = (Singleton) constructor.newInstance();
} catch (Exception e) {
//异常处理
}
}
public static Singleton getSingleton() {
return mSingleton;
}
}
Singleton的对象是通过反射方式创建的,通过获取类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。以上通过工厂方法模式创建一个单例对象,该框架可以继续扩展,所有需要产生单例的类都遵循一定的规则,然后通过该框架,只要输入一个类型就可以获得唯一的一个实例。
延迟初始化
一个对象被消费完毕后,并不立即释放,工厂类保持其初始化状态,等待被再次使用,延迟初始化是工厂方法模式一个扩展应用。类图如下:
ProductFactory负责产品类对象的创建工作,并且通过mMap变量产生一个缓存,对需要再次被重用的对象保留。代码示例
延迟加载的工厂类
/**
* 延迟加载工厂类
*/
public class ProductFactory {
private static final Map<String, Product> mMap = new HashMap<>();
private static synchronized Product createProduct(String type) {
Product product = null;
//如果map中已经有这个对象了,直接赋值
if (mMap.containsKey(type)) {
product = mMap.get(type);
} else {
if (type.equals("product1")) {
product = new ConcreteProduct1();
} else {
product = new ConcreteProduct2();
}
//创建对象之后把对象存到mMap中
mMap.put(type, product);
}
return product;
}
}
代码比较简单,通过定义一个Map容器,容纳所有产生的对象,如果Map容器已经有的对象,可以直接取出返回,如果没有,则根据需要的类型的产生一个对象并放入Map容器中,以方便下次调用。延迟加载框架是可以扩展的,例如限制某一类产品类的最大实例化数量,可以通过判断Map中已经有的对象数量来实现,这样处理是非常有意义的。
尾声
工厂方法模式在项目中使用的非常频繁,以至于很多代码中都包含工厂方法模式,该模式几乎人尽皆知,但不是每个人都能用的好,熟能生巧,熟练掌握该模式,多思考工厂模式如果应用,而且工厂方法模式还可以与其他模式混合使用,变化出无穷的优秀设计,这也是软件设计和开发的乐趣所在。
特别感谢
《设计模式之禅》
PS:觉得文章不还错的话可以添加公众号关注下,文章都会同步到公众号。