设计模式之工厂模式
学Spring的时候常常听说到工厂模式,什么BeanFactory大都听过一点
描述的结构
- 引入
- 简单工厂模式的两种实现
- 工厂模式的实现
- 抽象工厂的实现
引入
工厂模式到底是什么呢?
看下面代码:(比较繁琐。不想看直接跳过!!!)
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = null;
if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new XmlRuleConfigParser();
} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new YamlRuleConfigParser();
} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new PropertiesRuleConfigParser();
} else {
throw new InvalidRuleConfigException(
"Rule config file format is not supported: " + ruleConfigFilePath);
}
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数。按照这个设计思路,我们可以将代码中涉及 parser 创建的部分逻辑剥离出来,抽象成 createParser() 函数。重构之后的代码如下所示:
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = createParser(ruleConfigFileExtension);
if (parser == null) {
throw new InvalidRuleConfigException(
"Rule config file format is not supported: " + ruleConfigFilePath);
}
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
private IRuleConfigParser createParser(String configFormat) {
IRuleConfigParser parser = null;
if ("json".equalsIgnoreCase(configFormat)) {
parser = new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(configFormat)) {
parser = new XmlRuleConfigParser();
} else if ("yaml".equalsIgnoreCase(configFormat)) {
parser = new YamlRuleConfigParser();
} else if ("properties".equalsIgnoreCase(configFormat)) {
parser = new PropertiesRuleConfigParser();
}
return parser;
}
}
为了让类的职责更加单一(设计模式原则中的单一职责,)、代码更加清晰,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类。
简单工厂模式
为了便于理解我们就用一个简单的例子来试验一下
现在我们有一个Shape接口 里面只有一个方法就是画画的方法
public interface Shape {
public void draw();
}
然后我们有他的实现类 Y(圆)Z(正方型)
public class Y implements Shape{
@Override
public void draw() {
System.out.println("Y");
}
}
public class Z implements Shape{
@Override
public void draw() {
System.out.println("z");
}
}
现在我们有一个工厂类来负责实例化对象
public class Factory {
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
}if (shapeType.equals("Y")) {
return new Y();
}if (shapeType.equals("Z")){
return new Z();
}return null;
}
}
这样的话我们对于对象的获取就非常方便了,我们直接调用工厂类就可以进行获取,这也是简单工厂模式的第一种实现方式。
但是问题来了,如果我们需要添加一个类的话,需要改的代码实在是太多了
如果 可以复用,为了节省内存和对象创建的时间,我们可以将这些Shape事先创建好缓存起来。当调用 getShape() 函数的时候,我们从缓存中取出 Shape 对象直接使用。这有点类似单例模式和简单工厂模式的结合
import java.util.HashMap;
import java.util.Map;
public class Factory {
private static final Map<String, Object> map = new HashMap<String, Object>();
static {
map.put("Y", new Y());
map.put("Z", new Z());
}
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
Shape shape = (Shape) map.get(shapeType);
return shape;
}
}
我们把上一种实现方法叫作简单工厂模式的第二种实现方法。
- 对于上述的两种工厂模式,我们如果需要添加一个类,我们需要修改的东西太多了,那么这符合我们开闭原则吗?实际上,如果不是频繁的修改代码,偶尔修改一下,不满足开闭原则,我们也是可以接受的。
- 除此之外,在第一种代码实现中,有一组 if 分支判断逻辑,是不是应该用多态或其他设计模式来替代呢?实际上,如果 if 分支并不是很多,代码中有 if 分支也是完全可以接受的。应用多态或设计模式来替代 if 分支判断逻辑,也并不是没有任何缺点的,它虽然提高了代码的扩展性,更加符合开闭原则,但也增加了类的个数,牺牲了代码的可读性。
- 总结一下,尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也没有太多的 Shape)是没有问题的。
工厂方法
如果我们非得要将 if 分支逻辑去掉,那该怎么办呢?比较经典处理方法就是利用多态:这里我们使用人类来表示,分为黑人和白人
这里有一个人的大类接口
public interface human {
public void say();
}
然后有两个他的实现类
黑人
public class BlackHuman implements human {
@Override
public void say() {
System.out.println("i am a black human");
}
}
白人
public class WhiteHuman implements human{
@Override
public void say() {
System.out.println("I am a white human");
}
}
然后们的工厂类
public abstract class AbstractHumanFactory {
public abstract human createHuman();
}
继承两个工厂类的(子工厂)类
黑人工厂
public class BlackHumanFactory extends AbstractHumanFactory{
@Override
public human createHuman() {
return new BlackHuman();
}
}
白人工厂
public class WhiteHumanFactory extends AbstractHumanFactory{
@Override
public human createHuman() {
return new WhiteHuman();
}
}
测试类
public class Test {
public static void main(String[] args) {
AbstractHumanFactory abstractHumanFactory = null;
abstractHumanFactory = new BlackHumanFactory();
BlackHuman blackHuman = (BlackHuman) abstractHumanFactory.createHuman();
blackHuman.say();
abstractHumanFactory = new WhiteHumanFactory();
WhiteHuman whiteHuman = (WhiteHuman) abstractHumanFactory.createHuman();
whiteHuman.say();
}
}
这样的实现方法在如果我们想增加一个黄种人进去,那么我们只需要实现一个Human大类的接口和集成Factory的子工厂就可以了,这样的话就大大的解耦了
所以,工厂方法模式比起简单工厂模式更加符合开闭原则。
抽象工厂方法
在简单工厂和工厂方法中,类只有一种分类方式。
但是,如果类有两种分类方式呢?
比如我们的黑人和白人分别还分为男人和女人
- 这就引出了我们的抽象工厂方法
在这种方法中,我们还是有一个大类Human
public interface Human {
void printColor();
}
它的两个抽象实现方法(注意抽象)
Man和WoMan
public abstract class Man implements Human {
public void printGender() {
System.out.println("I am a man");
}
@Override
public abstract void printColor();
}
public abstract class WoMan implements Human{
public void printGender(){
System.out.println("i am a woman");
}
@Override
public abstract void printColor();
}
在man和woman的分类下当然还有之前的黑和白
Man下的子类
public class BlackMan extends Man{
@Override
public void printColor() {
System.out.println("i am blackColor");
}
}
public class WhiteMan extends Man {
@Override
public void printColor() {
System.out.println("I am white Color");
}
}
WoMan下的子类
public class BlackWoman extends WoMan{
@Override
public void printColor() {
System.out.println("I am black Color");
}
}
public class WhiteWoman extends WoMan {
@Override
public void printColor() {
System.out.println("i am white color");
}
}
当然还有我们的工厂类
public abstract class AbstractHumanFactory {
public abstract Man createMan();
public abstract WoMan createWoman();
}
黑人的(子工厂)
public class BlackHumanFactory extends AbstractHumanFactory{
@Override
public Man createMan() {
return new BlackMan();
}
@Override
public WoMan createWoman() {
return new BlackWoman();
}
}
白人的(子工厂)
public class WhiteHumanFactory extends AbstractHumanFactory{
@Override
public Man createMan() {
return new WhiteMan();
}
@Override
public WoMan createWoman() {
return new WhiteWoman();
}
}
最后测试一下
public class test {
public static void main(String[] args) {
AbstractHumanFactory factory = null;
factory = new BlackHumanFactory();
BlackMan blackMan = (BlackMan) factory.createMan();
blackMan.printColor();
blackMan.printGender();
BlackWoman blackWoman = (BlackWoman) factory.createWoman();
blackWoman.printColor();
blackWoman.printGender();
factory = new WhiteHumanFactory();
WhiteMan whiteMan = (WhiteMan) factory.createMan();
whiteMan.printColor();
whiteMan.printGender();
WhiteWoman whiteWoman = (WhiteWoman) factory.createWoman();
whiteWoman.printColor();
whiteWoman.printGender();
}
}
这就是整个的实现过程了
那么看完之后我们发现了什么呢?
再看一遍抽象工厂的类图关系
0、 在这里我们可以清楚的看到,如果我们想要获取一个BlackMan对象,那么我们只需要调用BlackHumanFactory这个工厂就行了,我们不需要去了解他的实现。
1、 在我们想要加入一个新的颜色的人,我们也不必去管其他的类,例如这里的黑人和白人。换个方式讲,黑人和白人放在这里很稳定,测试通过后就可以不必去改动它,如果我们想要增加,只需要实现对应的方法,测试新实现的就好了
2、工厂模式虽然麻烦,但是耦合度可以大大的降低,这是目前我认为工厂模式最大的好处。