一、什么是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 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.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模式。