设计模式:Abstract Factory模式

抽象工厂模式,通过抽象工厂将抽象零件组装成抽象产品;不用关心零件的具体实现,而是只关心接口,使用这些接口将零件组装成产品

与之前Factory,Template Method以及Builder模式一样,具体方法的实现在子类中,但不同的是,抽象工厂模式中,子类也有一个具体的工厂,负责将具体的零件组装成具体产品

来一个具体的案例,将带有层次关系的链接制作成HTML文件

最终浏览器显示如下:

LinkPage


李辉

对应的HTML源码如下

<html><head><title>LinkPage</title></head>
<body>
<h1>LinkPage</h1>
<ul>
<li>
门户
<ul>
<li><a href="http://www.sina.com/"> 新浪 </a></li>
<li><a href="http://www.sohu.com/"> 搜狐 </a></li>
</ul>
</li>
<li>
搜索引擎
<ul>
<li>
Google
<ul>
<li><a href="http://www.google.com/">Google</a></li>
<li><a href="http://www.google.cn/">Google.CN</a></li>
</ul>
</li>
<li><a href="http://www.baidu.com/">BaiDu</a></li>
<li><a href="http://www.sogou.com/">Sogou</a></li>
</ul>
</li>
</ul>
<hr><address> 李辉 </address></body></html>

该程序可以划分为3个包:

abstractfactory.factory包:包含抽象工厂,抽象零件,抽象产品的包

abstractfactory.listfactory包:包含具体工厂,具体零件,具体产品的包,这里代表以HTML的<ul>标签输出

具体的类如下:

Factory类:factory包,表示抽象工厂的类,制作Link,Tray,Page

Item类:factory包,用来统一处理Link和Tray的类

Link类:factory包,表示HTML连接的类,是抽象零件

Tray类:factory包,表示含有Link和Tray的类,是抽象零件

Page类:factory包,表示HTML页面的类,是抽象零件

Main类:main,测试

ListFactory类:listfactory包,表示具体工厂的类,制作ListLink,ListTray,ListPage

ListLink类:listfactory包,表示HTML链接的类,是具体零件

ListTray类:listfactory包,表示含有Link和Tray的类,是具体零件

ListPage类:listfactory包,表示HTML页面的类,是具体零件

抽象零件,Item类

Item类是Link类和Tray类的父类,相当于项目类,caption字段表示项目的标题,makeHTML方法是抽象方法,返回HTML文件的内容

package abstractfactory.factory;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Item
* Author: lihui
* Date: 2018/5/4 23:00
*/

public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}

抽象零件,Link类

Link类是HTML超链接的抽象类,url字段保存超链接指向的地址

package abstractfactory.factory;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Link
* Author: lihui
* Date: 2018/5/4 23:00
*/

public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}

抽象零件,Tray类

Tray类表示一个含有多个Link类和Tray类的容器,可以想象托盘上放置着一个一个的项目

Tray类使用add方法将Link类和Tray类集合在一起,设置add方法的参数为Link类和Tray类的父类Item类;Tray类没有重写了Item类的抽象方法,也是一个抽象类

package abstractfactory.factory;

import java.util.ArrayList;
import java.util.List;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Tray
* Author: lihui
* Date: 2018/5/4 23:00
*/

public abstract class Tray extends Item {
protected List tray = new ArrayList();
public Tray(String caption) {
super(caption);
}

public void add(Item item) {
tray.add(item);
}
}

抽象产品,Page类

Page类是表示HTML页面的抽象类,相当于Link和Tray是抽象零件,Page就是抽象产品,title和author就是页面的标题和作者

可以使用add方法从页面中添加Item(也就是Link或者Tray),然后再页面上显示出来

output方法根据页面的标题来确定文件名,接着调用makeHTML方法将自身保存的HTML内容写入到文件中

package abstractfactory.factory;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Page
* Author: lihui
* Date: 2018/5/4 23:01
*/

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";
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类

getFactory方法根据指定的类名生成具体工厂的实例;通过调用Class类的forName方法动态读取类信息,使用newInstance方法来生成该类的实例,并将它作为返回值返回给调用者

Class类属于java.lang包,用来表示类的类;Class类包含在Java标准库中,forName是java.lang.Class的静态方法,类方法,newInstance是java.lang.Class实例方法

有一点,getFactory方法生成的是具体工厂的实例,但是返回值的类型却是抽象工厂类型

createLink,createTray,createPage方法都是在抽象工厂中生成零件和产品的抽象方法,而具体实现都交给了Factory的子类,但是这里确定了方法的名字和签名

package abstractfactory.factory;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Factory
* Author: lihui
* Date: 2018/5/4 23:00
*/

public abstract class Factory {
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory)Class.forName(classname).newInstance();
} catch (ClassNotFoundException e) {
System.out.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);
}

Main类,使用工厂将零件组装成为产品

package abstractfactory;

import abstractfactory.factory.Factory;
import abstractfactory.factory.Link;
import abstractfactory.factory.Page;
import abstractfactory.factory.Tray;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Main
* Author: lihui
* Date: 2018/5/4 22:59
*/

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.sina.com/");
Link gmw = factory.createLink(" 搜狐 ", "http://www.sohu.com/");

