设计模式
创建型模式
创建型模式的主要关注点是 “怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
工厂模式
工厂模式用于对象的创建,使得客户从具体的产品对象中被解耦,是一种创建型模式。其主要分为简单工厂、工厂方法、抽象工厂模式三种。本文从一个具体的例子逐步深入分析,来体会三种工厂模式的应用场景和利弊。
这里以制造咖啡为例分别介绍三种工厂模式。
简单工厂
该模式对对象创建管理方式最为简单,因为其仅仅简单的对不同类对象的创建进行了一层薄薄的封装。该模式通过向工厂传递类型来指定要创建的对象。
UML 类图如下:
模式的结构:
- 简单工厂:是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- 抽象产品:是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
- 具体产品:是简单工厂模式的创建目标。
我们知道 coffee 只是一种泛举,在点购咖啡时需要指定具体的咖啡种类:美式咖啡、卡布奇诺、拿铁等等
interface Coffee {
/**
* 获取咖啡名称
* @return
*/
public String getName();
}
class Americano implements Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
class Cappuccino implements Coffee {
@Override
public String getName() {
return "卡布奇洛";
}
}
class Latte implements Coffee {
@Override
public String getName() {
return "拿铁";
}
}
创建咖啡的简单工厂
public class SimpleFactory {
public static void main(String[] args) {
CoffeeFactory coffeeFactory = new CoffeeFactory();
Coffee americano = coffeeFactory.createCoffee("americano");
Coffee cappuccino = coffeeFactory.createCoffee("cappuccino");
Coffee latte = coffeeFactory.createCoffee("latte");
System.out.println(americano.getName());
System.out.println(cappuccino.getName());
System.out.println(latte.getName());
System.out.println();
Coffee americano2 = coffeeFactory.createAmericano();
System.out.println(americano2.getName());
Coffee cappuccino2 = CoffeeFactory.createCappuccino();
System.out.println(cappuccino2.getName());
}
}
/**
* 简单工厂
*/
class CoffeeFactory {
public Coffee createAmericano() {
return new Americano();
}
public static Coffee createCappuccino() {
return new Cappuccino();
}
public Coffee createCoffee(String type) {
switch (type) {
case "americano":
return new Americano();
case "cappuccino":
return new Cappuccino();
case "latte":
return new Latte();
default:
return null;
}
}
}
总结:
实例化对象的时候不再使用 new Object() 形式,可以根据用户的选择条件来实例化相关的类。对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。
优点:我们可以对创建的对象进行一些 “加工” ,而使用者不需要关注,因为工厂隐藏了这些细节。如果没有工厂的话,那我们是不是就得自己写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护。
缺点:如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。
工厂方法模式
和简单工厂模式中工厂负责生产所有产品相比,工厂方法模式将生成具体产品的任务分发给具体的产品工厂。定义了一个创建对象的工厂接口,但由工厂子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到了子类。
其UML类图如下:
也就是定义一个抽象工厂,其定义了产品的生产接口,但不负责具体的产品,将生产任务交给不同的派生类工厂。这样不用通过指定类型来创建对象了。
模式的结构:
- 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
场景延伸:不同地区咖啡工厂受制于环境、原料等因素的影响,制造出的咖啡种类有限。中国咖啡工厂能制造卡布奇诺、拿铁,而美国咖啡工厂仅能制造美式咖啡、拿铁。【依然使用上面创建的产品类】
public class FactoryMethod {
public static void main(String[] args) {
CoffeeFactory chinaCoffeeFactory = new ChinaCoffeeFactory();
System.out.println("中国工厂生产的咖啡: ");
Coffee[] chinaCoffees = chinaCoffeeFactory.createCoffee();
printOut(chinaCoffees);
System.out.println();
CoffeeFactory americaCoffeeFactory = new AmericaCoffeeFactory();
System.out.println("美国工厂生产的咖啡: ");
Coffee[] americaCoffees = americaCoffeeFactory.createCoffee();
printOut(americaCoffees);
}
static void printOut(Coffee[] c){
for (Coffee coffee : c) {
System.out.println(coffee.getName());
}
}
}
/**
* 咖啡工厂接口
*/
interface CoffeeFactory {
/**
* 生产咖啡
* @return
*/
public Coffee[] createCoffee();
}
/**
* 中国咖啡工厂
*/
class ChinaCoffeeFactory implements CoffeeFactory {
@Override
public Coffee[] createCoffee() {
return new Coffee[]{new Cappuccino(), new Latte()};
}
}
/**
* 美国咖啡工厂
*/
class AmericaCoffeeFactory implements CoffeeFactory {
@Override
public Coffee[] createCoffee() {
return new Coffee[]{new Americano(), new Latte()};
}
}
总结:
工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
工厂模式中,要增加产品类时也要相应地增加工厂类,代码量增加了不少。但是实现了对简单工厂的解耦,实例对象可以通过相应的工厂创建,而不必再耦合到一个工厂类中,同时也符合开-闭原则。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。