new是创建对象实例时最常用的方法(不是唯一方法,反射也可以创建实例),但是一旦涉及到new,就会涉及到具体实现类,而非接口,这就可能带来一些问题。例如下述代码:
Car car = new Car();
Wheel wheel = null;
if (type.equal("A")) {
wheel = new AWheel();
} else if (type.equal("B")) {
wheel = new BWheel();
} else if (type.equal("C")) {
wheel = new CWheel();
}
car.setWheel(wheel);
很简单的代码,创建一个汽车对象和轮子对象,然后根据type来选择使用哪种轮子。这里代码是有问题的,如果随着时间的变化,A轮子不生产了,也就是A轮子不能使用了,那我们就不得不修改代码,将A相关的代码移除,但有很多其他客户端也是使用这样的代码,这意味着其他客户端的代码也必须得改。换句话说,这里一系列的判断是会“变化”的部分。比较好的方法是将其封装起来,单独放到一个地方去,以后如果有变化,只需改动一处即可。这就是简单工厂模式。
简单工厂模式
我们换个例子,现在来看看一个披萨的例子。如下代码所示:
//Pizza抽象类
public abstract class Pizza {
protected String name;
public abstract void bake();
public abstract void cut();
@Override
public String toString() {
return "Pizza{" +
"name='" + name + '\'' +
'}';
}
}
//A类型Pizza
public class APizza extends Pizza {
public APizza() {
this.name = "A Pizza";
}
@Override
public void bake() {
System.out.println("A bake");
}
@Override
public void cut() {
System.out.println("A cut");
}
}
//B类型Pizza
public class BPizza extends Pizza {
public BPizza() {
this.name = "B Pizza";
}
@Override
public void bake() {
System.out.println("B bake");
}
@Override
public void cut() {
System.out.println("B cut");
}
}
//Pizza商店
public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = null;
if ("A".equals(type)) {
pizza = new APizza();
} else if ("B".equals(type)) {
pizza = new BPizza();
}
if (pizza != null) {
pizza.bake();
pizza.cut();
}
return pizza;
}
}
//测试类
public class Main {
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
Pizza pizza = store.orderPizza("A");
}
}
运行测试类,可以看到如下结果:
A bake
A cut
符合我们的预期,但是如果我们有很多个商店,而且这些商店订购披萨的逻辑都是根据类型来判断(注意各个商店卖的Pizza不一定相同,所以往往不能抽到基类中去),即上述的if-else if ..结构。那么如果现在不打算生产A类型的Pizza了,取而代之的是C类型Pizza,我们就不得不到每个和A类型Pizza相关的商店代码里更改。
现在用简单工厂模式来改写一下代码,将if-else结构抽到工厂类里。如下代码所示:
//简单工厂
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
if ("A".equals(type)) {
return new APizza();
} else if ("B".equals(type)) {
return new BPizza();
}
return null;
}
}
//修改后的Pizza商店
public class PizzaStore {
private SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza = this.factory.createPizza(type);
if (pizza != null) {
pizza.bake();
pizza.cut();
}
return pizza;
}
}
其余类不做修改,这样做的好处是将变化的部分抽到一个单独的类中,Pizza商店类仅仅依赖工厂的实现,而不依赖Pizza的具体实现,可以简单理解成Pizza商店不再自己生产Pizza,而是需要什么样的Pizza就到一个工厂中去“取货”即可。
现在会过来分析一下如果需求变化了会发生什么?如果某个Pizza商店不需要A类型Pizza了(可能是因为不好卖),Pizza该怎么做呢?在我们的代码里,他什么也不用做!不需要修改任何代码即可,这是因为生产Pizza的责任交由Pizza工厂来管理了,客人需要什么样的Pizza,商店直接去Pizza工厂拿就行了,不关心Pizza具体是怎样的。
不过这种方法也有局限性,例如现在有很多商店,这些商店可能想要一些自己的特色,不想到工厂里获取(注意,工厂也可以有很多种,商店可以任意选择到哪个工厂获取),该怎么办?一种解决方案是利用工厂方法模式。
工厂方法模式
工厂方法模式和简单工厂的区别是,生产产品的地方不是类,而是在一个方法里,如下代码所示:
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
if (pizza != null) {
pizza.bake();
pizza.cut();
}
return pizza;
}
protected abstract Pizza createPizza(String type);
}
//注意这里的createPizza(String type);是抽象方法。
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
if (type.equals("A")) {
return new APizza();
}
return null;
}
}
public class CHPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
if (type.equals("B"))
return new BPizza();
return null;
}
}
//测试类
public class Main {
public static void main(String[] args) {
PizzaStore pizzaStore = new NYPizzaStore();
pizzaStore.orderPizza("A");
}
}
可以看到上述代码不再使用单独的工厂类了,具体的生产Pizza的责任交由每个商店自己处理,自己想要干嘛就干嘛,而且自己负责。
抽象工厂模式
现在各个商店还不满足,他们发现客人对原料的要求很高,所以他们想自己去拿原料,各地的原料的质量又不一样,那到哪拿呢?怎么拿呢?这种情况可以用抽象工厂模式解决,引入一个抽象工厂,其子类是具体的工厂,然后各个商店决定从哪个工厂拿材料。如下代码所示:
public interface PizzaFactory {
void createSourceA();
void createSourceB();
void createSourceC();
}
public class APizzaFactory implements PizzaFactory {
@Override
public void createSourceA() {
System.out.println("A create source A");
}
@Override
public void createSourceB() {
System.out.println("A create source B");
}
@Override
public void createSourceC() {
System.out.println("C create source C");
}
}
//B工厂的代码和A工厂差不多,不贴了。
//修改后的商店
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
PizzaFactory pizzaFactory = new APizzaFactory();
if (type.equals("A")) {
return new APizza(pizzaFactory);
}
return null;
}
}
//修改后的A类型Pizza
public class APizza extends Pizza {
private PizzaFactory pizzaFactory;
public APizza(PizzaFactory pizzaFactory) {
this.pizzaFactory = pizzaFactory;
this.name = "A Pizza";
}
@Override
public void bake() {
this.pizzaFactory.createSourceA();
this.pizzaFactory.createSourceB();
this.pizzaFactory.createSourceC();
System.out.println("A bake");
}
@Override
public void cut() {
System.out.println("A cut");
}
}
//测试类
public class Main {
public static void main(String[] args) {
PizzaStore pizzaStore = new NYPizzaStore();
pizzaStore.orderPizza("A");
}
}
这样一来,各个商店可以自己决定从哪个工厂拿原料来生成Pizza了。
小结
工厂模式主要有三种:
- 简单工厂(常见的静态工厂也属于简单工厂)。
- 工厂方法。
- 抽象工厂。
简单工厂很简单直观,就是将生产产品的逻辑抽离到一个单独的类中做管理,需要的时候就去拿。但是有局限性,即不够灵活。
工厂方法的灵活性比简单工厂要高,通过继承,子类可以自行决定生产的逻辑。
抽象工厂和工厂方法的区别不是那么明显,比较直观的区别是工厂方法是通过继承来做的,抽象工厂主要是通过组合来实现。抽象工厂的缺点是扩展需要新增子类,而工厂方法仅需要实现方法的具体逻辑即可,不过抽象工厂的结构会更加明显,易于阅读。总之,这两种方式效果差不多,具体选择哪种,还得看实际情况决定。
下面是工厂模式的定义:
工厂方法模式:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例推迟到子类
抽象工厂模式: 抽象工厂模式提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。