目录
一、什么是Abstract Factory模式
二、Abstract Factory示例代码
2.1 类之间的关系
2.2 抽象的零件:ltem类
2.3 抽象的零件:Link类
2.4 抽象的零件:Tray类
2.5 抽象的产品: Page类
2.6 抽象的工厂:Factory类
2.7 使用工厂将零件组装称为产品:Main类
2.8 具体的工厂:ListFactory类
2.9 具体的零件:ListLink类
2.10 具体的零件:ListTray类
2.11 具体的产品:ListPage类
2.12 运行结果
三、拓展思路的要点
3.1 易于增加具体的工厂
3.2 难以增加新的零件
四、相关的设计模式
4.1 Builder模式
4.2 Factory Method模式
4.3 Composite模式
4.4 Singleton模式
一、什么是Abstract Factory模式
Abstract的意思是“抽象的”,Factory的意思是“工厂”。在Abstract Factory模式中,不仅有“抽象工厂”,还有“抽象零件”和“抽象产品”。抽象工厂的工作是将“抽象零件”组装为“抽象产品”。
请大家先回忆一下面向对象编程中的“抽象”这个词的具体含义。它指的是“不考虑具体怎样实现,而是仅关注接口(API )"的状态。例如,抽象方法(Abstract Method)并不定义方法的具体实现,而是仅仅只确定了方法的名字和签名(参数的类型和个数)。
关于“忘记方法的具体实现(假装忘记),使用抽象方法进行编程”的设计思想,我们在Template Method模式和 Builder模式中已经稍微提及了一些。
设计模式学习(六):Template Method模板方法模式_玉面大蛟龙的博客-CSDN博客
在Abstract Factory模式中将会出现抽象工厂,它会将抽象零件组装为抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口(API )。我们仅使用该接口(API)将零件组装成为产品。
在Tempate Method模式和Builder模式中,子类这一层负责方法的具体实现。在 AbstractFactory模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。
用一句话概况就是:将关联零件组装成产品。
二、Abstract Factory示例代码
示例程序的功能是将带有层次关系的链接的集合制作成HTML文件。最后制作完成的HTML文件如图8-1所示,在浏览器中查看到的结果如图8-2所示。
2.1 类之间的关系
类的功能:
类图:
源文件结构:
2.2 抽象的零件:ltem类
Item类是Link类和Tray类的父类( Item有“项目”的意思)。这样,Link类和Tray类就具有可替换性了。
package factory;
public abstract class Item {
//项目的“标题”
protected String caption;
public Item(String caption) {
this.caption = caption;
}
/**
* 返回HTML文件的内容(需要子类去实现)
*/
public abstract String makeHTML();
}
2.3 抽象的零件:Link类
Link类是抽象地表示HTML的超链接的类。
乍一看,在Link类中好像一个抽象方法都没有,但实际上并非如此。由于Link类中没有实现父类(Item类)的抽象方法(makeHTML),因此它也是抽象类。
package factory;
public abstract class Link extends Item {
//超链接所指向的地址
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
2.4 抽象的零件:Tray类
Tray类表示的是一个含有多个Link类和Tray类的容器(Tray有托盘的意思。请想象成在托盘上放置着一个一个项目)。
Tray类使用add方法将Link类和Tray类集合在一起。为了表示集合的对象是“Link类和Tray类”,我们设置add方法的参数为Link类和Tray类的父类Item类。
虽然Tray类也继承了Item类的抽象方法makeHTML,但它并没有实现该方法。因此,Tray类也是抽象类。
我们可以发现,tray示例是protected类型的,如果改为private类型,优点是Tray的子类(即具体的零件)不会依赖于tray字段的实现;缺点是必须重新编写一些方法,让外部可以访问自身。通常,与将字段的可见性设置为protected相比,将字段的可见性设置为private,然后编写用于访问字段的方法会更安全。
package factory;
public abstract class Tray extends Item{
//Link类和Tray类的集合
protected ArrayList tray = new ArrayList();
public Tray(String caption) {
super(caption);
}
/**
* 将Link类和Tray类集合在一起
*/
public void add(Item item) {
tray.add(item);
}
}
2.5 抽象的产品: Page类
Page类是抽象地表示HTML页面的类。如果将Link和Tray比喻成抽象的“零件”,那么Page类就是抽象的“产品”。
其中,我们可以去掉 writer.write(this.makeHTML()) 中的this,但为了强调调用的是Page类自己的makeHTML方法,我们显式地加上了this。这里调用的makeHTML方法是一个抽象方法。output方法是一个简单的Template Method模式的方法。
/**
* Page类是抽象地表示HTML页面的类。如果将Link和Tray比喻成抽象的“零件”,那么Page类就是抽象的“产品”。
*/
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;
}
/**
* 向页面中增加Item。增加的Item将会在页面中显示出来
* @param item Link或Tray
*/
public void add(Item item) {
content.add(item);
}
/**
* 首先根据页面标题确定文件名,接着调用makeHTML方法将自身保存的HTML内容写入到文件中
*/
public void output() {
try {
String filename = title + ".html";
Writer writer = new FileWriter(filename);
//为了强调调用的是Page类自己的makeHTML方法,我们显式地加上了this
writer.write(this.makeHTML());
writer.close();
System.out.println(filename + "编写完成。");
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract String makeHTML();
}
2.6 抽象的工厂:Factory类
前面我们学习了抽象零件和抽象产品的代码,现在来看看抽象工厂。
createLink、createTray、createPage等方法是用于在抽象工厂中生成零件和产品的方法。这些方法都是抽象方法,具体的实现被交给了Factory类的子类。不过,这里确定了方法的名字和签名。
/**
* 抽象工厂
*/
public abstract class Factory {
/**
* 根据指定的类名生成具体工厂的实例
* @param classname 具体工厂的类名所对应的字符串
* @return 抽象工厂类型
*/
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory) Class.forName(classname).newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} 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);
}
2.7 使用工厂将零件组装称为产品:Main类
Main类使用抽象工厂生产零件并将零件组装成产品。Main类中只引入了factory包,从这一点可以看出,该类并没有使用任何具体零件、产品和工厂。
具体工厂的类名是通过命令行来指定的。例如,如果要使用listfactory包中的ListFactory类,可以在命令行中输入以下命令:
java Main listfactory.ListFactory
Main类会使用getFactory方法生成该参数( arg [0] )对应的工厂,并将其保存在factory变量中。
之后,Main类会使用factory生成Link 和 Tray,然后将Link和Tray都放入Tray中,最后生成Page并将生成结果输出至文件。
import 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);
}
Factory factory = Factory.getFactory(args[0]);
Link people = factory.createLink("人民日报", "http://www.prople.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.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", "哈哈哈");
page.add(traynews);
page.add(traysearch);
page.output();
}
}
2.8 具体的工厂:ListFactory类
之前我们学习了抽象类的代码,现在让我们将视角切换到具体类。首先,我们来看看listfactory包中的工厂———ListFactory类。
ListFactory类实现了Factory类的createLink方法、createTray方法以及createPage方法。当然,各个方法内部只是分别简单地new出了ListLink类的实例、ListTray类的实例以及ListPage类的实例(根据实际需求,这里可能需要用Prototype模式来进行clone )。
/**
* 具体工厂
*/
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);
}
}
2.9 具体的零件:ListLink类
ListLink类是Link类的子类。在ListLink类中必须实现的方法是在父类中声明的makeHTML抽象方法。ListLink类使用<li>标签和<a>标签来制作HTML片段。这段HTML片段也可以与ListTary和ListPag的结果合并起来,就如同将螺栓和螺母拧在一起一样。
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
@Override
public String makeHTML() {
return "<li><a href=\"" + url + "\">" + caption + "</a></li>\n";
}
}
2.10 具体的零件:ListTray类
ListTray类是Tray类的子类。这里我们重点看一下makeHTML方法是如何实现的。tray字段中保存了所有需要以HTML格式输出的Item,而负责将它们以HTML格式输出的就是makeHTML方法了。
请注意,buffer.append(item.makeHTML()) 这里并不关心变量item中保存的实例究竟是ListLink的实例还是ListTray的实例,只是简单地调用了item.makeHTML ()语句而已。这里不能使用switch语句或if语句去判断变量item中保存的实例的类型,否则就是非面向对象编程了。变量item是Item类型的,而Item类又声明了makeHTML方法,而且ListLink类和ListTray类都是Item类的子类,因此可以放心地调用。之后item会帮我们进行处理。至于item究竟进行了什么样的处理,只有item的实例(对象)才知道。这就是面向对象的优点。
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
//使用<li>标签输出标题( caption )
buffer.append("<li>\n");
buffer.append(caption + "\n");
buffer.append("<ul>\n");
//使用<ul>和<li>标签输出每个Item
Iterator it = tray.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
//输出为HTML格式
buffer.append(item.makeHTML());
}
buffer.append("</ul>\n");
buffer.append("</li>\n");
return buffer.toString();
}
}
2.11 具体的产品:ListPage类
ListPage类是Page类的子类。
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
@Override
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<html><head><title>" + title + "</title></head>\n");
buffer.append("<body>\n");
buffer.append("<hl>" + title + "</hl>\n");
buffer.append("<ul>\n");
//content继承自Page类的字段
Iterator it = content.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
buffer.append(item.makeHTML());
}
buffer.append("</ul>\n");
//作者名(author)用<address>标签输出
buffer.append("<hr><address>" + author + "</address>");
buffer.append("</body></html>\n");
return buffer.toString();
}
}
2.12 运行结果
编译和运行方法如下:
javac Main.java listfactory/ListFactory.java
java Main listfactory.ListFactory
编写出的html文件:
三、拓展思路的要点
3.1 易于增加具体的工厂
在Abstract Factory模式中增加具体的工厂是非常容易的。这里说的“容易”指的是需要编写哪些类和需要实现哪些方法都非常清楚。
假设现在我们要在示例程序中增加新的具体工厂,那么需要做的就是编写Factory、Link、Tray、Page这4个类的子类,并实现它们定义的抽象方法。也就是说将factory包中的抽象部分全部具体化即可。
这样一来,无论要增加多少个具体工厂(或是要修改具体工厂的Bug ),都无需修改抽象工厂和Main部分。
3.2 难以增加新的零件
请试想一下要在Abstract Factory模式中增加新的零件时应当如何做。例如,我们要在factory包中增加一个表示图像的Picture零件。这时,我们必须要对所有的具体工厂进行相应的修改才行。例如,在listfactory包中,我们必须要在ListFactory中加入createPicture方法、新增ListPicture类。
已经编写完成的具体工厂越多,修改的工作量就会越大。
四、相关的设计模式
4.1 Builder模式
Abstract Factory模式通过调用抽象产品的接口(API )来组装抽象产品,生成具有复杂结构的实例。Builder模式则是分阶段地制作复杂实例。
4.2 Factory Method模式
有时Abstract Factory模式中零件和产品的生成会使用到Factory Method模式。
设计模式学习(七):Factory Method工厂模式_玉面大蛟龙的博客-CSDN博客
4.3 Composite模式
有时Abstract Factory模式在制作产品时会使用Composite模式。
4.4 Singleton模式
有时Abstract Factory模式中的具体工厂会使用Singleton模式。