简单工厂模式

对于设计模式,可能是很多程序员一直认为是评判水平的一个因素,而且在面试时也是必问的一门技术,所以,人人都或多或少的都学过它,既使没学全过GOF23种设计模式,都有自己比较熟悉的几个,但是,我想大部分人(当然包含我自己)学习它的主要目的只是去认识里面的每个模式大概是怎么一个形态,而学完之后可能也就是面试时被问到相关技术时说下它们的一些概念,或是能照着设计模式书里面提供的例子能很清晰的看懂里面代码,貌似已经完成融入到了自己脑子里面,但是总觉得跟实际开发没有太大的关系,难道这些前辈大牛们的精华就不能用于实际开发吗?我想肯定不是,于是,接下来自己要一步一步脚印先把基础打牢,把概念理解清楚,把23种模式从头仔细学一遍,最后再研究下实际运用这些模式,以写博文的形式来记录下自己的学习过程,相信只要用心学肯定会领悟这些模式的用意的,好了,开篇先发表下学习前的感想,参考<研磨设计模式>一书来进行学习,会把里面的信息进行提练,但加上自己的休会记录下来,纯学习笔记呵!

好了,言归正传,第一个出场的模式是简单工厂,这个模式比较容易理解,为了记录学习的完整性,还是将其记录下来,加深印象。

定义:

  提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类、也可以是具体类

结构及说明:

     

  Api:定义客户所需要的功能接口

     Impl:具体实现Api的实现类,可能会有多个

     Factory:工厂,选择合适的实现类来创建Api接口对象

     Client:客户端,通过Factory去获取Api接口对象,然后面向Api接口编程

有了上面简单的结构及角色介绍之后,先不用深入去理解这种模式的特点,用实际的代码去体会一下它主要是干嘛的,然后从代码中再去深入挖掘该模式。

为了一步步深入去理解使用该模式的好处,这里先从不用该模式的情况下来来慢慢演变过来,具体结构如下图:

 

接下来直接先上代码,然后再对其进行说明:

总共三个类:

Api.java

/**
 * 某个接口(通用的、抽象的、非具体的功能的) 
 */
public interface Api {
    /**
     * 某个具体的功能方法的定义,用test1来演示一下。
     * 这里的功能很简单,把传入的s打印输出即可 
     * @param s 任意想要打印输出的字符串
     */
    public void test1(String s);
}

Impl.java

/**
 * 对接口的实现 
 */
public class Impl implements Api{
    
    public void test1(String s) {
        System.out.println("Now In Impl. The input s=="+s);
    }
}

Client.java

 1 /**
 2  * 客户端:测试使用Api接口
 3  */
 4 public class Client {
 5     public static void main(String[] args) {
 6         //这里是直接在客户端去new一个具体实现类,然后再去直接调用里面的方法
 7         Api api = new Impl();
 8         api.test1("哈哈,不要紧张,只是个测试而已!");
 9     }
10 }

说明:该结构的缺点是向客户端暴露了细节实现,客户端要调用其方法,需详细了解其实现,对于实现重度依赖

接下来加入单简工厂模式的解决方案,来解决上面说的问题,其结构如下:

所有类结构都不用变,只是加入了一个工厂类:

Factory.java

/**
 * 工厂类,用来创造Api对象
 */
public class Factory {

    // 标准的工厂做法应该将其构造私有化,以防止外部的对它进行new操作
    private Factory() {

    }

    /**
     * 具体的创造Api对象的方法
     * 
     * @return 创造好的Api对象
     */
    public static Api createApi() {
        // 主要用来实现 选择合适的实现类 来创建实例对象

        // 由于只有一个实现,就不用条件判断了
        return new Impl();
    }
}

此时Client.java的调用方式就得发生变化了:

/**
 * 客户端:测试使用Api接口
 */
public class Client {
    public static void main(String[] args) {
        //重要改变,没有new Impl()了,取而代之Factory.createApi()
        Api api = Factory.createApi();
        
        
        api.test1("哈哈,不要紧张,只是个测试而已!");
    }
}

说明: 以上涉入简单工厂模式之后,就有效的解决了向客户端暴露细节的问题了,但是,目前只有一个实现类,如何去让客户端去根据情况去选择不同的实现类呢?

接下来来实现多个实现类的切换,其类结构如下:

Impl2.java

/**
 * 对接口的一种实现 
 */
public class Impl2 implements Api{
    
    public void test1(String s) {
        System.out.println("Now In Impl2. The input s=="+s);
    }
}

Factory.java

/**
 * 工厂类,用来创造Api的
 */
