创建型设计模式
知识补充
java反射机制
此处仅作最简单的应用型描述,因为下面会用到,所以在此补充,读者如果想深入了解java反射机制,请自行查资料。
举个简单例子:
Class c = Class.forName("String");
Object obj = c.newInstance();
return obj;
上面的代码能够返回一个String 对象(与new String()是一样的),比较神奇的是,我的String是出现在字符串中的。
所以可以做最简单的理解,java反射机制的一个最简单的应用就是从字符串到一个类或者对象的转变,即给你一个"String",你能通过反射机制获取String对象。
索引
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 原型模式
- 建造者模式
1. 简单工厂模式
简述
- 简单工厂模式不是GOF提出的23种模式中的一个,简单工厂仅仅是一个小弟,他大哥叫抽象工厂模式。
- 思路很简单,一张图就能搞明白:
- 示例代码:
package structure.SampleFactory; public class ConcreteProductB extends Product { @Override void display() { System.out.println("ConcreteProductB"); } } ============分割线============ package structure.SampleFactory; public class ConcreteProductA extends Product { @Override void display() { System.out.println("ConcreteProductA"); } } ==============分割线============= package structure.SampleFactory; import org.junit.Test; public class Factory { static Product factoryMethod(String name) { Product product; if (name.equals("A")) { product = new ConcreteProductA(); } else if (name.equals("B")) { product = new ConcreteProductB(); } else { product = null; } return product; } @Test public void testFactory() { Product product = factoryMethod("A"); product.display(); Product product1 = factoryMethod("B"); product1.display(); } }
理解
- 上图一共四个类,抽象产品类,具体产品A类,具体产品B类,工厂类,前三个类的作用不用多说了,提取出一个抽象类有利于开闭原则以及代码复用。工厂类的作用:看上图左边的代码,根据传的参数来判断返回哪一个具体产品类。
- 上图仅仅是一个基本原理图,聪明的读者一定发现,这种代码还是不符合开闭原则。关于开闭原则,参考笔者的上一篇博客
个人体会:
- 这里也有针对接口编程的体现,Factory类中的factoryMethod(String arg):Product表示返回类型是Product即抽象类,根据参数判断返回哪个具体类。
- 上述方法的缺点:仅仅将复杂的逻辑移动到了工厂类而已,并不能削减复杂度,而且如果新增产品的话,还是需要修改代码,导致扩展困难。
适用范围: - 适用于创建的对象比较少,而且比较固定,不会产生扩展的情况。
2. 工厂方法模式
简述
上面说到,简单工厂方法对扩展很不友好,需要修改代码,不符合开闭原则,所以引入了工厂方法模式。
示例代码,只贴出了核心调用代码,其余的没有贴,读者可以自行完善:
package structure.Factory;
import org.junit.Test;
public class ConcreteFactoryA extends Factory {
@Override
Product factoryMethod() {
Product product = new ConcreteProduct();
/*
* 这里存在大量用于初始化产品的代码
* */
return product;
}
@Test
public void testConcreteFactory() {
Factory factory = new ConcreteFactoryA();
Product product = factory.factoryMethod();
product.display();
}
}
理解
上图仅仅是最基本的原理图,读者可能还会有一些疑惑:当需要扩充新产品ConcreteProductB的时候怎么办?答案是:新建一个与之对应的ConcreteFactoryB,那么问题又来了:
每个产品都对应一个工厂,而且工厂里的factoryMethod()方法就执行了一行new语句,那要工厂有啥用呢?
- 事情并不是这样,其实引入工厂的初衷是因为类或者对象需要大量初始化代码,我们将默认初始化代码全部放入factoryMethod中,这样对于客户端代码来说就友好了很多,也就是说 工厂方法创建的对象是已经做过一些初始化操作的。
这种方法能符合开闭原则的 对修改关闭对扩展开放吗?
能:当需要新增ConcreteProductB的时候,新建ConcreteProductB和ConcreteFactoryB,这就是对扩展开放。那么客户端在创建产品的时候还是要修改一些语句,比如需要将 ConcreteFactory.factoryMethod() 修改成 ConcreteFactoryB.factoryMethod() 那岂不是还是要修改代码,这个问题我们在下文解决。
使用反射解决上文的问题
关于反射的简单使用,看上文的知识补充。其实解决方案在上两篇文章中提到过:依赖注入。将类名写在xml文件中,然后读取xml文件,利用反射获取相应对象,下面给一个示例代码:
package util;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
//读取配置文件,并返回类型名
private static String getClassType(String nodeName) {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config/config.xml"));
//获取包含图表类型的文本结点
NodeList nl = doc.getElementsByTagName(nodeName);
Node classNode = nl.item(0).getFirstChild();
// System.out.println("getClassType name is:" + name);
return classNode.getNodeValue();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static Object getBean(String nodeName) {
try {
Class c = Class.forName("structure.Factory." + getClassType(nodeName));
return c.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
==================分割线==================
<?xml version="1.0"?>
<config>
<chartType>NewChart</chartType>
<ConcreteFactory>ConcreteFactoryA</ConcreteFactory>
</config>
==================分割线=================
@Test
public void testConcreteFactory2() {
Factory factory = (Factory) XMLUtil.getBean("ConcreteFactory");
Product product = factory.factoryMethod();
product.display();
}
再次引入一个问题:
客户需要修改配置文件岂不是很麻烦,其实解决方案很简单,将修改配置文件部分做成图形界面就ok了。
总结
优点:
- 用户无需关心创建产品的细节
- 在系统中加入新产品的时候,无需修改代码,只需去修改配置文件,然后加入具体产品类和其对应的工厂方法。修改配置文件而非代码的一个优点就是不需要重新编译,在运行时确定要使用哪个对象。
缺点也很明显:
- 增加了类的个数*,每次引入新产品都需要加具体产品类和对应的工厂方法。
- 引入抽象层,增加了理解难度,而且还用到了反射,增加了系统实现难度。
适用场景:
- 客户端不知道它所需要的对象的类,只需要知道对应的工厂即可。
- 在程序运行时确定使用哪个子类,然后进行替换,覆盖,使系统更容易扩展。
- 如果产品比较复杂, 初始化代码很多,那么加一层工厂是很好的。如果产品比较简单,那么可以省略工厂。
3. 抽象工厂模式
简述
抽象工厂模式又称为Kit模式,可以理解成是一种打包式的工厂模式,即工厂模式如果子工厂类无限扩充怎么办?那把他们打包一下,再增加一个抽象层,也就是抽象工厂。
理解
如果能够理解工厂方法模式的话,再理解抽象工厂方法模式就不难了,他们本质上是相同的。抽象工厂针对的是一个产品家族,而工厂方法针对的是一个产品,可以理解成一个抽象工厂包含多个工厂,这样抽象工厂生产的就是一堆产品,即产品族。
总结
优点
隔离了具体类的生成,生成了产品族,而且增加新的产品族很方便
缺点
增加新的产品结构很难:抽象工厂A内有A1, A2, A3工厂,抽象工厂B内有B1, B2, B3,我新增一个抽象工厂C内涵C1, C2, C3很简单。如果要将A中的A1换成B1呢?这就需要修改代码了。
适用场景
- 系统中有产品需要组合成一个产品族。
- 产品族中的产品是有约束的,即这些产品是一起生效的,比如一个皮肤中的背景+按钮样式他们是一起生效的,属于同一款主题中的不同控件。
- 产品结构稳定,不会频繁更新产品结构。
下一节传送门 单例模式