设计模式 | 工厂方法模式

知识点二: 工厂方法模式


一、概述

工厂方法模式(Factory Method)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂模式(Polymorphic Factory),工厂方法模式是简单工厂模式的一个延伸,它属于23种GOF设计模式的创建型设计模式。在工厂方法模式中,父类负责定义创建对象的公共接口,而子类则负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成,即由子类来决定究竟应该实例化(创建)哪一个类。 工厂方式法模式(Factory Method),定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。

它解决的仍然是软件设计中与创建对象有关的问题,可以更好的处理客户的需求变化。 此模式的核心精神是封装类中不变的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。 通俗地说就是:

所谓工厂方法模式,其实也像我们现实生活中的工厂,也是用来生产东西的,只不过我们代码中的工厂是用来生产对象的 。

核心工厂类不再负责产品的创建,这样核心类成为了一个抽象工厂的角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是是的工厂方法模式可以是系统在不修改具体工厂角色的情况下引进新的产品。


二、优缺点及使用场景

优点

  • 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

缺点

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

使用场景

  • 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
  • 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

三、模式中包含的角色和其职责以及模式实现方式

1.角色

抽象工厂(Creator)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。

具体工厂(Concrete Creator)角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。在上图中有两个这样的角色:BulbCreator与TubeCreator。

抽象产品(Product)角色:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。在上图中,这个角色是Light。

具体产品(Concrete Product)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

2.实现方式

工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了,就像把一个大蛋糕切成了多个小蛋糕。 如图:

工厂方法模式.png


四、在Java中的实现

1、创建抽象工厂类

AnimalFactory.java

/**
 * @Description: 抽象工厂
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 20:12
 */
public interface AnimalFactory {
    public Animal getAnimal();
}

2、创建具体工厂类

DogFactory.java

/**
 * @Description: 产生狗类 具体工厂
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 20:33
 */
public class DogFactory implements AnimalFactory {
    public Animal getAnimal() {
        return new Dog();
    }
}

CatFactory.java

/**
 * @Description: 产生猫类 具体工厂
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 20:34
 */
public class CatFactory implements AnimalFactory {
    public Animal getAnimal() {
        return new Cat();
    }
}

3、创建抽象产品类

Animal.java

/**
 * @Description: 抽象产品
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/2 16:23
 */
public interface Animal {
    public void get();
}

4、创建具体产品类

Dog.java

/**
 * @Description: 具体实体类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/2 16:23
 */
public class Dog implements Animal {
    public void get() {
        System.out.println("获得狗");
    }
}

Cat.java

/**
 * @Description: 具体实体类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/2 16:24
 */
public class Cat implements Animal {
    public void get() {
        System.out.println("获得猫");
    }
}

5、创建测试类

MainClass.java

/**
 * @Description:
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/2 16:24
 */
public class MainClass {
    @Test
    public void testFactoryMethod(){
        //制造狗类
        AnimalFactory dogFactory = new DogFactory();
        Animal dog = dogFactory.getAnimal();
        dog.get();

        //制造猫类
        AnimalFactory catFactory = new CatFactory();
        Animal cat = catFactory.getAnimal();
        cat.get();

    }
}

结果

结果1.png


五、工厂方法模式和简单工厂模式的对比

简单工厂模式最大的优点在于工厂类中,包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。就像之前使用简单工厂模式设计的计算器代码,客户端不用管该用哪个类的实例,只需要把相应的运算符号给工厂,工厂自动就给出了相应的实例,客户端只需要去做运算就可以了,不同的实例会实现不同的运算。当问题也就在这里,如果要加一个“求 M 数的 N 次方” 的功能,我们是一定需要给简单工厂类的方法里加分支条件的,这就等于说,我们不仅对扩展开发了,也对修改开放了,这样就违背了开-闭原则。而且如果简单工厂类里与创建对象相关的代码太多,也会导致耦合性高。

工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端。而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。所以它们都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可以实现,降低了客户程序与产品对象的耦合。工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。

工厂方法模式退化后可以演变为简单工厂模式。


六、工厂方法模式在生产中的应用

使用工厂方法模式写一个计算器(面试题:写一个简单的工厂方法模式)

普通写法

import java.util.Scanner;

/**
 * @Description: 主方法类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 21:07
 */
public class MainClass {
    public static void main(String[] args) {
        //1.接收控制台输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("*********计算机程序*********");
        System.out.println("请输入第一个数:");
        String num1 = scanner.nextLine();

        System.out.println("请输入操作符:");
        String oper = scanner.nextLine();

        System.out.println("请输入第二个数:");
        String num2 = scanner.nextLine();

        double result = 0;

        if("+".equals(oper)){
            result = Double.parseDouble(num1)+Double.parseDouble(num2);
        }else if("-".equals(oper)){
            result = Double.parseDouble(num1)-Double.parseDouble(num2);
        }else if("*".equals(oper)){
            result = Double.parseDouble(num1)*Double.parseDouble(num2);
        }else if("/".equals(oper)){
            result = Double.parseDouble(num1)/Double.parseDouble(num2);
        }else{
            System.out.println("没有该运算方法");
        }

        System.out.println(num1 + oper + num2 + "=" + result);
    }
}

缺点:完全是面向过程设计,缺少代码重用

使用工厂模式

(注,因为本人较懒所以只写了一个加法运算,还望见谅=.=)

  • 抽象工厂类

    OperationFactory.java

/**
 * @Description: 抽象工厂类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 21:48
 */
public interface OperationFactory {
    public Operation getOperation();
}
  • 具体工厂类

    AddOperationFactory.java

/**
 * @Description: 具体工厂类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 22:00
 */
public class AddOperationFactory implements OperationFactory {
    public Operation getOperation() {
        return new AddOperation();
    }
}
  • 抽象产品类

    Operation.java

/**
 * @Description: 抽象产品类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 21:25
 */
public abstract class Operation {
    private double num1;
    private double num2;

    public double getNum1() {
        return num1;
    }

    public void setNum1(double num1) {
        this.num1 = num1;
    }

    public double getNum2() {
        return num2;
    }

    public void setNum2(double num2) {
        this.num2 = num2;
    }

    //抽象运算方法
    public abstract double getResult();
}
  • 具体产品类

    AddOperation.java

/**
 * @Description: 具体产品类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 21:43
 */
public class AddOperation extends Operation {
    public double getResult() {
        double result = this.getNum1() + this.getNum2();
        return result;
    }
}
  • 测试类

    MainClass.java

import java.util.Scanner;

/**
 * @Description: 主方法类
 * @Author: Ling.D.S
 * @Date: Created in 2018/11/4 21:07
 */
public class MainClass {
    public static void main(String[] args) {
        //1.接收控制台输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("*********计算机程序*********");
        System.out.println("请输入第一个数:");
        double num1 = Double.parseDouble(scanner.nextLine());

        System.out.println("请输入操作符:");
        String oper = scanner.nextLine();

        System.out.println("请输入第二个数:");
        double num2 = Double.parseDouble(scanner.nextLine());

        double result = 0;

        //2.进行运算
        if("+".equals(oper)){
            OperationFactory factory = new AddOperationFactory();
            Operation operation = factory.getOperation();
            operation.setNum1(num1);
            operation.setNum2(num2);
            result = operation.getResult();
        }
        System.out.println(num1 + oper + num2 + "=" + result);
    }
}

结果:

结果2.png


转载请注明原地址,宋德凌的博客:http://CoderOfSong.github.io 谢谢!

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