工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
问题
假设你开设了一个汽车工厂。创业初期工厂只能生产宝马这一款车,因此大部分代码都位于名为宝马的类中。
工厂效益非常好,为了获得更多利润,你决定扩大生产,引入一款名为奥迪的车。
可是代码问题改如何处理呢?目前,大部分代码斗鱼宝马类相关。在程序中添加奥迪类需要修改全部代码。更糟糕的是,如果你以后需要在程序中支持另外一种车型,很可能需要再次对这些代码进行大幅修改。
解决方案
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用(即使用new运算符)。不用担心,对象仍将通过new运算符创建,只是该运算符改在工厂方法中调用罢了。工厂方法返回的对象通常被称作“产品”。
假设生产汽车只有创建汽车和销售汽车两种功能。
这种更改可能毫无意义:我们只是改变了程序中调用构造函数的位置而已。但是,现在你可以在子类中重写工厂方法,从而改变其创建产品的类型。
这里的工厂方法是create()方法。
注意,仅当这些产品具有共同的基类或者接口时子类才能返回不同类型的产品,同时基类中的工厂方法还应将其返回类型声明为这一共有接口。
举例来说,宝马BMW和奥迪Audi类都必须实现汽车Car接口,该接口声明了一个名为行驶run的方法。每个类都将以不同的方式实现该方法:宝马以后轮驱动的驱动形式行驶,奥迪以四轮驱动的形式驱动行驶。宝马部门BMWDepartment类的工厂方法返回宝马对象,奥迪部门AudiDepartment类则返回奥迪对象。
只要产品类实现共同的接口,你就可以将其对象传递给客户代码,而无需提供额外数据。
调用工厂方法的代码(通常被称为客户端代码)无需了解不同子类返回实际对象之间的差别。客户端将所有产品视为抽象汽车Car。客户端知道所有汽车对象都提供行驶run方法,但是并不关心其具体实现方式。
结构
-
产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。
-
具体产品(Concrete Products)是产品接口的不同实现。
-
创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。
你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。
注意,尽管它的名字是创建者,但它最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。
-
具体创建者(Concrete Creators) 将会重写基础工厂方法,使其返回不同类型的产品。
注意,并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、对象池或其他来源的已有对象。
代码
interface Car {void run();
}class BMW implements Car {@Overridepublic void run() {System.out.println("后轮驱动行驶!");}
}class Audi implements Car {@Overridepublic void run() {System.out.println("四轮驱动行驶!");}
}class CarDepartment {public Car create() {return null;}public void sell() {Car car = create();// 试驾汽车System.out.println("试驾汽车:");car.run();// ……其他操作……}
}class BMWDepartment extends CarDepartment {public Car create() {return new BMW();}
}class AudiDepartment extends CarDepartment {public Car create() {return new Audi();}
}public class FactoryMethod {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);String carType = scanner.nextLine();CarDepartment carFactory = null;if ("BMW".equals(carType)) {carFactory = new BMWDepartment();} else if ("Audi".equals(carType)) {carFactory = new AudiDepartment();} else {throw new RuntimeException("错误!未知的车型。");}carFactory.sell();}
}