工厂模式(Factory Pattern)属于创建型模式,它提供了一种创建对象的较优方式,是 Java 中最常用的设计模式之一。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
工厂模式有三种形式,分别是简单工厂、工厂方法、抽象工厂。
背景
有个35岁的码农因为疫情被裁员,就琢磨做点小生意。刚好上海有很多外国人,该码农灵机一动,想开家Pizza店,用以谋生。Pizza店初期试水阶段主要卖两种产品,CheesePizza 和 GreekPizza 。
Pizza
public class Pizza {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void prepare(){
System.out.println("准备pizza");
}
public void bake(){
System.out.println("烘烤pizza");
}
public void cut(){
System.out.println("切片pizza");
}
public void box(){
System.out.println("打包pizza");
}
}
CheesePizza
public class CheesePizza extends Pizza {
public CheesePizza() {
this.name = "CheesePizza";
}
@Override
public String toString() {
return "CheesePizza{" +
"name='" + name + '\'' +
'}';
}
}
GreekPizza
public class GreekPizza extends Pizza {
public GreekPizza() {
this.name = "GreekPizza";
}
@Override
public String toString() {
return "GreekPizza{" +
"name='" + name + '\'' +
'}';
}
}
PizzaStore
public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
if (StringUtils.equals(type, "cheese")) {
pizza = new CheesePizza();
} else if(StringUtils.equals(type, "greek")) {
pizza = new GreekPizza();
}else{
pizza = new CheesePizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
orderPizza()方法中会传入的Pizza类型创建具体的Pizza类,如果Pizza出于同行竞争,Pizza种类变得更多,创建Pizza的这段代码必须一改再改,这违背了面向对象对修改关闭。可以把创建Pizza的代码移到另一个对象中,由这个对象专职创建Pizza。
简单工厂
SimplePizzaFactory
我们新建一个简单工厂类,可以生产所有的Pizza类型。
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza;
if (StringUtils.equals(type, "cheese")) {
pizza = new CheesePizza();
} else if(StringUtils.equals(type, "greek")) {
pizza = new GreekPizza();
}else{
pizza = new CheesePizza();
}
return pizza;
}
}
新的PizzaStore
新的PizzaStore应该这样写了
public class PizzaStore {
SimplePizzaFactory simplePizzaFactory;
public PizzaStore() {
this.simplePizzaFactory = new SimplePizzaFactory();
}
public Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
工厂方法
简单工厂负责生产所有东西,当增加新的Pizza种类时,上述简单工厂中的createPizza()方法逻辑需要改变,即需要添加新的 if-else 分支。能生产多种披萨看似功能很强大,但大家想想,这个世界存在什么都生产的工厂吗?
上述代码中我们简化了生产的逻辑,真实场景,生产逻辑相对复杂,比如选择各种原料,各种披萨尺寸。如果仍使用简单工厂生产所有披萨,一旦生产逻辑变化,因为简单工厂负责的功能相对较多,或许要改动的地方就太多了。改动多,管理起来就很不方便,就容易犯错。同时改动现有代码也违背了面向对象的对扩展开放,对修改关闭的原则,我们不应该允许随意修改现有代码。这个时候可以采用工厂方法。
接口PizzaFactory
public interface PizzaFactory {
Pizza createPizza();
}
CheesePizzaFactory
public class CheesePizzaFactory implements PizzaFactory {
@Override
public Pizza createPizza() {
return new CheesePizza();
}
}
GreekPizzaFactory
public class GreekPizzaFactory implements PizzaFactory {
@Override
public Pizza createPizza() {
return new GreekPizza();
}
}
PizzaStore
public class PizzaStore {
public static void main(String[] args) {
PizzaFactory cheesePizzaFactory = new CheesePizzaFactory();
Pizza cheesePizza = cheesePizzaFactory.createPizza();
PizzaFactory greekPizzaFactory = new GreekPizzaFactory();
Pizza greekPizza = greekPizzaFactory.createPizza();
}
}
这里我把PizzaStore的功能改变了,让其只是一个测试类了,这不影响工厂方法模式的内涵。工厂方法的本质在于一个工厂只负责生产一种披萨,如果想生产其他种类的披萨,可以实现一个新的工厂类,负责生产新品类的披萨。这样就不会动现有代码了,满足了对扩展开放-修改关闭的原则。
简单工厂和工厂方法模式的不同在于前者生成产生产品的行为封装在一个方法中,根据参数的类型进行实例化,同时不存在抽象接口。而后者则增加了抽象工厂,通过工厂方法的不同实现方式来创建不同的产品,一种工厂通常指生产一类商品,这种方式相较于前者扩展性更高,在需求增加时完全符合开闭原则和依赖倒置原则。
抽象工厂
码农生意做得火爆,于是想开拓业务到四川。但是四川人的口味偏辣,现阶段的产品必然不能 满足川渝人的口味。老板必须要做川渝口味的披萨。
上述Pizza是专门针对外国人口味的,其中准备、烘烤等做法不能直接使用,新的川味Pizza是一种新的产品,我们使用一个新的父类ChuanPizza。
新的产品
ChuanPizza
public class ChuanPizza {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void prepare(){
System.out.println("准备川味pizza");
}
public void bake(){
System.out.println("烘烤川味pizza");
}
public void cut(){
System.out.println("切片川味pizza");
}
public void box(){
System.out.println("打包川味pizza");
}
}
ChuanCheesePizza
public class ChuanCheesePizza extends ChuanPizza{
public ChuanCheesePizza() {
this.name = "ChuanCheesePizza";
}
}
ChuanGreekPizza
public class ChuanGreekPizza extends ChuanPizza{
public ChuanGreekPizza() {
this.name = "ChuanGreekPizza";
}
}
新的工厂
现在我们的CheesePizzaFactory 工厂不仅要能生产符合外国人口味的披萨,还要能生产符合川渝人口味的披萨。我们可以在PizzaFactory 中新增一个createChuanPizza()方法,然后由不同的工厂来实现它。
PizzaFactory
public interface PizzaFactory {
Pizza createPizza();
ChuanPizza createChuanPizza();
}
CheesePizzaFactory
负责生产CheesePizza披萨的CheesePizzaFactory,因为对CheesePizza披萨的生产比较精通,可以大胆的将生产川味CheesePizza的任务交给它
public class CheesePizzaFactory implements PizzaFactory {
@Override
public Pizza createPizza() {
return new CheesePizza();
}
@Override
public ChuanPizza createChuanPizza() {
return new ChuanCheesePizza();
}
}
GreekPizzaFactory
public class GreekPizzaFactory implements PizzaFactory {
@Override
public Pizza createPizza() {
return new GreekPizza();
}
@Override
public ChuanPizza createChuanPizza() {
return new ChuanGreekPizza();
}
}
抽象工厂模式是工厂方法模式的升级版,工厂方法面向单个产品,抽象工厂面向的的是一个产品族。抽象工厂定义:为创建一组相关/互相依赖的对象,提供一个接口而无需指定它们的具体类。
比如生产5G手机,富士康可以给苹果代工,也可以给华为代工,因为他们是一类商品,一个工厂也较容易管理它们。通常产品族的定义我觉得才是困难之处,比如到底富士康在产能有限的情况下,生产方式1:ipad/iphone/mac/d,方式2:苹果手机、华为手机、小米手机,选择哪种生产方式才是难以做抉择的。
上述上产披萨其实我划分的工厂也有问题,方式1:一个工厂负责生产外国人口味的芝士披萨、greek披萨;另一个工厂负责生产川味芝士披萨、川味greek披萨。方式2:一个工厂生产芝士披萨、川味芝士披萨;另一个工厂生产greek披萨、川味greek披萨。仁者见仁智者见智。
总结
简单工厂:把复杂的创建对象的过程单独拿出来,用一个类负责创建。通常这些各种产品继承自同一个父类。
工厂方法:一个工厂只负责生产一种产品。新增产品时,无须更改现有代码,可以扩展出新的工厂来生产新产品。
初学阶段,你可能会想到,在工厂中写两个方法,如下
public interface PizzaFactory {
CheesePizza createCheesePizza();
GreekPizza createGreekPizza();
}
但是感觉这样违背了面向接口编程的习惯,你在代码中也不再能使用多态了,因此还是分成两个工厂较为好一点。
抽象工厂:可以扩展工厂的功能,使其能生产一族产品。这族产品通常不是继承自同一个父类,因此抽象工厂中可以添加新的方法。
在使用抽象工厂模式时,怎么划分工厂的职责,也是难以决定的地方。大概需要很多的设计模式使用经验,才能真正学会使用设计模式吧。