天天看点

抽象工厂模式

抽象工厂模式是工厂方法模式的进一步推广。它的定义是:

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。

抽象工厂的使用场景如下:

客户端不关心产品类实例如何被创建、组合和表达的细节。这一条对于所有的工厂模式都是重要的;

这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品,这是抽象工厂的原始定义;

系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

在本文举例中,会出现蒙牛牛奶、蒙牛酸奶,伊利牛奶、伊利酸奶等产品,而蒙牛的牛奶和酸奶就是一个产品族。蒙牛专卖店只消费蒙牛产品族而不会消费伊利的产品族。

抽象工厂的一般组成:

抽象工厂、具体工厂、产品族。

本文会演示抽象工厂的实例以及与其它工厂模式的对比,以及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           

这样,让程序更优雅、高效。