代码说明(版本 1)
比如苹果、橙子都是水果,那么可以定义一个水果工厂,工厂中有个方法叫做榨果汁,如果传入的水果为苹果,那么就榨出来苹果汁,如果是橙子那就榨出来橙汁。
按照之前我的写法是这样的
public abstract class Fruit {
abstract void makeJuice();
}
这个没什么好说的,我们假设水果可以榨自己成为果汁,所以给它弄一个 makeJuice 榨果汁
方法。
然后对应的两个实现类,分别榨出属于自己的果汁。
public class Apple extends Fruit {
@Override
void makeJuice() {
System.out.println("榨苹果汁了,快来喝");
}
}
public class Orange extends Fruit {
@Override
void makeJuice() {
System.out.println("榨橙汁了,快来喝");
}
}
当然,水果不是凭空得来的,得有对应的工厂去生产它,所以我们再写一个工厂类。
public abstract class AbstractFactory {
abstract Fruit create(String className);
}
这里定义了一个抽象工厂,主要是因为,可能会有多个苹果工厂来生产苹果。
public class FruitFactory extends AbstractFactory {
@Override
public Fruit create(String className) {
if ("apple".equals(className))
return new Apple();
if ("orange".equals(className))
return new Orange();
return null;
}
}
水果工厂也定义好了,现在只需要客户端去调用就可以生产水果了。
public class FruitClient {
public static void main(String[] args) {
FruitFactory fruitFactory = new FruitFactory();
Fruit apple = fruitFactory.create("apple");
Fruit orange = fruitFactory.create("orange");
apple.makeJuice();
orange.makeJuice();
}
}
执行我们的 main
方法,客户端依次打印出
以上其实就是一个工厂模式。
问题:如果这时候新来了西瓜,需求说现在要榨西瓜汁了,该怎么办?
想都不用想,二话不说,我们新增一个西瓜类
public class Watermelon extends Fruit {
@Override
void makeJuice() {
System.out.println("榨西瓜汁了,要来喝吗?");
}
}
然后修改我们的工厂类,改后的代码如下:
public class FruitFactory extends AbstractFactory {
@Override
public Fruit create(String className) {
if ("apple".equals(className))
return new Apple();
if ("orange".equals(className))
return new Orange();
if ("watermelon".equals(className))
return new Watermelon();
return null;
}
}
最后修改我们的客户端,增加一个
这样我们就创建了一个西瓜,然后调用 watermelon.makeJuice()
就能榨西瓜汁了。
一切看起来都是那么完美,直到……
直到项目经理告诉你说,现在又新来了几种水果。
这时候就有问题了,我们发现每次一新来水果,我们的工厂类就要改变,这不正是违反了开闭原则吗?
开闭原则(对扩展开放,对修改关闭),简单来说,就是对于新的需求,尽量在原有的类上拓展,比如继承,然后重写原来的方法等,尽量不要在原有的类上进行修改,因为 bug 不仅是写出来的,也是改出来的。
并且既然都是工厂类了,我们肯定希望它能稳定点,总是变来变去,说不准哪天就倒闭了呢?
代码说明(版本 2)
版本 1 的代码既然违反了开闭原则,那么我们就来修复它。
可以看到,造成我们反复修改工厂类的主要原因是,每新来一种水果,我们就要将水果的名称传入,然后 new
对应的实例。
那么可不可以,不需要我们主动判断实例类型,不用显式地去 new
对应的实例呢?可不可以在程序运行的时候,能够自动获取到对应的实例呢?
答案是肯定的。这个时候就要有请开着上帝模式的反射登场了。
利用反射机制,我们可以获取到类的各种信息,比如类型,方法,参数,属性,还能修改访问权限等。
总之,反射好像做啥都行,我们这里要创造一个实例,很简单啊,反射给你做!
修改我们的代码:
public abstract class AbstractFactory {
abstract <T extends Fruit> T create(Class<T> c);
}
注意,这里我们的参数由之前的 String
改成了 Class<T>
,主要就是实现反射时候需要用到类名。然后修改我们的具体实现类。
public class FruitFactory extends AbstractFactory {
@Override
<T extends Fruit> T create(Class<T> c) {
try {
return (T) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
这样,在程序运行时,就会利用反射机制自动为我们创建对应的实例。
获取实例的几种方式
-
Object.class
直接调用对象的.class
-
Class.forName(c.getName())
- 通过实例来获取
- 通过类加载器获取
我们这里采用第二种方式获取实例。
最后修改我们的客户端:
public class FruitClient {
public static void main(String[] args) {
FruitFactory fruitFactory = new FruitFactory();
Fruit apple = fruitFactory.create(Apple.class);
apple.makeJuice();
}
}
这样的话,如果新增一个水果,那么我们只需要传入对应的类型就可以了,也就不用频繁修改工厂类了。
好了,到此我们就实现了一个简单工厂。