VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 设计模式之简单工厂模式(Simple Factory Pattern)

一、简单工厂模式的由来

 

  所有设计模式都是为解决某类问题而产生的,那么简单工厂模式是为解决什么问题呢?我们假设有以下业务场景:

 

  在一个学生选课系统中,文科生用户选课时,我们要获得文科生的所有课程列表;理科生用户选课时,获得理科生的所有课程列表;体育生用户选课时,获得体育生的所有课程列表;那我们的逻辑怎么写呢?很简单,我们用伪代码实现如下:

 

复制代码
if(userType is 文科生){

    return 文科生课程;

}else if(userType is 理科生){

    return 理科生课程;

}else if(userType is 体育生){

   return 体育生课程;

}
复制代码

 

  这时我们发现,不光在学生选课时需要这段逻辑,在学生查询课程时,也需要这段代码,于是我们理所应当的将这段代码复制到了查询课程的方法中,如果老师查询课程的方法中也要这段代码,那继续复制到老师查看课程的方法中。。。。。。

 

  这就造成了重复性代码,如何让这段代码只写一遍,多处可以复用呢?我们可以将这段代码抽取成公共方法,将学生类型定义成参数。在调用的地方,通过传入不同的参数类型,返回不同的课程列表。这样就实现了一次代码,多次复用的效果。

 

  以上的这种优化过程,其实就是简单工厂模式的生成过程。

 

二、什么是简单工厂模式?

   在简单工厂模式中,程序员创建或者获得对象,不再用new Object()的方式进行,而是给工厂类传入参数,工厂类根据程序员传入参数的不同,返回不同的对象。

 

三、简单工厂模式的角色

  简单工厂模式中,有以下几种角色,这些角色撑起了简单工程模式的实现:

  •   产品规范:提供规范,供其它产品子类来实现这些规范;可以是接口,抽象类或者父类;
  •   产品实现:产品规范的具体实现类;实现或者继承产品规范的接口,抽象类或父类;
  •   调度类(工厂类):有了产品规范,也有了产品实现,那用哪个产品呢,由调度类来调度决定。
  •   产品使用者:使用产品的地方,传入想使用的产品参数,通过调度类给产品使用者返回产品。

 

 

 

  其中,产品规范可以是接口,抽象类或者父类,总之,它的作用就是定义产品的规范,想生产这个产品,必须遵循这套规范,以此达到统一的标准。产品实现类是子类,来实现接口的方法,定义具体的产品。产品实现类可以有很多个,只要实现了产品规范,都是产品的实现类。工厂类,对具体的产品实现类进行调度,通过逻辑判断,决定输出哪个产品。这里的产品使用者,就是我们敲代码的程序员,我们想要使用某一个对象或其他产品,只需往工厂类里传入相应参数,就能返回了想要的产品。

  由此可见,设计模式是针对程序员服务的,程序员只需输入参数,就能获得产品。设计模式并不是针对软件用户的东西,所以,我们在学习设计模式时,一定要把这个观念树立起来:在设计模式中,用户就是我们敲业务代码的程序员,而我们在写设计模式时,我们服务的对象,也就成了业务代码程序员,而不是普通的软件用户了。

四、简单工厂模式的实现

  下面,我们自己实现一套简单工厂模式的代码。由上面可知,实现该模式,其实就是实现产品规范、产品实现类、调度工厂类、使用者类几个角色即可。

  首先,定义产品规范,产品规范可以是接口,抽象类或父类,这里,我们用接口定义。

复制代码
/**
 * 产品规范接口:定义生产家具规范
 */
public interface Furniture {
    /**
     * 成产家具的方法

     */
    public void createFurniture();
}
复制代码

然后,我们定义两个产品的实现类,实现createFurniture()规范

复制代码
public class Table implements Furniture{
    @Override
    public void createFurniture() {
        System.out.println("生产桌子");
    }
}

public class Chair implements Furniture{
    @Override
    public void createFurniture() {
        System.out.println("生产椅子");
    }
}
复制代码

  然后,我们定义调度工厂类,工厂类需要进行调度,判断返回哪种家具。

  注意:工厂类的调度方法,通常用static静态方法,方便使用者调用

复制代码
/**
 * 工厂类,用于调度家具,返回程序员想要的家具

 */
public class FurnitureFactory {

    /**
     * 调度方法,根据传入的type参数,判断返回哪种家具

     * @param type
     * @return
     */
    public static Furniture getFurniture(String type){
        Furniture f=null;
        if("桌子".equals(type)){
            f=new Table();
        }else if("椅子".equals(type)){
            f=new Chair();
        }
        return f;
    }
}
复制代码

  最后,定义调用类,来调用工厂,返回想要的家具

复制代码
/**
 * 产品使用者

 */
public class FurnitureUser {
    public static void main(String[] args) {
        //甲程序员需要桌子,传入桌子参数,获得桌子
        Furniture f1=FurnitureFactory.getFurniture("桌子");
        f1.createFurniture();
        //乙程序员需要椅子,传入椅子参数,获得椅子
        Furniture f2=FurnitureFactory.getFurniture("椅子");
        f2.createFurniture();

    }

}
复制代码

  这样,一个简单工厂模式,就实现了。由此可见,作为简单工厂模式的使用者,即业务程序员而言,我们只关注给工厂类传参,获取对象即可。而作为简单工厂模式的设计者,我们需要定义产品规范,定义调度工厂类的调度逻辑。至于产品实现类,可以由多个第三方来实现。