public class Factory {
    /**
     * 具体的创造Api的方法,根据客户端的参数来创建接口
     * 
     * @param type
     *            客户端传入的选择创造接口的条件
     * @return 创造好的Api对象
     */
    public static Api createApi(int type) {

        // 根据type来进行选择,当然这里的1和2应该做成常量,这里只是为了说明问题
        // 选择===〉如何选?====〉选择的参数===〉参数从何而来?
        // 1:参数来源于 client

        Api api = null;
        if (type == 1) {
            api = new Impl();
        } else if (type == 2) {
            api = new Impl2();
        }
        return api;
    }
}

Client.java

/**
 * 客户端:测试使用Api接口
 */
public class Client {
    public static void main(String[] args) {
        // 重要改变,没有new Impl()了,取而代之Factory.createApi()
        // 注意这里传递的参数,修改参数就可以修改行为,试试看吧

        //调用第一个实现类
        Api api2 = Factory.createApi(1);
        api2.test1("哈哈,不要紧张,只是个测试而已!");

        //调用第二个实现类
        Api api = Factory.createApi(2);
        api.test1("哈哈,不要紧张,只是个测试而已!");
    }
}

说明:对于具体实现类的选择,是由客户端来决定的,也就是最终客户端还是得知道传递参数的含义来决定选择哪个实现类,那有没有完全不让客户端知道细节,而同样能达到实现类切换的效果呢?答案肯定是有。
我们可以配置文件的方式,来决定到底用哪个实现类,具体实现细节如下:

FactoryTest.properties:

Factory.java:

/**
 * 工厂类,用来创造Api对象
 */
public class Factory {
    /**
     * 具体的创造Api的方法,根据配置文件的参数来创建接口
     * 
     * @return 创造好的Api对象
     */
    public static Api createApi() {
        // 直接读取配置文件来获取需要创建实例的类

        // 至于如何读取Properties还有如何反射这里就不解释了
        Properties p = new Properties();
        InputStream in = null;
        try {
            // 注意:文件名必须以"/"开头
            in = Factory.class.getResourceAsStream("/FactoryTest.properties");
            p.load(in);
        } catch (IOException e) {
            System.out.println("装载工厂配置文件出错了,具体的堆栈信息如下:");
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 用反射去创建,那些例外处理等完善的工作这里就不做了
        Api api = null;
        try {
            api = (Api) Class.forName(p.getProperty("ImplClass")).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return api;
    }
}

Client.java:

/**
 * 客户端:测试使用Api接口
 */
public class Client {
    public static void main(String[] args) {
        //这里就只是简单调用,也不用传具体参数来决定用哪个实现类了,这样客户端就完全不用知道细节
        Api api = Factory.createApi();

        api.test1("哈哈,不要紧张,只是个测试而已!");
    }
}

运行结果:

用配置的好外:添加新的实现类之后,无须修改代码,就能把这个新的类加入到应用中。

对于实现类的选择,还有另外一种方式,客户端的调用方式不变,但工厂的实现方法会有所改变:

Factory.java:

/**
 * 工厂类,用来创造Api的
 */
public class Factory {
    // 该值是运行期来进行改变,工厂内部用它来决定实现类
    private static int count = 0;

    /**
     * 具体的创造Api的方法,根据客户端的参数来创建接口
     * 
     * @return 创造好的Api对象
     */
    public static Api createApi() {

        // 选择===〉如何选?====〉选择的参数===〉参数从何而来?
        // 3:参数来源于系统自身,比如运行期间的某个值

        Api api = null;
        if (count < 3) {
            api = new Impl();
            count++;
        } else {
            api = new Impl2();
            count++;
        }
        return api;
    }
}

Client.java:

/**
 * 客户端:测试使用Api接口
 */
public class Client {
    public static void main(String[] args) {

        // 这里用一个循环来测试用条件来改变运行期间的某个值来决定到底选用哪个实现类
        for (int i = 0; i < 5; i++) {
            Api api = Factory.createApi();
            api.test1("哈哈,不要紧张,只是个测试而已!");
        }

    }
}

运行结果:

以上对于简单工厂的各种形式的演变都已经说明完了,下面进行一些总结(来自于教程中,这些总结需细细体会哦,根据上面的实现结果,学设计模式,体会是很重要的!):

  • 简单工厂的调用顺序示意图:

  • 简单工厂命名的建议:

  1、类名建议为"模块名称+Factory",比如:用户模块的工厂就称为:UserFactory

  2、方法名称通常为"get+接口名称"或者是"create+接口名称"

      3、不建议把方法名称命名为"new + 接口名称"

  •  简单工厂的优缺点:

      1、帮助封装

  2、解耦

  3、可能增加客户端的复杂度

  4、不方便扩展子工厂

  • 简单工厂的本质:

  它的本质是:选择实现(好好体会下)

  • 何时选用简单工厂:

  1、如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无需关心具体实现

  2、如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个简单工厂来,从而实现集中管理和控制

posted on 2013-10-20 11:37  cexo  阅读(238)  评论(0)    收藏  举报

导航