定义
工厂模式是一种创建型设计模式,其核心思想是将对象的创建与使用分离,即客户端代码不再直接负责对象的创建,而是通过一个或多个工厂类来负责。这样,客户端代码只需关心如何使用对象,而无需关注对象的创建细节。工厂模式使得对象的创建过程更加灵活和可配置,从而提高了代码的可维护性和可扩展性。
工厂模式的类型
工厂模式主要有三种类型:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式
简单工厂模式,又称静态工厂方法模式,是最基础的工厂模式。尽管它并不属于23种经典的GOF设计模式,但它的实现方式却非常直观。简单工厂模式是通过一个工厂类来负责所有产品类型的创建。客户端只需传入一个参数(如字符串或整数等)给工厂类,然后工厂类会依据这个参数来返回对应的产品实例。
然而,简单工厂模式存在一个显著的问题:当需要新增一种产品类型时,必须对工厂类的代码进行修改。这明显违反了开闭原则,即软件实体应对扩展开放,对修改关闭。换言之,理想的设计应当允许我们添加新功能,而无需改动现有代码。
现在,我们通过一个关于咖啡制作的例子来说明简单工厂模式的工作原理:设想在制作咖啡的过程中,我们需要处理多种类型的咖啡,比如美式、拿铁和卡布奇诺,每种咖啡的制备方法各不相同。我们将使用一个简单的工厂类来根据客户的选择制造并供应相应的咖啡类型。
首先,我们定义一个咖啡的抽象接口:
public interface Coffee {
void prepare();
}
接着,我们创建几个实现了Coffee接口的具体咖啡类:
public class Americano implements Coffee {
@Override
public void prepare() {
System.out.println("Preparing Americano...");
// 具体的制作步骤...
}
}
public class Latte implements Coffee {
@Override
public void prepare() {
System.out.println("Preparing Latte...");
// 具体的制作步骤...
}
}
public class Cappuccino implements Coffee {
@Override
public void prepare() {
System.out.println("Preparing Cappuccino...");
// 具体的制作步骤...
}
}
然后,我们创建一个咖啡工厂类:
public class CoffeeFactory {
public static Coffee createCoffee(String type) {
if ("americano".equalsIgnoreCase(type)) {
return new Americano();
} else if ("latte".equalsIgnoreCase(type)) {
return new Latte();
} else if ("cappuccino".equalsIgnoreCase(type)) {
return new Cappuccino();
} else {
throw new IllegalArgumentException("Invalid coffee type: " + type);
}
}
}
最后,我们创建一个客户端类来演示如何使用这个简单工厂:
public class CoffeeShop {
public static void main(String[] args) {
// 制作美式咖啡
Coffee americano = CoffeeFactory.createCoffee("americano");
americano.prepare(); // 输出: Preparing Americano...
// 制作拿铁咖啡
Coffee latte = CoffeeFactory.createCoffee("latte");
latte.prepare(); // 输出: Preparing Latte...
}
}
在这个例子中,CoffeeFactory是简单工厂类,它根据用户输入的咖啡类型字符串来创建并返回相应的咖啡实例。Coffee是抽象产品接口,而Americano、Latte和Cappuccino是实现了这个接口的具体产品类。客户端代码通过调用CoffeeFactory的静态方法createCoffee()来创建并准备咖啡。
工厂方法模式
工厂方法模式对简单工厂模式进行了改进。它通过定义一个创建对象的接口,将实际的对象创建过程委托给了实现该接口的具体工厂类。这种设计使得每个具体工厂类只专注于实例化一种特定的产品类型,有效地分散了创建逻辑,并减少了代码之间的耦合。在工厂方法模式中,每个具体工厂类通常只负责创建一种类型的对象,这不仅有助于保持代码的清晰和可管理性,而且符合单一职责原则,即一个类应该只有一个引起变化的原因。这种模式特别适用于那些具有多种产品类型且创建逻辑较为复杂的应用场景。
以下是基于咖啡制作例子的工厂方法模式实现:
首先,我们定义一个咖啡的抽象接口和创建几个实现了Coffee接口的具体咖啡类(代码同上)。然后,我们定义了一个抽象工厂接口,它包含一个用于创建咖啡对象的工厂方法:
public interface CoffeeFactory {
Coffee createCoffee();
}
接着,我们为每个具体咖啡类创建了一个具体的工厂类,这些工厂类实现了CoffeeFactory接口,并返回相应的具体咖啡对象:
public class AmericanoFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new Americano();
}
}
public class LatteFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new Latte();
}
}
public class CappuccinoFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new Cappuccino();
}
}
最后,在客户端代码中,我们使用具体的工厂类来创建并准备咖啡:
public class CoffeeShop {
public static void main(String[] args) {
CoffeeFactory americanoFactory = new AmericanoFactory();
Coffee americano = americanoFactory.createCoffee();
americano.prepare(); // 输出: Preparing Americano...
CoffeeFactory latteFactory = new LatteFactory();
Coffee latte = latteFactory.createCoffee();
latte.prepare(); // 输出: Preparing Latte...
}
}
在这个例子中,每个具体咖啡类都有一个与之关联的具体工厂类,这些工厂类负责创建和返回相应的具体咖啡对象。客户端代码与具体的工厂类交互,而不是与具体的产品类直接交互,从而实现了代码的松耦合和可扩展性。如果需要添加新的咖啡类型,只需要添加新的具体产品类和具体工厂类,而不需要修改现有的客户端代码。
抽象工厂模式
抽象工厂模式进一步扩展了工厂方法模式的概念。它提供了一种方式来封装一组具有共同主题的单个工厂的创建接口。在这种模式中,定义了一个抽象工厂接口,该接口规定了创建一系列相关或依赖产品的方法。客户端代码与抽象工厂接口交互,一次性创建一组相关的产品,而不需要指定具体的产品类。抽象工厂模式适用于那些需要生成多个产品族的应用,且这些产品族之间有一定的关联性。通过使用这种模式,可以更容易地扩展和维护系统。
在咖啡制作的例子中,我们可以将不同的咖啡和与咖啡相关的产品,如咖啡伴侣(通常指的是添加到咖啡中以改善其口感或风味的物品,如牛奶、奶精、糖等)、甜点等,视为一系列相关产品。我们可以创建一个抽象工厂接口,该接口定义了创建咖啡、咖啡伴侣和甜点的方法。然后,我们可以为不同的咖啡系列(如拿铁系列、卡布奇诺系列等)创建具体的工厂类,这些类实现了抽象工厂接口并返回相应的具体产品。
以下是基于咖啡制作例子的抽象工厂模式实现:
首先,我们定义几个抽象产品接口。
public interface Coffee {
void prepare();
}
// 添加咖啡伴侣
public interface CoffeeMate {
void add();
}
// 供应甜点
public interface Dessert {
void serve();
}
接着,我们创建实现了这些接口的具体产品类。
public class Americano implements Coffee {
@Override
public void prepare() {
System.out.println("Preparing Americano...");
}
}
public class Latte implements Coffee {
@Override
public void prepare() {
System.out.println("Preparing Latte...");
}
}
// 添加糖
public class Sugar implements CoffeeMate {
@Override
public void add() {
System.out.println("Adding Sugar Mate...");
}
}
// 添加牛奶
public class Milk implements CoffeeMate {
@Override
public void add() {
System.out.println("Adding Milk Mate...");
}
}
// 供应饼干甜点
public class Cookie implements Dessert {
@Override
public void serve() {
System.out.println("Serving Cookie Dessert...");
}
}
// 供应甜甜圈甜点
public class Donut implements Dessert {
@Override
public void serve() {
System.out.println("Serving Donut Dessert...");
}
}
然后,我们定义一个抽象工厂接口,它包含了创建所有产品的方法。
public interface CoffeeShop {
Coffee createCoffee();
CoffeeMate createCoffeeMate();
Dessert createDessert();
}
接着,我们为每个咖啡系列创建具体的工厂类,这些类实现了抽象工厂接口并返回相应的具体产品。
public class LatteCoffeeShop implements CoffeeShop {
@Override
public Coffee createCoffee() {
return new Latte();
}
@Override
public CoffeeMate createCoffeeMate() {
return new Sugar();
}
@Override
public Dessert createDessert() {
return new Cookie();
}
}
public class AmericanoCoffeeShop implements CoffeeShop {
@Override
public Coffee createCoffee() {
return new Americano();
}
@Override
public CoffeeMate createCoffeeMate() {
return new Milk();
}
@Override
public Dessert createDessert() {
return new Donut();
}
}
最后,在客户端代码中,我们使用具体的工厂类来创建并准备一系列的咖啡和相关产品:
public class CoffeeOrder {
public static void main(String[] args) {
CoffeeShop latteShop = new LatteCoffeeShop();
Coffee latte = latteShop.createCoffee();
CoffeeMate latteMate = latteShop.createCoffeeMate();
Dessert dessert = latteShop.createDessert();
latte.prepare();
latteMate.add();
dessert.serve();
// 类似地,可以使用AmericanoCoffeeShop来创建美国咖啡系列的产品
}
}
在这个例子中,抽象工厂模式允许我们定义一组相互关联或相互依赖的产品的接口,而无需指定它们的具体类。通过创建具体工厂类,我们可以很容易地切换到不同的产品系列,而无需修改客户端代码。这增加了代码的灵活性和可扩展性。
总结
工厂模式是一种非常实用的设计模式,它通过将对象的创建与使用分离,使得代码更加灵活和可扩展。在实际项目中,我们可以根据具体的需求选择合适的工厂模式来实现对象的创建。同时,我们也需要注意避免过度使用工厂模式,以免增加系统的复杂性。