- 意图: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 何时使用: 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
前言
Builder模式用来组装复杂的实例,Builder就是我们盖房子的一块块砖头,钢筋和水泥,以及简单的用法,使用监工将这些元素有机的组合在了一起就能够建造整个建筑了,是监工将这些原材料按照一定的次序和特定的处理流程糅合在了一起,这个过程就是组装了。而现在我们学习抽象工厂模式,将关键零件组装成产品。
抽象工厂模式是一个非常复杂的模式,和工厂方法简直差别太大了,但是也有相同之处,那就是抽象工厂也是用了工厂方法来创造产品,只不过抽象工厂模式中包含了零件、产品、工厂、抽象等概念,这样就非常的复杂了,一般还要用到模板方法、迭代器甚至原型模式等,就“抽象”两个字来说,就是将所有的角色都分成两部分,一部分是这个角色的的抽象类,另一部分是这个角色的具体类(实现类),工厂就是沿用了工厂模式,因此抽象工厂模式主要分成两大部分,抽象部分和具体部分,如图所示。
就上面的抽象部分,最重要的就是抽象的工厂类、抽象的零件类(Link和Tray)、抽象的产品类(Page),这两个零件有共同之处,因此通过Item类进行抽象便于两者之间的互通。下面的具体实现部分就是对抽象的实现了,之后我们使用Main类来进行整合,可以发现我们只用对抽象类进行编程,完全不用使用任何的具体类就能实现我们想要的功能,甚至导致编译器在编译的时候还需要指出需要编译的具体类,因为我们使用了Class.forName()方法实现了反射。这样的结构看似非常的庞大,其实仔细的推敲,反而妙趣横生,当我们还想创建另一个具体的工厂的时候实在是太简单不过了,原来抽象工厂的代码都不用修改,只用按照相应的抽象实现就可以了,之后我们就可以直接使用了,只用在Main中将Class.forName()所指定的类改一下就可以了,非常的方便。凡是有利就有弊,如果我们对抽象工厂中的某些定义不满意了呢,这个时候如果我们对抽象方法进行一定的调整和更改(增加或删除),那么所有实现了该抽象工厂的具体工厂的类都需要进行修改,如果有100个具体工厂,无疑是非常可怕的,因此我们应该理智的取舍,
实例
我们将类分成三个部分,这样思路更加清晰,第一部分是抽象工厂中的类,第二部分是具体实现的具体工厂类,第三部分是测试使用的Main类。
抽象工厂包
- 抽象的零件类 Item.java
capation字段表示项目的”标题”1
2
3
4
5
6
7
8
9
10
11package com.edu.tju.GOF.Abstract_Factory.factory;
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
} - 抽象的零件 Link.java
抽象地表示HTML的超链接的类1
2
3
4
5
6
7
8
9package com.edu.tju.GOF.Abstract_Factory.factory;
public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
} - 抽象的零件:Tray.java
表示一个含有多个Link类和Tray类的容器
虽然Tray类也继承了Item类的抽象方法makeHTML,但是它并没有实现该方法因此,Tray也是个抽象类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.edu.tju.GOF.Abstract_Factory.factory;
import java.util.ArrayList;
public abstract class Tray extends Item {
protected ArrayList tray = new ArrayList();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
tray.add(item);
}
} - 抽象的产品:Page.java
Page类是抽象地表示HTML页面的类.
为了强调调用的是Page类自己的makeHTML方法,我们显示地加上了this.
makeHTML是一个抽象方法.有子类实现.
output方法是一个简单的 模板模式的 方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package com.edu.tju.GOF.Abstract_Factory.factory;
import java.io.*;
import java.util.ArrayList;
public abstract class Page {
protected String title;
protected String author;
protected ArrayList content = new ArrayList();
public Page(String title, String author) {
this.title = title;
this.author = author;
}
public void add(Item item) {
content.add(item);
}
public void output() {
try {
String filename = title + ".html";
Writer writer = new FileWriter(filename);
writer.write(this.makeHTML());
writer.close();
System.out.println(filename + " 编写完成。");
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract String makeHTML();
} - 抽象的工厂:Factory.java
getFactory方法可以根据指定的类名生成具体工厂的实例.
下文会对Class类的forName方法和newInstance方法进行解释.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.edu.tju.GOF.Abstract_Factory.factory;
public abstract class Factory {
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory) Class.forName(classname).newInstance();//反射
} catch (ClassNotFoundException e) {
System.err.println("没有找到 " + classname + "类。");
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
public abstract Link createLink(String caption, String url);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title, String author);
}
- 抽象的零件类 Item.java
Main类
使用工厂将零件组装成产品 Main.java
Main类会使用factory生成Link和Tray,然后将Link和Tray都放入Tray中.最后生成Page并将生成结果输出至文件.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package com.edu.tju.GOF.Abstract_Factory;
import com.edu.tju.GOF.Abstract_Factory.factory.*;
public class Main {
public static void main(String[] args) {
// if (args.length != 1) {
// System.out.println("Usage: java Main class.name.of.ConcreteFactory");
// System.out.println("Example 1: java Main listfactory.ListFactory");
// System.out.println("Example 2: java Main tablefactory.TableFactory");
// System.exit(0);
// }
String s1 = "com.edu.tju.GOF.Abstract_Factory.listfactory.ListFactory";
String s2 = "com.edu.tju.GOF.Abstract_Factory.tablefactory.TableFactory";
Factory factory = Factory.getFactory(s1);
Link people = factory.createLink("yjn", "http://tjuyjn.top/");
Link gmw = factory.createLink("rlj", "https://r1895.github.io/");
Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/");
Link excite = factory.createLink("Excite", "http://www.excite.com/");
Link google = factory.createLink("Google", "http://www.google.com/");
Tray traynews = factory.createTray("友链");
traynews.add(people);
traynews.add(gmw);
Tray trayyahoo = factory.createTray("Yahoo!");
trayyahoo.add(us_yahoo);
trayyahoo.add(jp_yahoo);
Tray traysearch = factory.createTray("检索引擎");
traysearch.add(trayyahoo);
traysearch.add(excite);
traysearch.add(google);
Page page = factory.createPage("LinkPage", "Jason");
page.add(traynews);
page.add(traysearch);
page.output();
}
}具体工厂包
ListFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.edu.tju.GOF.Abstract_Factory.listfactory;
import com.edu.tju.GOF.Abstract_Factory.factory.*;
public class ListFactory extends Factory {
public Link createLink(String caption, String url) {
return new ListLink(caption, url);
}
public Tray createTray(String caption) {
return new ListTray(caption);
}
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}ListLink.java
1
2
3
4
5
6
7
8
9
10
11
12
13package com.edu.tju.GOF.Abstract_Factory.listfactory;
import com.edu.tju.GOF.Abstract_Factory.factory.*;
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
public String makeHTML() {
return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
}
}ListLink类和ListTray类都是Item类的子类,所以可以放心调用makeHTML方法.
ListTray.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.edu.tju.GOF.Abstract_Factory.listfactory;
import java.util.Iterator;
import com.edu.tju.GOF.Abstract_Factory.factory.*;
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<li>\n");
buffer.append(caption + "\n");
buffer.append("<ul>\n");
Iterator it = tray.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
buffer.append(item.makeHTML());
}
buffer.append("</ul>\n");
buffer.append("</li>\n");
return buffer.toString();
}
}ListPage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.edu.tju.GOF.Abstract_Factory.listfactory;
import java.util.Iterator;
import com.edu.tju.GOF.Abstract_Factory.factory.*;
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<html><head><title>" + title + "</title></head>\n");
buffer.append("<body>\n");
buffer.append("<h1>" + title + "</h1>\n");
buffer.append("<ul>\n");
Iterator it = content.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
buffer.append(item.makeHTML());
}
buffer.append("</ul>\n");
buffer.append("<hr><address>" + author + "</address>");
buffer.append("</body></html>\n");
return buffer.toString();
}
}最后效果图是这样的
可以看到完全组成了一个网页。到了这里大家可能很质疑,难道费了这么大的功夫就是为了实现这么简单的功能?!其实这里我们可以看到抽象工厂的强大之处,零件的组装与嵌套,相互关联,通过迭代器、模板方法、工厂方法等模式最终实现了这种功能,可扩展性非常之强大,如果还要生成其他种类的工厂,将非常的方便,直接写实现类就可以了,其他代码基本上不需要改动,这样的功能可以说非常强大了,至今为止我们很多的代码都是强耦合
的,很难实现复用,而这个抽象的工厂就可以实现高层次的复用,只需要知道实现类的类名就可以执行了,我们完全可以实现其他工厂,从而实现其他的功能。抽象工厂模式最重要的就是可复用性和完美的隔离性,其中使用了makeHTML()非常多次,通过迭代器来展现了这个方法的多态。灵活使用抽象工厂模式可以说是设计模式真正入门的起点。抽象工厂将抽象零件组装成抽象产品,易于增加具体的工厂难于增加新的零件。
添加其他工厂
照着上面代码的格式 仿生一个table工厂
TableFactor.java
1 | package com.edu.tju.GOF.Abstract_Factory.tablefactory; |
TableLink.java
1 | package com.edu.tju.GOF.Abstract_Factory.tablefactory; |
TablePage.java
1 | package com.edu.tju.GOF.Abstract_Factory.tablefactory; |
TableTray.java
1 | package com.edu.tju.GOF.Abstract_Factory.tablefactory; |
可以看出抽象工厂模式易于增加具体的工厂,但是增加新的零件的话,经编写完成的具体工厂越多,修改的工作量就会越大.
总结
实现方式:
- 以不同的产品类型与产品变体为维度绘制矩阵。
- 为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。
- 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。
- 为每种产品变体实现一个具体工厂类。
- 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。
- 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。
优点
- 你可以确保同一工厂生成的产品相互匹配。
- 你可以避免客户端和具体产品代码的耦合。
- 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
- 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
缺点
- 产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景
- QQ 换皮肤,一整套一起换
- 生成不同操作系统的程序。
注意事项:
AbstractProduct(抽象产品)
AbstractProduct角色负责定义AbstractFactory 角色所生成的抽象零件和产品的接口(API).在程序中,Link,Tray,Page 类扮演此角色.AbstractFactory(抽象工厂)
AbstractFactory角色负责定义用于生成产品的接口(API).在实例程序中,由Factory类扮演此角色.Client (委托者)
Client角色仅会调用AbstractFactory角色和AbstractProduct角色的接口(API)来进行工作,对于具体的零件,产品和工厂一无所知.在程序中,由Main类扮演此角色.ContreteProduct(具体产品)
ConcreteProduct角色负责实现AbstractProduct角色的接口(API).在程序中
ListLink类,ListTray类,ListPage类扮演此角色ConcreteFactory(具体工厂)
ConcreteFactory角色负责实现AbstractFactory角色的接口(API).
listfactory类扮演此角色AbstractFactory模式的类图(忽略了Client角色)
抽象工厂模式 与其他模式的关系
- 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
- 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
- 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
java反射
尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:
- 性能问题。
Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。 - 安全限制。
使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。 - 程序健壮性。
反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
熟能生巧
用抽象工厂模式设计农场类。
分析:农场中除了像畜牧场一样可以养动物,还可以培养植物,如养马、养牛、种菜、种水果等,所以本实例比前面介绍的畜牧场类复杂,必须用抽象工厂模式来实现。
本例用抽象工厂模式来设计两个农场,一个是韶关农场用于养牛和种菜,一个是上饶农场用于养马和种水果,可以在以上两个农场中定义一个生成动物的方法 newAnimal() 和一个培养植物的方法 newPlant()。
对马类、牛类、蔬菜类和水果类等具体产品类,由于要显示它们的图像(点此下载图片),所以它们的构造函数中用到了 JPanel、JLabel 和 ImageIcon 等组件,并定义一个 show() 方法来显示它们。
客户端程序通过对象生成器类 ReadXML 读取 XML 配置文件中的数据来决定养什么动物和培养什么植物(点此下载 XML 文件)。其结构图如图所示