前言
在刚开始学习设计模式时,工厂设计模式中的简单工厂
、工厂方法
、抽象工厂
可能总是傻傻分不清,今天这篇文章就来帮助你了解一下这三种模式(严格来说,简单工厂并不算一种设计模式)到底是啥?
文章目录
创造型设计模式
我们知道,设计模式主要分为三大类:创造型设计模式
、结构型设计模式
、行为型设计模式
。而今天要讲解的工厂设计模式(简单工厂、工厂方法、抽象工厂)就是属于创造型设计模式。所以,在讲解工厂设计模式前,我们先来认识一下它所属的创造型设计模式。
创造型设计模式主要关注的是类的实例化,也就是说体现的是对象的创建方法,利用这些模式,我们可以在适当的情况下以适当的形式创建对象,创造型设计模式通过控制对象的创建来解决设计中的问题。
创造型设计模式主要包含以下子类别:
-
对象创造型设计模式:主要完成对象创建,并将对象中部分内容放到其他对象中创建。
-
类创造型设计模式:主要完成类的实例化,并将类中的部分对象放到子类中创建,此类模式在实例化过程中高效地利用了继承机制。
创造型设计模式主要包含以下 5 种 具体的设计模式:
-
抽象工厂设计模式
提供一个用于创建相关对象或相互依赖对象的接口,无需指定对象的具体类。
-
生成器设计模式
将复杂对象的构建与其表示相互分离,使得同样的构建过程可以创建不同的表示。
-
工厂方法设计模式
允许在子类中实现本类的实例化类。
-
原型设计模式
使用一个原型实例来指定创建对象的种类,然后通过拷贝这些原型实现新对象的创建。
-
单例模式
确保某个类在系统中仅有一个实例,并提供一个访问它的全局访问点。
对象创造型设计模式 | 类创造型设计模式 |
---|---|
抽象工厂设计模式 | 工厂方法设计模式 |
生成器设计模式 | |
原型设计模式 | |
单例设计模式 |
下面先来看看工厂模式的基础——简单工厂
1. 简单工厂
简单工厂实际不能算作一种设计模式,它引入了创建者的概念,将实例化的代码从应用代码中抽离,在创建者类的静态方法中只处理创建对象的细节,后续创建的实例如需改变,只需改造创建者类即可;但由于使用静态方法来获取对象,使其不能在运行期间通过不同方式去动态改变创建行为,因此存在一定局限性。
1.1 模式中包括的类
- 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
1.2 基本示例
这里以制造coffee的例子开始工厂(简单工厂、工厂方法、抽象工厂)模式设计。
我们知道coffee只是一种泛举,在点购咖啡时需要指定具体的咖啡种类:美式咖啡、卡布奇诺、拿铁等等。
/**
* 拿铁、美式咖啡、卡布奇诺等均为咖啡家族的一种产品
* 咖啡则作为一种抽象概念,用抽象类描述——产品类
*/
public abstract class Coffee {
//获取coffee名称
public abstract String getName();
}
/**
* 美式咖啡——具体产品类
*/
public class Americano extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
/**
* 卡布奇诺——具体产品类
*/
public class Cappuccino extends Coffee {
@Override
public String getName() {
return "卡布奇诺";
}
}
/**
* 拿铁——具体产品类
*/
public class Latte extends Coffee {
@Override
public String getName() {
return "拿铁";
}
}
下面代码具体展示了简单工厂下如何创建不同的咖啡实例:
/**
* 简单工厂--用于创建不同类型的咖啡实例
*/
public class SimpleFactory {
/**
* 通过类型获取Coffee实例对象
* @param type 咖啡类型的字符串
* @return 具体类型的咖啡实例
*/
public static Coffee createInstance(String type){
if("americano".equals(type)){
return new Americano();//美式
}else if("cappuccino".equals(type)){
return new Cappuccino();//卡布奇诺
}else if("latte".equals(type)){
return new Latte();//拿铁
}else{
throw new RuntimeException("type["+type+"]类型不可识别,没有匹配到可实例化的对象!");
}
}
//测试
public static void main(String[] args) {
Coffee latte = SimpleFactory.createInstance("latte");
System.out.println("创建的咖啡实例为:" + latte.getName());
Coffee cappuccino = SimpleFactory.createInstance("cappuccino");
System.out.println("创建的咖啡实例为:" + cappuccino.getName());
}
}
1.3 优缺点
在Java中 java.text.DateFormat
就是简单工厂模式的典型案例。
- 优点:专门定义一个工厂类负责创建其他类的实例,最大的优点在于工厂类中包含了必要的逻辑,根据客户需要的条件动态实例化相关的类。
- 缺点:当需要增加一种产品时,比如
摩卡(Mocha)
就需要修改简单工厂类 SimpleFactory(增加if-else
块),这违背了开闭原则。
TIPS:
其实如果采用反射机制实现简单工厂并没有违背开闭原则。
利用反射机制,将简单工厂类改成:
public class SimpleFactory {
public IProduct produce(Class<? extends Coffee> c) throws Exception {
return (Coffee)Class.forName(c.getName()).newInstance();
// return (Coffee)c.newInstance(); //或者采用这种方法
}
}
2. 工厂方法设计模式
工厂方法模式是简单工厂模式的进一步抽象化和推广,工厂方法模式里不再只由一个工厂类决定哪一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做。即:定义了一个创建对象的接口,但由其子类决定要实例化的产品类是哪一个,工厂方法让类把实例化推迟到了子类。
2.1 模式中包括的类
- 抽象产品类(Product):是具体产品继承的父类或者实现的接口。在 java 中一般由抽象类或者接口来实现。
- 具体产品类( Concrete Product):继承或实现了抽象产品类。具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现。
- 工厂类( Creator,因为由它来创建产品类,所以叫作工厂类):这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现。其中声明的工厂方法,返回一个产品类对象。
- 具体工厂类( Concrete Creator):它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
2.2 UML图
2.3 功能及应用场景
- 当需要创建一个类,而在编程时不能确定这个类的类型时(需要运行时确定)。
- 当一个类希望由其子类来指定所创建对象的具体类型时。
- 当我们想要定位被创建类,并获取相关信息时。
2.4 基本示例
继续根据上面简单工厂的例子进行场景延伸:不同地区咖啡工厂受制于环境、原料等因素的影响,制造出的咖啡种类有限。中国咖啡工厂仅能制造卡布奇诺、拿铁,而美国咖啡工厂仅能制造美式咖啡、拿铁。
/**
* 定义一个抽象的咖啡工厂——工厂类
*/
public abstract class CoffeeFactory {
/**
* 生产该工厂可制造的咖啡
* @return 该工厂可制造的具体类型咖啡实例 数组
*/
public abstract Coffee[] createCoffee();
}
//中国咖啡工厂——具体工厂类
public class ChinaCoffeeFactory extends CoffeeFactory {
@Override
public Coffee[] createCoffee() {
// TODO Auto-generated method stub
return new Coffee[]{new Cappuccino(), new Latte()};//卡布奇诺、拿铁
}
}
//美国咖啡工厂——具体工厂类
public class AmericaCoffeeFactory extends CoffeeFactory {
@Override
public Coffee[] createCoffee() {
// TODO Auto-generated method stub
return new Coffee[]{new Americano(), new Latte()};//美式咖啡、拿铁
}
}
/**
* 工厂方法测试
*/
public class FactoryMethodTest {
//打印工厂可生产的咖啡
static void print(Coffee[] c){
for (Coffee coffee : c) {
System.out.println(coffee.getName());
}
}
public static void main(String[] args) {
CoffeeFactory chinaCoffeeFactory = new ChinaCoffeeFactory();//创建具体工厂
Coffee[] chinaCoffees = chinaCoffeeFactory.createCoffee();
System.out.println("中国咖啡工厂可以生产的咖啡有:");
print(chinaCoffees);
CoffeeFactory americaCoffeeFactory = new AmericaCoffeeFactory();//创建具体工厂
Coffee[] americaCoffees = americaCoffeeFactory.createCoffee();
System.out.println("美国咖啡工厂可以生产的咖啡有:");
print(americaCoffees);
}
}
2.5 优缺点
工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式,把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来,从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。
工厂方法相比于简单工厂模式的优点是增加一个产品,只需要增加一个具体工厂类和具体产品类,没有修改原先的抽象工厂类,符合开闭原则。缺点是客户端的代码会需要修改(简单工厂模式的客户端不需要修改),随着产品的继续增加,所要实现的类的个数也会随之增多。
3. 抽象工厂设计模式
在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式。
抽象工厂模式相比于工厂方法模式的抽象层次更高。这意味着抽象工厂返回的是一组类的工厂。与工厂方法模式类似(返回多个子类中的一个),此方法会返回一个工厂,而这个工厂会返回多个子类中的一个。简单来说,抽象工厂是一个工厂对象,该对象又会返回若干工厂中的一个。(工厂的工厂)
工厂模式是创造型模式的典型示例。抽象工厂设计模式是工厂方法模式的扩展,从而使我们无须担心所创建对象的实际类就能够创建对象。抽象工厂模式扩展了工厂方法模式,允许创建更多类型的对象。
3.1 模式中包括的类
- 抽象工厂(AbstractFactory)声明一个用于完成抽象产品对象创建操作的接口。
- 具体工厂(ConcreteFactory)实现创建具体产品对象的操作。
- 抽象产品(AbstractProduct)声明一个用于一类产品对象的接口。
- 具体产品(ConcreteProduct)定义由相应的具体工厂来创建的产品对象。
- 客户端(Client)使用由抽象工厂和抽象产品类声明的唯一接口。
3.2 UML图
3.3 功能及应用场景
抽象工厂模式的主要优点之一是它屏蔽了这些具体类的创建方法。实际应用的类名称不需要再让客户端(将客户端与具体类解耦)知道。由于具体类是屏蔽的,因此我们可以在不同的工厂(实现方法)之间进行切换。
3.4 基本示例
在上述的场景上继续延伸:咖啡工厂做大做强,引入了新的饮品种类:茶、 碳酸饮料。中国工厂只能制造咖啡和茶,美国工厂只能制造咖啡和碳酸饮料。
如果用上述工厂方法方式,除去对应的产品实体类还需要新增2个抽象工厂(茶制造工厂、碳酸饮料制造工厂),4个具体工厂实现。随着产品的增多,会导致类数量激增。
所以这里引出一个概念——产品家族,在此例子中,不同的饮品就组成我们的饮品家族, 饮品家族开始承担创建者的责任,负责制造不同的产品。
/**
* 抽象的饮料产品家族制造工厂
*/
public interface AbstractDrinksFactory {
//制造咖啡
Coffee createCoffee();
//制造茶
Tea createTea();
//制造碳酸饮料
Sodas createSodas();
}
/**
* 中国饮品工厂
* 制造咖啡与茶,无法制造碳酸饮料
*/
public class ChinaDrinksFactory implements AbstractDrinksFactory {
@Override
public Coffee createCoffee() {
// TODO Auto-generated method stub
return new Latte(); //返回拿铁实例
}
@Override
public Tea createTea() {
// TODO Auto-generated method stub
return new MilkTea(); //返回奶茶实例
}
@Override
public Sodas createSodas() {
// TODO Auto-generated method stub
return null; //无法生产碳酸饮料
}
}
/**
* 美国饮品制造工厂
* 制造咖啡和碳酸饮料
*/
public class AmericaDrinksFactory implements AbstractDrinksFactory {
@Override
public Coffee createCoffee() {
// TODO Auto-generated method stub
return new Latte(); //返回拿铁实例
}
@Override
public Tea createTea() {
// TODO Auto-generated method stub
return null; //无法生产茶
}
@Override
public Sodas createSodas() {
// TODO Auto-generated method stub
return new CocaCola(); //返回可乐实例
}
}
/**
* 抽象工厂测试类
*/
public class AbstractFactoryTest {
//打印具体工厂能生产的产品
static void print(Drink drink){
if(drink == null){
System.out.println("产品:--" );
}else{
System.out.println("产品:" + drink.getName());
}
}
public static void main(String[] args) {
AbstractDrinksFactory chinaDrinksFactory = new ChinaDrinksFactory();//中国工厂
Coffee coffee = chinaDrinksFactory.createCoffee();
Tea tea = chinaDrinksFactory.createTea();
Sodas sodas = chinaDrinksFactory.createSodas();
System.out.println("中国饮品工厂有如下产品:");
print(coffee);
print(tea);
print(sodas);
AbstractDrinksFactory americaDrinksFactory = new AmericaDrinksFactory();//美国工厂
coffee = americaDrinksFactory.createCoffee();
tea = americaDrinksFactory.createTea();
sodas = americaDrinksFactory.createSodas();
System.out.println("美国饮品工厂有如下产品:");
print(coffee);
print(tea);
print(sodas);
}
}
3.5 优缺点
- 优点:抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。
- 缺点:产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。
3.6 工厂方法模式 VS 抽象工厂模式
- 工厂方法模式:一个抽象产品类,可以派生出多个具体产品类。每个具体工厂类只能创建一个具体产品类的实例。
- 抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。一个抽象工厂类可以派生出多个具体工厂类。每个具体工厂类可以创建多个具体产品的实例。
区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。