引入
假设我们现在有一个需求;
设计一个咖啡店点餐系统;
设计一个咖啡类(Coffee),并定义其两个子类(A咖啡和B咖啡)
再设计一个咖啡店类(Store),咖啡店具有点咖啡的功能。
如图所示;
那么代码如下
package simple_factory;
public class Store {
public Coffee orderCoffee(String coffeeName){
if(coffeeName.equals("coffeeA")){
return new ACoffee();
}else if(coffeeName.equals("coffeeB")){
return new BCoffee();
}else{
throw new RuntimeException("不存在这种Coffee");
}
}
}
-------
package simple_factory;
public abstract class Coffee {
public abstract String getCoffeeName();
}
-------
package simple_factory;
public class ACoffee extends Coffee{
@Override
public String getCoffeeName() {
return "A咖啡";
}
}
-------
package simple_factory;
public class BCoffee extends Coffee{
@Override
public String getCoffeeName() {
return "B咖啡";
}
}
这样的设计有明显的缺点
- 如果我们其他地方又需要点咖啡(比如外卖系统),那么判断咖啡种类的代码是会重复的;
- 如果要新添咖啡种类,那么我们需要修改代码,违背了开闭原则
优化一
我们引入一个咖啡工厂,作为中间件,帮助咖啡店与咖啡解耦,咖啡店只需要与工厂打交道即可;
即使后期推出外卖类,也只需要与工厂打交道
如下图
代码如下,咖啡部分的代码不变,如优化前;
package simple_factory;
public class EasyCoffeeFactory {
public static Coffee createCoffee(String coffeeName){
if(coffeeName.equals("coffeeA")){
return new ACoffee();
}else if(coffeeName.equals("coffeeB")){
return new BCoffee();
}else{
throw new RuntimeException("不存在这种Coffee");
}
}
}
---
package simple_factory;
public class Store {
public Coffee orderCoffee(String coffeeName){
return EasyCoffeeFactory.createCoffee(coffeeName);
}
}
这样做,我们解决了上述的问题1,但是遇到新品种咖啡,我们依然需要修改代码,依然违反开闭原则;
优化二
我们引入一个配置文件,实现极其简易的IOC容器;
命名为coffeeConfig.properties
,内容如下;
coffeeA = simple_factory.ACoffee
coffeeB = simple_factory.BCoffee
我们只修改工厂部分的代码,其他部分不变;
package simple_factory;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
public class EasyCoffeeFactory {
private static HashMap<String,Coffee> coffeeMap = new HashMap();
static {
Properties properties = new Properties();
InputStream is = EasyCoffeeFactory.class.getClassLoader().getResourceAsStream("coffeeConfig.properties");
try {
properties.load(is);
Set<Object> keySet = properties.keySet();
//这里的key是配置文件中的coffeeA和coffeeB
for (Object key : keySet) {
String clsName = (String) properties.get(key);
Class cls = Class.forName(clsName);
Coffee coffee = (Coffee) cls.getDeclaredConstructor().newInstance();
coffeeMap.put((String) key,coffee);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String coffeeName){
return coffeeMap.get(coffeeName);
}
}
这样以后需要添加新的咖啡的时候,就不需要改动这里的代码了;
只需要在配置文件中写上新的key以及全类名即可,如下;
coffeeA = simple_factory.ACoffee
coffeeB = simple_factory.BCoffee
coffeeC = simple_factory.CCoffee
当然目前这个工厂里的咖啡对象都是相同的,当然你也可以改写成不同的,怎样都行…
比如可以改成这样
package simple_factory;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
public class EasyCoffeeFactory {
private static HashMap<String,Constructor> coffeeMap = new HashMap();
static {
Properties properties = new Properties();
InputStream is = EasyCoffeeFactory.class.getClassLoader().getResourceAsStream("coffeeConfig.properties");
try {
properties.load(is);
Set<Object> keySet = properties.keySet();
//这里的key是配置文件中的coffeeA和coffeeB
for (Object key : keySet) {
String clsName = (String) properties.get(key);
Class cls = Class.forName(clsName);
Constructor constructor = cls.getDeclaredConstructor();
coffeeMap.put((String) key,constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String coffeeName) throws IllegalAccessException, InvocationTargetException, InstantiationException {
return (Coffee) coffeeMap.get(coffeeName).newInstance();
}
}