通常,我们不会将 “抽象的” 这个词与 “工厂” 这个词联系到一起。所谓工厂,就是将零件组装成产品的地方,这是一项具体的工作。大在 Abstract Factory 模式中,不仅有 “抽象工厂” ,还有 “抽象零件” 和 “抽象产品” 。抽象工厂的工作是将 “抽象零件” 组装为 “抽象产品”。
在面向对象的编程中 “抽象” 这个词的具体含义指的是 “不考虑具体怎样实现,而是仅关注接口(API)” 的状态。例如,抽象方法并不定义方法的具体实现,而是仅只确定了方法的名字和签名(参数的类型和个数)。
在 Abstract Factory 模式中将会出现抽象工厂,并不关心零件的具体实现,而是只关心接口(API)。我们仅使用该接口(API)将零件组装成为产品。
在 Template Method 模式和 Builder 模式中,子类这个一层负责方法的具体实现。在 Abstract Factory 模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。
功能:将带有层次关系的链接的集合制作成 HTML 文件。
在示例程序中,类被划分为以下3个包。
Item 类是 Link 类和 Tray 类的父类,这样 Link 类和 Tray 类就有了可替换性。
caption 字段表示项目的 “标题”。
makeHTML 方法是抽象方法,需要子类来实现这个方法。该方法返回 HTML 文件的内容。
/**
* 抽象的零件 Item 类
*/
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
Link 类是抽象地表示 HTML 的超链接的类。
因为其继承了 Item 类,且未实现 makeHTML 方法,所以其也是一个抽象类。
/**
* 抽象零件:Link 类
*/
public abstract class Link extends Item{
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
Tray 类表示的是一个含有多个 Link 类和 Tray 类的容器(Tray 有托盘的意思)。
Tray 类使用 add 方法jiang Link 类和 Tray 类集合在一起。为了表示集合的对象是 “Link类和 Tray类”,我们设置add 方法的参数是两者的父类 Item。
同理,也是一个抽象类。
/**
* 抽象零件:Tray
*/
public abstract class Tray extends Item{
protected List- trays = new ArrayList<>();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
trays.add(item);
}
}
Page 类是抽象地表示 HTML 页面的类。如果将 Link 和 Tray 比喻成抽象的零件,那么 Page 类就是抽象地产品。
可以使用 add 方法想页面中增加 Item(即 Link 或 Tray)。增加的 Item 将会在页面中显示出来。
output 方法首先根据页面标题确定文件名,接着调用 makeHTML 方法将自身保存的 HTML 内容写入到文件中。
public abstract class Page {
protected String title;
protected String author;
protected List- 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";
FileWriter writer = new FileWriter(fileName);
writer.write(makeHTML()); // 这里是子类的方法
writer.close();
System.out.println(fileName + " 编写完成。");
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract String makeHTML();
}
getFactory 方法根据指定的类名生成具体工厂的实例(反射原理)。通过调用 Class 类的 forName 方法来动态地读取类信息,接着使用 newInstance 方法生成该类的实例,并将其作为返回值返回给调用者。
虽然 getFactory 方法生成的是具体的工厂实例,但是返回值的类型是抽象工厂类型。
其他三个方法用于在抽象工厂中生成零件和产品的方法。这些方法都是抽象方法,具体的实现交给了 Factory 类的子类,不过,这里确定了方法的名字和签名。
/**
* 抽象工厂:Factory
*/
public abstract class Factory {
public static Factory getFactory(String className) {
Factory factory = null;
try {
factory = (Factory)Class.forName(className).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
System.out.println("没有找到 " + className + " 类。");
}
return factory;
}
public abstract Link createLink(String caption, String url);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title, String author);
}
Main 类的代码使用抽象工厂生产零件并将零件组装成产品。Main 类中只引入了 factory 包,该类并没有使用任何具体零件、产品和工厂。
Main 类会使用 getFactory 方法生成该参数(arg[0])对应的工厂,并将其保存在 factory变量中。
之后,Main 类会使用 factory 生成 Link 和 Tray,然后将 Link 和 Tray 都放入 Tray 中,最后生成 Page 并将生成的结果输出至文件。
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);
}
Factory factory = Factory.getFactory(args[0]);
Link people = factory.createLink("人民日报", "http://www.people.com.cn");
Link gmw = factory.createLink("光明日报", "http://www.gmw.cn/");
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", "mingchenxu");
page.add(trayNews);
page.add(traySearch);
page.output();
}
}
ListFactory 类实现了 Factory 类的 createLink 方法、createTray 方法以及 createPage 方法。各个方法内部只是分别简单地 new 出了各自的实例(根据实际情况,可能需要 Prototype 模式来进行 clone)。
/**
* 具体的工厂:ListFactory
*/
public class ListFactory extends Factory {
@Override
public Link createLink(String caption, String url) {
return new ListLink(caption, url);
}
@Override
public Tray createTray(String caption) {
return new ListTray(caption);
}
@Override
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}
继承了 Link 类,实现了 makeHTML 抽象方法。使用
/**
* 具体的零件:ListLink
*/
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
@Override
public String makeHTML() {
return "" + caption + " \n";
}
}
ListTray类是 Tray 类的子类。tray 字段中保存了所有需要以 HTML 格式输出的 Item,而负责将它们以 HTML 格式输出的就是 makeHTML 方法了。
makeHTML 方法首先使用
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuilder builder = new StringBuilder();
builder.append("- \n");
builder.append(caption).append("\n");
builder.append("
\n");
trays.forEach(tray -> builder.append(tray.makeHTML()));
builder.append("
\n");
builder.append(" \n");
return builder.toString();
}
}
ListPage 类是 Page 类的子类。ListPage 将字段中保存的内容输出为 HTML 格式。其中遍历语句包裹在
/**
* 具体的产品:ListPage
*/
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
@Override
public String makeHTML() {
StringBuilder builder = new StringBuilder();
builder.append("").append(title).append(" \n");
builder.append("\n");
builder.append("").append(title).append("
\n");
content.forEach(e -> builder.append(e.makeHTML()));
builder.append("
运行main方法,输入结果:
以示例程序为例,如果只是为了编写带有 HTML 超链接集合的文件,那我们的阵势未免有些过大了。当只有一个具体工厂的时候,是完全没有必要划分 “抽象类” 与 “具体类” 的。下面,我们将在示例程序的几次上再增加其他的具体工厂。
现在我们来使用 tablefactory 将链接以表格形式展示出来。
public class TableFactory extends Factory {
@Override
public Link createLink(String caption, String url) {
return new TableLink(caption, url);
}
@Override
public Tray createTray(String caption) {
return new TableTray(caption);
}
@Override
public Page createPage(String title, String author) {
return new TablePage(title, author);
}
}
public class TableLink extends Link {
public TableLink(String caption, String url) {
super(caption, url);
}
@Override
public String makeHTML() {
return "" + caption + " ";
}
}
public class TableTray extends Tray {
public TableTray(String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuilder builder = new StringBuilder();
builder.append("")
.append("")
.append("")
.append(caption).append(" ")
.append(" \n");
builder.append("\n");
trays.forEach(e -> builder.append(e.makeHTML()));
builder.append("
")
.append(" ");
return builder.toString();
}
}
public class TablePage extends Page {
public TablePage(String title, String author) {
super(title, author);
}
@Override
public String makeHTML() {
StringBuilder builder = new StringBuilder();
builder.append("")
.append(title)
.append(" \n");
builder.append("\n")
.append("").append(title).append("
\n");
builder.append("\n");
content.forEach(e -> builder.append("").append(e.makeHTML()).append(" "));
builder.append("
\n");
builder.append("
").append(author).append("");
builder.append("\n");
return builder.toString();
}
}
运行类与示例程序的 main 类一致,更换 arg[0] 参数即可,运行结果如下:
在 Abstract Factory 模式中有以下登场角色。
在 Abstract Factory 模式中增加具体的工厂是非常容易的。要编写哪些类和需要实现哪些方法都非常清楚。
假设我们要在示例程序中增加新的具体工厂,那么需要做的就是编写 Factory、Link、Tray、Page 这4个类的子类,并实现它们定义的抽象方法。也就是说将 factory 包中的抽象部分全部具体化即可。无论要增加多少个具体工厂,都无需修改抽象工厂和 Main 部分。
试想一下在 Abstract Factory 模式中增加新的零件时应当如何做。例如,在示例程序中,我们要在 factory 包中增加一个表示图像的 Picture 零件。这时,我们必须要对所有的具体工厂进行相应的修改才行。例如,在 listfactory 包中,我们必须做以下修改。
已经编写完成的具体工厂越多,修改的工作量就会越大。