工厂模式是面向对象设计模式中非常重要,非常流行的模式,是应该首先被理解透彻的模式。
我们讲对象的相关职责包括:
而对象的创建在Java中有四种方式:
工厂模式是创建型设计模式
我们先描述一个场景,某产品展销会上,用户甲需要了解多种产品的详细情况,他需要自己去根据每个产品名字在地图上查找其陈列位置,然后跑过去挨家看。而此时,待在家里的用户乙也想了解一下这个展销会里面的产品,他给举办此次展销会的厂商丙打了个电话,厂商接待人员说,“你想了解哪个产品,把名字告诉我,我直接就告诉那个产品的情况。”
上面提到了对象的三种职责,根据“单一职责原则”。
两个类A和B之间的关系应该仅仅是A创建B或者A使用B,而不能是两种都有。
工厂模式就是要增加一层厂商丙来统一管理对象的创建职责,而不是让对象实例化的代码掺杂在多个类中到处都是。
定义一个工厂类,增加一个静态方法(所以也叫静态工厂模式),传入不同的参数,内部根据这个参数进行判断,返回不同类的实例,这些被创建实例的类通常都有共同的父类。我们看一下在简单工厂模式中,用户是如何获取一个实例化对象的。
class User {
public static void main(String args[]) {
Book book1 = Factory.createBook("novel");
book1.read();
}
}
而工厂类的内容是:
class Factory {
public static Book createBook(String bookType) {
if("novel".equals(bookType){
return new NovelBook();
}else if("poem".equals(bookType){
return new PoemBook();
}else if("history".equals(bookType){
return new HistoryBook();
}else{
return null;
}
}
}
缺陷:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 我们看一下在工厂方法模式中,用户是如何获取一个实例化对象的。
class User {
public static void main(String args[]) {
Factory factory = new NovelBookFactory();
Book book1 = factory.createBook();
book1.read();
}
}
稍作解读,就是在简单工厂模式里的工厂类的基础上又抽象出一层,将其改为工厂接口Factory,声明一个工厂方法factoryMethod(),而具体产品要有对应的工厂类去实现Factory接口,并重写工厂方法返回对应的具体产品实例。将实例化时机又转移给了用户,用户来决定用哪个具体产品实例。
interface Factory {
Book creatBook();
}
class NovelBookFactory implement Factory{
Book creatBook(){
return new NovelBook();
}
}
这与前面提到的场景中,用户甲也是自己判断然后创建实例是截然不同的,因为工程方法只是让用户决定实例化哪个具体产品,而不会让他去创建,创建的工作还是交给工厂,所以在扩展的时候,当前工厂类,产品类都是不用改动的,只需要再增加一对具体工厂类和具体产品即可。
缺陷:
class User {
public static void main(String args[]) {
Book book1 = new NovelBook();
book1.read();
}
}
看上去与上面的工厂方法模式的使用没有任何区别,扩展的时候,也只是需要新增一个子类即可,符合“开闭原则”,但是,这样写是不是就违反了“单一职责原则”呢?我们希望能将具体对象的创建职责通过工厂去管理起来,使程序的可读性更高,也降低类之间的耦合度,不在使用对象时直接new其实例。
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 接着上面的代码改造一下。
class User {
public static void main(String args[]) {
Factory factory = new LuXunBookFactory();
Prose prose1 = factory.creatProseBook();
Essay essay1 = factory.creatEssayBook();
Novel novel1 = factory.creatNovelBook();
prose1.read();
essay1.read();
novel1.read();
}
}
class LuXunBookFactory implement Factory{
Prose creatProseBook(){//散文
return new ProseBook();
}
Essay creatEssayBook(){//杂文
return new EssayBook();
}
Novel creatNovelBook(){
return new NovelBook();
}
}
相较于工厂方法模式,抽象工厂模式将具体工厂类中的具体产品类进行了组合的关联。如上面代码所示,用户要得到的是鲁迅全集的实例,鲁迅全集工厂类就要包括散文,小说和杂文,而这些都是书的子类。如果是工厂方法模式的话,恐怕就要每种书记形式都要建一个自己的工厂类,那实在是庞大至极。程序变得很冗长。所以抽象工厂模式在具体子类中具备一定的关联关系的时候,非常好用。 缺陷:
我们看到了上面用户在使用工厂模式创建实例时,避免了直接new具体对象实例的方式,但是能否把new LuXunBookFactory()也避免了呢?进一步解耦? 答案是肯定的。
我们在创建一个实例的时候,能否直接用字符串本身当做参数来创建对象呢?使用反射就可以达到这个目的。
Factory factory = (Factory) Class.forName("LuXunBookFactory").newInstance();
这样就代替了
Factory factory = new LuXunBookFactory();
那么如果要完全符合“开闭原则”,即用户使用的部分也不去修改,那么就可以采用配置文件的方式。 将字符串"LuXunBookFactory"保存在xml配置文件中。
<?xml version="1.0"?>
<config>
<className>LuXunBookFactory</className>
</config>
每次利用Java工具XMLUtil类去读取该配置文件,用变量代替原代码中字符串的位置。
我们将上面的工厂方法模式用户操作部分粘贴过来看一下:
class User {
public static void main(String args[]) {
Factory factory = new NovelBookFactory();
Book book1 = factory.createBook();
book1.read();
}
}
interface Factory {
Book creatBook();
}
class NovelBookFactory implement Factory{
Book creatBook(){
return new NovelBook();
}
}
我们发现Book中的read方法应该是直接在基类中声明,所有Book子类去重写具体book方法。那么在用户使用时,这个book方法的调用方法就可以抽象到Factory中去。 当前Factory是接口,接口是无法写方法体的,因此要改为abstract class。
abstract class Factory{
abstract Book creatBook();
public void read(){
Book book = this.creatBook();
book.read();
}
}
那么此时,用户在操作时就可以改为
class User {
public static void main(String args[]) {
Factory factory = new NovelBookFactory();
factory.read();
}
}
具体产品工厂类NovelBookFactory中并不需要去管藏方法的事,藏方法一定是在abstract class中放一个具体方法,用于获取当前抽象方法创建的具体子类实例后,直接调用其操作方法。 这样一来,在外部操作的时候,直接用Factory对象就可以调用具体子类里面的操作方法。