一、引言
在面向对象的编程中,创建对象是最基本也是最常用的一种操作,合理的对象创建方法对提高代码的复用性和降低模块之间的耦合来说极其重要,而工厂模式就是专门为合理创建对象而提出的。在GoF总结的23种常用的设计模式中,工厂模式就有3种,分别为简单工厂、工厂方法和抽象工厂,本文将结结合简单的例子对面向对象的编程、简单工厂模式和工厂方法模式进行较为详细介绍,并给出完整的代码示例,至于抽象工厂方法,我将在后续的博客中进行详细地介绍。
借助于面向对象编程的封装特性,我们将一个完整事物的不同功能模块封装成了一个个的类,类就像具有特定功能的产品,类的实例化也就是对象的创建就相当于对象的创建就像是产品的生产过程。在面向对象的编程中,一切事物和操作都是对象,这其中当然也就包括了对象的创建过程,因此我们可以将对象的创建过程封装为一个专门的类,由于产品都是从工厂中生产出来的,我们就将这个类称为工厂类,而用于创建对象的设计模式也就被称为工厂模式。
二、简单工厂模式
2.1 引入
考虑这样一个简单计算器应用,要求程序要根据输入的运算符和操作数进行相依的运算并输出结果,我们首先看一面向过程的编程思想是如何实现的。
import java.util.Scanner; public class ProgressCalculator { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 用于获取输入字符 System.out.print("请输入操作数: a = "); int a = Integer.parseInt(scanner.nextLine()); // 输入操作数a System.out.print("请输入操作数: b = "); int b = Integer.parseInt(scanner.nextLine()); // 输入操作数b System.out.print("请输入计算符号('+', '-', '*', '/'): "); String oprator = scanner.nextLine(); // 输入运算符 // 判断属于运算符并计算结果 System.out.print("计算结果:"); switch (oprator) { case "+": System.out.println("a + b = " + (a + b)); break; case "-": System.out.println("a - b = " + (a - b)); break; case "*": System.out.println("a * b = " + (a * b)); break; case "/": if (b == 0) { System.out.println("除数不能为0!"); break; }// if System.out.println("a / b = " + (a / b)); break; default: System.out.println("运算符输入错误!"); break; }// switch_oprator scanner.close(); }// main }/*ProgressCalculator*/
2.2 分析与实现
上述代码虽然实现了客户要求的功能,但是如果客户日后想要添加两数相除求余数的功能,我们就需要对客户端代码进行大量修改,这样,我们虽然仅增加了一个功能,却让早期已实现的加减乘除运算又重新被编译了一次,对本例来说这算不了什么,但是在一些大型系统中,重新编译所有代码将是一份十分巨大的工作。另外,除了计算器外,手机、电脑或者平板都要用到四则运算的功能,如果要想复用上述代码就只能通过复制粘贴的方法实现,在大型系统中同样不可取。这时,我们就需要用面向对象的方法对需求进行抽象和封装,从而解决上述问题。
首先,由于所有的运算都需要操作数和返回运算结果,因此,我们可以从加减乘除运算中抽象出一个基本运算类。
/** * 将加减乘除运算抽象成一个运算类,具有两个操作数和一个返回运算结果的方法,所有的运算子类均 * 继承自该类,从而可以利用多态性实现同一个父类引用变量实现不同子类操作的目的。 */ abstract class Calculate { public int num_a; public int num_b; abstract public int getResult(); }/*calculate*/
从能能上讲,四种运算应该是彼此独立的,根据单一职能原则,我们将这四种运算分别封装为四个不能的类,它们都继承自基本运算类并重写getResult()方法以实现自身的特有功能。
// 加法运算子类 class CalculateAdd extends Calculate { @Override public int getResult() { return (num_a + num_b); }// getResult }/*CalculateAdd*/ // 减法运算子类 class CalculateMinus extends Calculate { @Override public int getResult() { return (num_a - num_b); }// getResult }/*CalculateMinus*/ // 乘法运算子类 class CalculateMutiple extends Calculate { @Override public int getResult(){ return num_a * num_b; }// getResult }/*CalculateMutiple*/ // 除法运算子类 class CalculateDivision extends Calculate { @Override public int getResult() { if (num_b == 0) try { throw new Exception("除数不能为0!"); } catch (Exception e) { e.printStackTrace(); }// try return num_a / num_b; }// getResult }/*CalculateDivision*/
为了管理各个子类对象的创建过程,我们定义了一个工厂类,其中有一个根据输入参数产生不同对象的的方法,并将创建的对象返回给一个父类(基本运算类)类型的引用。
/** * 工厂类,所有子类对象均由该类定义并返回,像一个专门生产对象的工厂,因而被称为工厂类,如果 * 要对系统进行模块的增删,仅需要修改该类即可,其他模块不受影响。 */ class CalculateFactory { public static Calculate createCalculate(String opt) throws Exception { switch(opt) { case "+": return new CalculateAdd(); case "-": return new CalculateMinus(); case "*": return new CalculateMutiple(); case "/": return new CalculateDivision(); default: throw new Exception("运算符输入有误!"); }// switch }// createCalculate }/*ObjectFactory*/
在客户端中,用户不需要了解各种运算的具体实现过程,子需要通过一个工厂类创建各个具体运算类的实例,并利用多态特性通过一个基本运算类的引用变量调用各具体运算类的功能,从而将用户与功能的具体实现隔离开来,降低了耦合性。
/** * 面向对象思想实现的计算器代码 */ public class SimpleFactoryDemo { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); Calculate calculator = null; System.out.print("请输入计算符号('+', '-', '*', '/'): "); calculator = CalculateFactory.createCalculate(sc.nextLine()); // 根据输入的计算类型生产相应的子类 System.out.print("请输入操作数: a = "); calculator.num_a = Integer.parseInt(sc.nextLine()); System.out.print("请输入操作数: b = "); calculator.num_b = Integer.parseInt(sc.nextLine()); System.out.println("结果为:" + calculator.getResult()); sc.close(); }// main }// SimpleFactoryDemo
2.3 小节
至此,我们就完成了对简单工厂模式的学习,在简单工厂模式中,系统中子模块对象都通过工厂类产生,工厂类中有一个能够产生各种子模块对象的的静态方法,该静态方法有一个输入参数,用于判断到底要产生那个子类的对象实例,该静态方法返回类型为所有子类对的父类引用。用户只需要知道需要使用那些功能,以及要产生这些功能模块对象的条件,在使用时,通过向工厂类中传递生产条件的方式获得子类对象,通过终极父类的多态特性对各个子类的重写方法进行调用。下图为简单工厂模式的UML图。
图-1 简单工厂模式UML图
三、工厂方法模式
3.1 引入
简单工厂模式在工厂类中需要通过判断给定条件的方式创建对象,用户只需要了解一个功能的终极父类和一个工厂类即可获得各种功能,比较方便,但是使用简单工厂模式后,如果需要增加新的功能,那么不仅要添加新的功能子类,还需要更改工厂类中的switch分支语句,违背了开放-封闭原则(该原则简单来说就是可以增加新类,但是不能修改已有的类),因此便提出了工厂方法模式。
3.2 分析与实现
在简单工厂模式中,一个工厂负责生产所有对象,但是在工厂方法模式中,每一类对象都由自己专门的工厂进行生产(创建),因此系统有几个类,就有几个工厂,为了提高代码的可移植性和复用性,我们从这些工厂类抽象除了一个工厂接口,定义了所有工厂类必须具备的基本功能。
同样以上面的计算器的例子来说明工厂方法模式,由于有加减乘除四个具体运算类,根据工厂方法模式的定义,我们就要为每一个运算类定义一个工厂类,用以专门创建相应的运算类实例,为了提高代码的复用性和可拓展性,我们又对这些工厂类进行了一次抽象,形成了一个工厂接口,定义了具体工厂类应具有的功能(方法)。
// 工厂接口及各个运算子类的实体工厂类 interface InstanceFactory { public Calculate creatInstance(); }/*InstanceFactory*/ // 加法工厂 class AddFactory implements InstanceFactory { @Override public Calculate creatInstance() { return new CalculateAdd(); }// creatInstance }/*AddFactory*/ // 减法工厂 class MinusFactory implements InstanceFactory { @Override public Calculate creatInstance() { return new CalculateMinus(); }// creatInstance }/*MinusFactory*/ // 乘法工厂 class MultipleFactory implements InstanceFactory { @Override public Calculate creatInstance() { return new CalculateMutiple(); }// creatInstance }/*MultipleFactory*/ // 除法工厂 class DivisionFactory implements InstanceFactory { @Override public Calculate creatInstance() { return new CalculateDivision(); }// creatInstance }/*DivisionFactory*/
在客户端代码中,我们要先创建所需类的相应工厂实例,利用工厂实例创建所需类的实例。
import java.util.Scanner; public class FactoryMethodDemo { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 用于获取输入字符 Calculate calculator = null; OperationFactory factory = null; System.out.print("请输入计算符号('+', '-', '*', '/'): "); String oprator = scanner.nextLine(); // 输入运算符 // 判断属于运算符并创建相应类的工厂 switch (oprator) { case "+": factory = new AddFactory(); break; case "-": factory = new MinusFactory(); break; case "*": factory = new MultipleFactory(); break; case "/": if (b == 0) { System.out.println("除数不能为0!"); break; }// if factory = new DivisionFactory(); break; default: System.out.println("运算符输入错误!"); break; }// switch_oprator calculator = factory.creatInstance(); System.out.print("请输入操作数: a = "); calculator.num_a = Integer.parseInt(scanner.nextLine()); // 输入操作数a System.out.print("请输入操作数: b = "); calculator.num_b = Integer.parseInt(scanner.nextLine()); // 输入操作数b System.out.println("结果为" + calculator.getResult()); scanner.close(); }// main }/*FactoryMethodDemo*/
2.3 小节
工厂方法模式与简单工厂模式不同,工厂方法模式将工厂类进行了细化,为每一个 子功能类都创建了一个工厂类,然后再从各个子功能工厂类中抽象出一个工厂接口用于客户端格局需要创建子功能类对象。该方法进一步封装了子功能类对象的创建过程,同时避免了在添加或删除子功能类时需要修该简单工厂类的缺陷,维护了开放-封闭原则,下图为工厂方法模式的UML图。
图-2 工厂方法模式UML图
总结
在简单工厂模式中仅有一个工厂,负责生产所有类的对象实例,用户只需要了解一个工厂类和一个所有功能的父类(在本文例子中就是基本运算类)就可以创建并调用所有功能子类的特有方法,但是如果要增加新的功能,就必须要修改工厂类以创建新的功能子类实例,违背了开放-封闭原则;工厂方法模式通过为每一个功能子类定义一个专用工厂的方法结局了这个问题,但是如果在工厂模式中要增加新的功能,除了增加新功能的类之外还需要增加新的工厂类,增加了复杂度,同时,工厂方法模式又将分支语句放回了客户端代码中,还需要修改客户端中的分支结构以判断创建哪一个类的工厂。在后续的博文中我们将会学习到这一缺点可以通过Java的反射机制予以解决,消除代码总的判断分支结构,现阶段读者可以暂时不必关心。
本文所用的所有完整代码随后会上传至github中,链接稍后上传。