Link us_yahoo = factory.createLink("Google", "http://www.google.com/");
Link jp_yahoo = factory.createLink("Google.CN", "http://www.google.cn/");
Link excite = factory.createLink("BaiDu", "http://www.baidu.com/");
Link google = factory.createLink("Sogou", "http://www.sogou.com/");

Tray traynews = factory.createTray(" 门户 ");
traynews.add(people);
traynews.add(gmw);

Tray trayyahoo = factory.createTray("Google");
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", " 李辉 ");
page.add(traynews);
page.add(traysearch);
page.output();
}
}

这里只导入了factory包,并没有使用任何具体零件,产品和工厂,而具体工厂的类名要通过命令行参数来指定

具体源文件目录格式

lihui@MacBook  ~/IdeaProjects/cloud/src/main/java/abstractfactory  tree -F -L 2
.
├── Main.java
├── factory/
│   ├── Factory.java
│   ├── Item.java
│   ├── Link.java
│   ├── Page.java
│   └── Tray.java
├── listfactory/
│   ├── ListFactory.java
│   ├── ListLink.java
│   ├── ListPage.java
│   └── ListTray.java

由于Main类只使用了factory包,没有使用listfactory包;Main.java编译的时候,只有抽象类几个java文件会被编译,但是几个具体类的java文件并不会被编译,因此需要在编译的时候加上参数来保证具体类文件也能被编译,比如IDEA里,对比Main的路径

abstractfactory.listfactory.ListFactory

Main类使用getFactory方法生成arg[0]参数对应的工厂,并保存在factory变量中;然后Main类会使用factory生成Link和Tray,将Link和Tray都放入Tray中,最后生成Page并将生成结果输出至文件

具体工厂,ListFactory类

ListFactory类实现了Factory类的createLink,createTray,createPage方法,每个方法的实现new出了ListLink类,ListTray类,ListPage类的实例

package abstractfactory.listfactory;

import abstractfactory.factory.Factory;
import abstractfactory.factory.Link;
import abstractfactory.factory.Page;
import abstractfactory.factory.Tray;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: ListFactory
* Author: lihui
* Date: 2018/5/4 23:01
*/

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类

ListLink类是Link类的子类,实现了超类Link里的makeHTML抽象方法,ListLink使用<li>标签和<a>标签来制作HTML片段,这段HTML片段也可以和ListTray和ListPage的结果合并起来

package abstractfactory.listfactory;

import abstractfactory.factory.Link;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: ListLink
* Author: lihui
* Date: 2018/5/4 23:01
*/

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";
}
}

具体零件,ListTray类

ListTray类是Tray的子类,makeHTML方法的实现中,tray字段保存了所有需要以HTML格式输出的Item,而负责将它们以HTML格式输出的就是makeHTML方法了

makeHTML方法首先用<li>标签输出标题caption,接着使用<ul>和<li>标签输出每个Item,输出的结果先暂时保存在StringBuffer中,最后再通过toString方法将输出结果转换为String类型并返回给调用者

面向对象理解:

每个Item输出HTML格式通过调用每个Item的makeHTML方法,这里并不关心变量item中保存的实例是ListLink还是ListTray的实例,只是简单地调用了item.makeHTML();这里不能使用switch或者if去判断变量item中保存的实例的类型,否则就是非对象编程了;变量item是Item类型的,而Item类又声明了makeHTML方法,而且ListLink类和ListTray类都是Item泪的子类,可以放心调用,之后item会帮忙处理,至于item究竟进行了什么样的处理,只有item的实例对象才知道,这就是面向对象的优点

package abstractfactory.listfactory;

import abstractfactory.factory.Item;
import abstractfactory.factory.Tray;

import java.util.Iterator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: ListTray
* Author: lihui
* Date: 2018/5/4 23:01
*/

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类

ListPage类是Page类的子类,将字段中保存的内容输出为HTML格式,author用<address>标签输出

while语句放在<ul></ul>之间,是因为在while语句中append的item.makeHTML()的输出结果需要被嵌入在<ul></ul>之间

package abstractfactory.listfactory;

import abstractfactory.factory.Item;
import abstractfactory.factory.Page;

import java.util.Iterator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: ListPage
* Author: lihui
* Date: 2018/5/4 23:01
*/

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();
}
}

这样就OK了

 

假如需要增加其他的工厂

上面listfactory包功能以超链接条目显示,现在新增一种以tablefactory将超链接以表格的形式展示,如下

LinkPage

门户 
新浪  搜狐 
搜索引擎 
Google
Google Google.CN
BaiDu Sogou

李辉

对应的HTML源码如下

