工厂方法模式

作者: yzbkaka | 来源:发表于2018-11-25 11:06 被阅读23次

    定义

    工厂方法模式的定义为:

    Define an interface for creating an object,but let subclasses decide which class to
    instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)

    工厂方法模式的通用类图如下所示:


    工厂方法模式

    从图中可以看出,在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator来完成的。

    下面是一个比较通用的工厂方法模式的源码:

    抽象产品类:

    public abstract class Product {
        public void method1() {
            //产品类的公共方法
        }
        public abstract void method2();  //抽象的方法
    }
    

    具体产品类:

    public class ConcreteProduct1 extends Product {  //产品1
        public void method2() {
            //具体代码
        }
    }
    
    public class ConcreteProduct2 extends Product {  //产品2
        public void method2 {
            //具体代码
        }
    }
    ......
    

    具体的产品类可以是有多个,都是继承于抽象产品类的,并且需要自己来实现抽象产品类中的抽象方法。

    抽象工厂类:

    public abstract class Creator {
    /*创建一个产品对象,其输入参数类型可以自己设置
    通常为String、Enum、Class等,也可以为空*/
        public abstract <T extends Product> T createProduct(Class<T> c);
    }
    

    抽象工厂类负责定义产品对象的产生,在这里我们采用了泛型,通过定义泛型对输入的参数产生两层限制:1)输入参数必须是Class;2)输入参数必须是Product的实现类。

    具体工厂类:

    public class ConcreteCreator extends Creator {
        public <T extends Product> T createProduct(Class<T> c) {
            Product product = null;
            try {
                product = (Product)Class.forName(c.getName()).newInstance();
            }catch(Exception e) {
                //异常处理
            }
            return (T)product;
        }
    }
    

    场景类的调用方法:

    public class Client {
        public static void main(String[] args){
            Creator creator = new ConcreteCreator();
            Product product = new creator.createProduct(ConcreteProduct1.class);
            //更多处理
        }
    }
    

    下面来看一个具体的例子,这个例子代表是的女娲造人的过程,在这个过程当中有三个对象:女娲、八卦炉和三种肤色的人。在这里,我们可以将女娲用场景类来表示,八卦炉类似于一个工厂来负责制造人类,而三种不同肤色的人都是同一个接口下不同的实现类。因此我们可以得到下面的类图:


    例子类图

    在图中AbstractHumanFactory是一个抽象类,定义了一个八卦炉具有的整体功能,HumanFactory为它的实现类来完成创造人类的工作;Human是人类的接口;NvWa则是一个场景类来负责执行相关的任务。具体代码如下:

    //Human接口
    public interface Human {  //人类的总称
        public void getColor();  //肤色
        public void talk();  //语言
    }
    
    //黑色人种
    public class BlackHuman implements Human {  
        public void getColor() {
            System.out.println("黑人肤色是黑色");
        }
        
        public void talk() {
            System.out.println("黑人说话听不懂");
        }
    }
    
    //白色人种
    public class WhiteHuman implements Human {  
        public void getColor() {
            System.out.println("白人的肤色是白色");
        }
        
        public void talk() {
            System.out.println("白人说话是单音节");
        }
    }
    
    //黄色人种
    public class YellowHuman implements Human {  
        public void getColor() {
            System.out.println("黄人的肤色是黄色的");
            
        }
        
        public void talk() {
            System.out.println("黄人说话是双音节");
        }
    }
    
    //抽象的八卦炉
    public abstract class AbstractHumanFactory {  
        public abstract <T extends Human> T createHuman(Class<T> c);  
    }
    
    //八卦炉的具体实现类
    public class HumanFactory extends AbstractHumanFactory {
        public <T extends Human> T createHuman(Class<T> c) {
            Human human = null;
            try {
                human = (T)Class.forName(c.getName()).newInstance();  //产生一个人种
            }catch(Exception e) {
                e.printStackTrace();
            }
            return (T)human;
        }
    }
    
    //场景类NvWa
    public class NvWa {
        public static void main(String[] args) {
            AbstractHumanFactory YinYangLu = new HumanFactory();  //阴阳炉
            System.out.println("第一批是白色人种");
            Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class);
            whiteHuman.getColor();
            whiteHuman.talk();
            System.out.println("第二批是黑色人种");
            Human blackHuman = YinYangLu.createHuman(BlackHuman.class);
            blackHuman.getColor();
            blackHuman.talk();
            System.out.println("第三批是黄色人种");
            Human yellowHuman = YinYangLu.createHuman(YellowHuman.class);
            yellowHuman.getColor();
            yellowHuman.talk();
        }
    }
    

    运行结果:

    第一批人是白色人种
    白人肤色是白色的
    白人说话是单音节
    第二批人是黑色人种
    黑人肤色是黑色的
    黑人说话听不懂
    第三批人是黄色人种
    黄人肤色是黄色的
    黄人说话是双音节

    应用

    工厂方法模式的优点主要有以下几点:
    (1)具有良好的封装性,代码的逻辑清晰。在创建对象时不用了解创建对象的过程,只要知道这个产品的类名就可以了。
    (2)工厂方法模式的拓展性很优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成相应的业务逻辑。
    (3)能够屏蔽产品类。即产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化,因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。

    综合以上优点,我们可以总结出工厂方法模式的几个使用场景:
    (1)因为工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
    (2)当需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类,之后再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。
    (3)工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
    (4)可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。

    扩展

    工厂方法模式主要有以下4种扩展,下面来分别介绍。

    1.缩小为简单工厂模式

    当一个模块仅仅只需要一个工厂类的时候,就没有必要把它单独生产出来,使用静态的方法就可以了,例如女娲的那个例子中AbstarctHumanFactory,我们可以将它来修改一下,将AbstarctHumanFactory直接去掉,同时把createHuman方法设置为静态类型,代码如下:

    public class HumanFactory {
        public static  <T extends Human> T createHuman(Class<T> c) {
            Human human = null;
            try {
                human = (T)Class.forName(c.getName()).newInstance();  //产生一个人种
            }catch(Exception e) {
                e.printStackTrace();
            }
            return (T)human;
        }
    }
    

    然后我们再将场景类NvWa中的代码改动一下:

    public class NvWa {
        public static void main(String[] args) {
            System.out.println("第一批是白色人种");
            Human whiteHuman = createHuman(WhiteHuman.class);
            whiteHuman.getColor();
            whiteHuman.talk();
            System.out.println("第二批是黑色人种");
            Human blackHuman = createHuman(BlackHuman.class);
            blackHuman.getColor();
            blackHuman.talk();
            System.out.println("第三批是黄色人种");
            Human yellowHuman = createHuman(YellowHuman.class);
            yellowHuman.getColor();
            yellowHuman.talk();
        }
    }
    

    这样再次运行我们可以得到同样的结果,但是代码可以变得更加简单。这种模式是工厂方法模式的弱化,因为简单,所以称它为简单工厂模式。

    2.升级为多个工厂类

    当我们在做一个比较复杂的项目时,可能会遇到很难创建一个对象的情况,所有的产品类都放到一个工厂方法当中进行初始化会使得代码的逻辑不够清晰,这时我们就可以为每一个产品定义一个工厂。仍然以女娲为例,我们让每一个人种都有生产自己的八卦炉。代码如下:

    //抽象的工厂类
    public abstract class AbstractHumanFactory {  
        public abstract Human createHuman(); 
    }
    
    //生产黑人的炉子
    public class BlackHumanFactory extends AbstractHumanFactory {
        public Human createHuman() {
                return new BlackHuman();
          }
    }
    
    //生产黄人的炉子
    public class YellowHumanFactory extends AbstractHumanFactory {
        public Human createHuman() {
                return new YellowHuman();
          }
    }
    
    //生产白人的炉子
    public class WhiteHumanFactory extends AbstractHumanFactory {
        public Human createHuman() {
                return new WhiteHuman();
          }
    }
    

    然后在场景类中对代码进行改动即可:

    public class NvWa {
        public static void main(String[] args) {
            System.out.println("第一批是白色人种");
            Human whiteHuman = (new WhiteHumanFactory()).createHuman();
            whiteHuman.getColor();
            whiteHuman.talk();
            System.out.println("第二批是黑色人种");
            Human blackHuman = (new BlackHumanFactory()).createHuman();
            blackHuman.getColor();
            blackHuman.talk();
            System.out.println("第三批是黄色人种");
            Human yellowHuman = (new YellowHumanFactory()).createHuman();
            yellowHuman.getColor();
            yellowHuman.talk();
        }
    }
    

    运行一下,结果还是一样的。像这样对每一个产品类都创建了各自相应的工厂,好处就是各个工厂的职责清晰而且结构简单,但是却给可拓展性和可维护性带来了一定的影响,因为一旦要新建一个产品类,就要建立一个相应的工厂类,增加了复杂度。

    3.替代单例模式

    单例模式的核心要求是在内存中只能够有一个对象,我们通过工厂方法模式也可以实现只在内存中生产一个对象。


    类图

    Singleton定义了一个private的无参构造函数,目的是不允许通过new的的方式创建一个对象,它的代码如下:

    public class Singleton{
        private Singleton(){}
        public void dosomething(){
            //代码
        }
    }
    

    Singleton保证不能通过正常的渠道建立一个对象,而SingletonFactory则是通过反射的方式去创建,代码如下:

    public class SingletonFactory {
        private static Singleton singleton;
        static{
            try {
                Class cl= Class.forName(Singleton.class.getName());  //获得无参构造
                Constructor constructor=cl.getDeclaredConstructor();  //设置无参构造是可访问的
                constructor.setAccessible(true);  //产生一个实例对象
                singleton = (Singleton)constructor.newInstance();
                } catch (Exception e) {
                //异常处理
            }
        }
        public static Singleton getSingleton(){
            return singleton;
         }
    }
    

    这个方法是通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。

    4.延迟初始化

    当一个对象被消费完后,并不立刻释放,工厂类保持其初始状态等待着再次被使用,延迟初始化是工厂方法模式的一个扩展应用,它的类图如下:


    类图

    ProductFactory负责产品类对象的创建工作,并且通过prMap变量产生一个缓存,对需要再次被重用的对象保留,Product和ConcreteProduc则t是一个示例代码,ProductFactory代码如下:

    public class ProductFactory{
        private static final Map<String,Product> prMap = new HashMap();
        public static synchronized Product createProduct(String type) throws Exception{
            Product product = null;
            if(prMap.containsKey(type)){  //如果Map中有了这个对象
                product = prMap.get(type);
            }
            else{
                if(type.equals("Product1")) {
                    product = new ConcreteProduct1();
                }
                else{
                    product = new ConcreteProduct2();
                }
                prMap.put(type,product);  //把对象放到缓存器里面
            }
            return product;
        }
    }
    

    这里我们定义了一个Map容器来容纳产生所有产生的对象,如果Map容器中已经有了对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放入到Map容器中,以方便下一次的调用。

    相关文章

      网友评论

        本文标题:工厂方法模式

        本文链接:https://www.haomeiwen.com/subject/uzbhqqtx.html