五、缺点

缺点一:

  我们继续上面的例子,进行进一步开发。加入现在我们对家具进行扩展,要生产沙发,我们需要怎么做呢?

  首先,产品规范无需改动。

  然后,产品的实现类,我们要进行扩展,新建沙发类,来实现沙发产品

public class Sofa implements Furniture{
    @Override
    public void createFurniture() {
        System.out.println("生产沙发");
    }
}

  接下来,工厂类里,因为我们新加了产品,所以,在调度方法中,需要把新的产品逻辑写进去

复制代码
/**
 * 工厂类,用于调度家具,返回程序员想要的家具

 */
public class FurnitureFactory {

    /**
     * 调度方法,根据传入的type参数,判断返回哪种家具

     * @param type
     * @return
     */
    public static Furniture getFurniture(String type){
        Furniture f=null;
        if("桌子".equals(type)){
            f=new Table();
        }else if("椅子".equals(type)){
            f=new Chair();
        }else if("沙发".equals(type)){
            f=new Sofa();
        }
        return f;
    }
}
复制代码

  这样,我们在使用者中,就可以生产沙发了

复制代码
/**
 * 产品使用者

 */
public class FurnitureUser {
    public static void main(String[] args) {
        //甲程序员需要桌子,传入桌子参数,获得桌子
        Furniture f1=FurnitureFactory.getFurniture("桌子");
        f1.createFurniture();
        //乙程序员需要椅子,传入椅子参数,获得椅子
        Furniture f2=FurnitureFactory.getFurniture("椅子");
        f2.createFurniture();
        //丙程序员需要沙发
        Furniture f3=FurnitureFactory.getFurniture("沙发");
        f3.createFurniture();

    }

}
复制代码

  由此可见,我们新加一个产品,需要新加一个产品类,这个无可厚非。但是我们还需要在工厂类中,修改调度逻辑,把新加的产品逻辑写进去。这个操作,就违反了开闭原则。

  开闭原则:对外支持扩展,对内不允许修改。

  新产品类的增加,体现了对外扩展的支持,但是修改调度类,又违背了不对内修改的原则。这就是简单工厂模式的缺点之一。

缺点二:

  在上面的角色图中我们可以看出,工厂类连接了产品和使用者,是简单工厂模式的核心。加入工厂类出现了问题,那么所有角色都处于了瘫痪状态。

缺点三:

  我们上述所讲的简单工厂模式,是标准模式,在实际应用中,很多时候,我们会对简单工厂模式进行变形,例如,产品规范我们用抽象类来定义,在产品规范中,我们有定义产品调度的方法,这时,这个抽象类,就具有了产品规范和工厂调度两个角色。还拿上面的代码举例子,我们可以这样写:

复制代码
/**
 * 产品规范+产品调度:既定义产品的规范,又有产品调度的方法,合二为一
 */
public abstract class Furniture {

    /**
     * 定义规范
     */
    public abstract void createFurniture();

    /**
     * 调度方法
     */
    public static Furniture getFurniture(String type){
        Furniture f=null;
        if("桌子".equals(type)){
            f=new Table();
        }else if("椅子".equals(type)){
            f=new Chair();
        }else if("沙发".equals(type)){
            f=new Sofa();
        }
        return f;
    }
}

复制代码

  这样,Furniture就兼具了两种角色。那么它的实现类,就变成了继承Furniture类即可。这样改造,也是一种简单工厂模式的实现。

  在这种实现中,就违反了单一责任原则。但是在某种情况下,我们可以违背其中的原则,不要求生搬硬套。这里只是举例说明,在很多框架实现中,都用了这种模式的实现方式。

六、改进

  针对违背开闭原则的缺点,我们对代码进行改进。之所以违背了开闭原则,是因为工厂类里的调度方法,在新加产品时需要修改。那么,有什么办法可以不对其进行修改呢?思路就是要动态加载其产品类,这样,无论多少个产品,动态加载进来,就无需改动代码了。

  如何进行动态加载呢?我们可以采用xml配置的方式,将产品实现类加入xml里,在工厂类里,通过反射,创建xml里配置的产品类。我们还可以使用注解,在产品实现类上定义注解,然后工厂类扫描注解,通过反射动态加载产品类。这样,我们就只需要修改xml或者在产品类中加注解就行了,无需修改工厂类的方法,这就满足了开闭原则。这种思路是不是很熟悉呢?我们在spring框架的使用中,不是就可以在xml中配置类,也可以通过注解声明类吗?没错,spring就是通过这种方式满足开闭原则的。

  配置xml文件和自定义注解的方式实现简单工厂模式的代码,大家自行写一下  (●'◡'●)

七、优点

  1.面向接口编程,体现了面向对象的思想;

  2.用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。

八、简单工厂模式在JDK中的应用

  下面,我们来演示一下,在JDK中,用到的简单工厂模式。

  首先,我们讲解Calendar类中用到的简单工厂模式:

  上面说到,简单工厂模式由产品规范、产品实现、工厂调度、产品使用几个角色组成,那我们就一一找到这几个角色。

  创建Calendar日历对象,我们是通过Calendar.getInstance()方法获得,在这里,我们就是产品使用者调用getInstance()方法,获得产品。相应的,那Calendar就是工厂类了,因为使用者直接面对的是工厂。getInstance()方法就是工厂类的调度方法了,我们看其源码:

 

  可以看到,getInstance方法是个静态方法,我记得上面说到的调度类通常是静态方法吗


相关教程