<html><head><title>LinkPage</title></head>
<body>
<h1>LinkPage</h1>
<table width="80%" border="3">
<tr><td><table width="100%" border="1"><tr><td bgcolor="#cccccc" align="center" colspan="2"><b> 门户 </b></td></tr>
<tr>
<td><a href="http://www.sina.com/"> 新浪 </a></td>
<td><a href="http://www.sohu.com/"> 搜狐 </a></td>
</tr></table></td></tr><tr><td><table width="100%" border="1"><tr><td bgcolor="#cccccc" align="center" colspan="3"><b> 搜索引擎 </b></td></tr>
<tr>
<td><table width="100%" border="1"><tr><td bgcolor="#cccccc" align="center" colspan="2"><b>Google</b></td></tr>
<tr>
<td><a href="http://www.google.com/">Google</a></td>
<td><a href="http://www.google.cn/">Google.CN</a></td>
</tr></table></td><td><a href="http://www.baidu.com/">BaiDu</a></td>
<td><a href="http://www.sogou.com/">Sogou</a></td>
</tr></table></td></tr></table>
<hr><address> 李辉 </address></body></html>

同时命令行参数

abstractfactory.tablefactory.TableFactory

需要新增一个包,tablefactory,对应的类

TableFactory类:具体工厂的类,制作TableLink,TableTray,TablePage

TableLink类:具体零件的类,表示HTML的超链接的类

TableTray类:具体零件的类,表示含有Link和Tray的类

TablePage类:具体产品的类,表示HTML页面的类

具体工厂,TableFactory类

Factory类的子类,createLink,createTray以及createPage方法分别生成TableLink,TableTray,TablePage的实例

package abstractfactory.tablefactory;

import abstractfactory.factory.Factory;
import abstractfactory.factory.Link;
import abstractfactory.factory.Page;
import abstractfactory.factory.Tray;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: TableFactory
* Author: lihui
* Date: 2018/5/4 23:55
*/

public class TableFactory extends Factory {
public Link createLink(String caption, String url) {
return new TableLink(caption, url);
}

public Tray createTray(String caption) {
return new TableTray(caption);
}

public Page createPage(String title, String author) {
return new TablePage(title, author);
}
}

具体零件,TableLink类

package abstractfactory.tablefactory;

import abstractfactory.factory.Link;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: TableLink
* Author: lihui
* Date: 2018/5/4 23:55
*/

public class TableLink extends Link {
public TableLink(String caption, String url) {
super(caption, url);
}

public String makeHTML() {
return "<td><a href=\"" + url + "\">" + caption + "</a></td>\n";
}
}

具体零件,TableTray类,makeHTML使用<td>和<table>标签输出Item

package abstractfactory.tablefactory;

import abstractfactory.factory.Item;
import abstractfactory.factory.Tray;

import java.util.Iterator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: TableTray
* Author: lihui
* Date: 2018/5/4 23:55
*/

public class TableTray extends Tray {
public TableTray(String caption) {
super(caption);
}

public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<td>");
buffer.append("<table width=\"100%\" border=\"1\"><tr>");
buffer.append("<td bgcolor=\"#cccccc\" align=\"center\" colspan=\"" + tray.size() + "\"><b>" + caption + "</b></td>");
buffer.append("</tr>\n");
buffer.append("<tr>\n");
Iterator it = tray.iterator();
while (it.hasNext()) {
Item item = (Item)it.next();
buffer.append(item.makeHTML());
}
buffer.append("</tr></table>");
buffer.append("</td>");
return buffer.toString();
}
}

具体产品,TablePage类

package abstractfactory.tablefactory;

import abstractfactory.factory.Item;
import abstractfactory.factory.Page;

import java.util.Iterator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: TablePage
* Author: lihui
* Date: 2018/5/4 23:55
*/

public class TablePage extends Page {
public TablePage(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("<table width=\"80%\" border=\"3\">\n");
Iterator it = content.iterator();
while (it.hasNext()) {
Item item = (Item)it.next();
buffer.append("<tr>" + item.makeHTML() + "</tr>");
}
buffer.append("</table>\n");
buffer.append("<hr><address>" + author + "</address>");
buffer.append("</body></html>\n");
return buffer.toString();
}
}

 

抽象工厂模式的角色:

 AbstractProduct:抽象产品,例子中的Link,Tray,Page类;负责定义AbstractFactory角色所生成的抽象零件和产品的接口

AbstractFactory:抽象工厂,例子中的Factory类;负责定义用于生成抽象产品的接口

Client:委托者,例子中的Main类;调用AbstractFactory角色和AbstractProduct角色的接口进行工作,对于具体零件,产品和工厂一无所知

ConcreteProduct:具体产品,例子中的ListLink,ListTray,ListPage,TableLink,TableTray,TablePage类;负责实现AbstractProduct角色的接口

ConcreteFactory:具体工厂,例子中的ListFactory和TableFactory类;负责实现AbstractFactory角色的接口

思考:

易于新增具体工厂

如果要新增新的具体工厂,那么就需要编写Factory,Link,Tray,Page这四个超类的子类,并且要实现它们的抽象方法,也就是将factory包里抽象的部分全部具体化就行了,因此无论需要增加多少个具体工厂,或者修改,都无需要修改抽象工厂和Main部分

难于新增新的零件

如果要在AbstractFactory模式新增加新的零件,比如factory包里增加一个表示图像的Picture零件,这时候必须要对所有的具体工厂进行修改才行,比如listfactory包中,修改

在ListFactory中假如createPicture方法

新增ListPicture类

具体工厂越多,需要修改的越麻烦

发表回复