设计模式(八)Abstract Factory模式
抽象工厂的工作是将“抽象零件”组装为“抽象产品”。在抽象工厂模式中将会出现抽象工厂,它会将抽象零件组装为抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口。我们仅适用该接口将零件组装起来成为产品。
示例程序的功能是将带有层次关系的链接的集合制作成HTML文件。
1 package bigjunoba.bjtu.factory; 2 3 public abstract class Item { 4 protected String caption; 5 6 public Item(String caption) { 7 this.caption = caption; 8 } 9 10 public abstract String makeHTML(); 11 }
Item类是Link类和Tray类的父类。这样,Link类和Tray类就具有可替换性了。caption字段表示项目的“标题”。
1 package bigjunoba.bjtu.factory; 2 3 public abstract class Link extends Item { 4 protected String url; 5 6 public Link(String caption, String url) { 7 super(caption); 8 this.url = url; 9 } 10 }
Link类是抽象地表示HTML的超链接的类。url字段中保存的是超链接所指向的地址。由于Link类中没有实现父类的抽象方法makeHTML,因此它也是抽象类。
1 package bigjunoba.bjtu.factory; 2 import java.util.ArrayList; 3 4 public abstract class Tray extends Item { 5 protected ArrayList<Item> tray = new ArrayList<Item>(); 6 public Tray(String caption) { 7 super(caption); 8 } 9 public void add(Item item) { 10 tray.add(item); 11 } 12 }
Tray类表示的是一个含有多个Link类和Tray类的容器。add方法将Link类和Tray类集合在一起。同理,也没有实现父类的抽象方法makeHTML,因此它也是抽象类。
1 package bigjunoba.bjtu.factory; 2 3 import java.io.*; 4 import java.util.ArrayList; 5 6 public abstract class Page { 7 protected String title; 8 protected String author; 9 protected ArrayList<Item> content = new ArrayList<Item>(); 10 11 public Page(String title, String author) { 12 this.title = title; 13 this.author = author; 14 } 15 16 public void add(Item item) { 17 content.add(item); 18 } 19 20 public void output() { 21 try { 22 String filename = title + ".html"; 23 Writer writer = new FileWriter(filename); 24 writer.write(this.makeHTML()); 25 writer.close(); 26 System.out.println(filename + " 编写完成。"); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 32 public abstract String makeHTML(); 33 }
Page是抽象地表示HTML页面的类。如果将Link和Tray比喻成抽象的“零件”,那么Page类就是抽象的“产品”。使用add方法向页面中增加Item,output方法首选根据页面标题确定文件名,接着调用makeHTML方法(为了强调调用的是自己的makeHTML方法,直接显式地加上了this)将自身保存的HTML内容写入到文件中。
1 package bigjunoba.bjtu.factory; 2 3 public abstract class Factory { 4 public static Factory getFactory(String classname) { 5 Factory factory = null; 6 try { 7 factory = (Factory)Class.forName(classname).newInstance(); 8 } catch (ClassNotFoundException e) { 9 System.err.println("没有找到 " + classname + "类。"); 10 } catch (Exception e) { 11 e.printStackTrace(); 12 } 13 return factory; 14 } 15 public abstract Link createLink(String caption, String url); 16 public abstract Tray createTray(String caption); 17 public abstract Page createPage(String title, String author); 18 }
Factory类中的getFactory方法,可以根据指定的类名生成具体工厂的实例。getFactory方法通过调用Class类的forName方法来动态地读取类信息,接着使用newInstance方法生成该类的实例,并将其作为返回值返回给调用者。
1 package bigjunoba.bjtu.test; 2 3 import bigjunoba.bjtu.factory.*; 4 5 public class Main { 6 public static void main(String[] args) { 7 if (args.length != 1) { 8 System.out.println("Usage: java Main class.name.of.ConcreteFactory"); 9 System.out.println("Example 1: java Main bigjunoba.bjtu.listfactory.ListFactory"); 10 System.out.println("Example 2: java Main bigjunoba.bjtu.tablefactory.TableFactory"); 11 System.exit(0); 12 } 13 Factory factory = Factory.getFactory(args[0]); 14 15 Link people = factory.createLink("人民日报", "http://www.people.com.cn/"); 16 Link gmw = factory.createLink("光明日报", "http://www.gmw.cn/"); 17 18 Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/"); 19 Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/"); 20 Link excite = factory.createLink("Excite", "http://www.excite.com/"); 21 Link google = factory.createLink("Google", "http://www.google.com/"); 22 23 Tray traynews = factory.createTray("日报"); 24 traynews.add(people); 25 traynews.add(gmw); 26 27 Tray trayyahoo = factory.createTray("Yahoo!"); 28 trayyahoo.add(us_yahoo); 29 trayyahoo.add(jp_yahoo); 30 31 Tray traysearch = factory.createTray("检索引擎"); 32 traysearch.add(trayyahoo); 33 traysearch.add(excite); 34 traysearch.add(google); 35 36 Page page = factory.createPage("LinkPage", "杨文轩"); 37 page.add(traynews); 38 page.add(traysearch); 39 page.output(); 40 } 41 }
Main类使用抽象工厂生产零件并将零件组装成产品。由于Main类只引入了一个包,所以可以看出,该类并没有使用任何具体零件、产品和工厂。
1 package bigjunoba.bjtu.listfactory; 2 3 import bigjunoba.bjtu.factory.*; 4 5 public class ListFactory extends Factory { 6 public Link createLink(String caption, String url) { 7 return new ListLink(caption, url); 8 } 9 10 public Tray createTray(String caption) { 11 return new ListTray(caption); 12 } 13 14 public Page createPage(String title, String author) { 15 return new ListPage(title, author); 16 } 17 }
ListFactory类只是简单地new出了ListLink、ListTray和ListPage的实例。
1 package bigjunoba.bjtu.listfactory; 2 3 import bigjunoba.bjtu.factory.*;; 4 5 public class ListLink extends Link { 6 public ListLink(String caption, String url) { 7 super(caption, url); 8 } 9 10 public String makeHTML() { 11 return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n"; 12 } 13 }
ListLink类使用<li>和<a>标签来制作HTML片段。
1 package bigjunoba.bjtu.listfactory; 2 3 import bigjunoba.bjtu.factory.*; 4 import java.util.Iterator; 5 6 public class ListTray extends Tray { 7 public ListTray(String caption) { 8 super(caption); 9 } 10 11 public String makeHTML() { 12 StringBuffer buffer = new StringBuffer(); 13 buffer.append("<li>\n"); 14 buffer.append(caption + "\n"); 15 buffer.append("<ul>\n"); 16 Iterator<Item> it = tray.iterator(); 17 while (it.hasNext()) { 18 Item item = (Item) it.next(); 19 buffer.append(item.makeHTML()); 20 } 21 buffer.append("</ul>\n"); 22 buffer.append("</li>\n"); 23 return buffer.toString(); 24 } 25 }
ListTray类中tray字段保存了所有需要以HTML格式输出的Item,而负责将它们以HTML格式输出的就是makeHTML方法。makeHTML方法首先使用<li>标签输出标题,接着使用</ul>和</li>标签输出每个Item。
每个Item输出为HTML格式的方法就是调用每个Item的makeHTML方法了。这里并不关心变量item中保存的实例的类型究竟是ListLink还是ListTray,只是简单地调用了item.makeHTML()而已。变量item是Item类型的,而Item类又声明了makeHTML方法,而且ListLink类和ListTray类都是Item类的子类,因此可以放心调用。这就是面向对象的优点。
1 package bigjunoba.bjtu.listfactory; 2 3 import bigjunoba.bjtu.factory.*; 4 import java.util.Iterator; 5 6 public class ListPage extends Page { 7 public ListPage(String title, String author) { 8 super(title, author); 9 } 10 11 public String makeHTML() { 12 StringBuffer buffer = new StringBuffer(); 13 buffer.append("<html><head><title>" + title + "</title></head>\n"); 14 buffer.append("<body>\n"); 15 buffer.append("<h1>" + title + "</h1>\n"); 16 buffer.append("<ul>\n"); 17 Iterator<Item> it = content.iterator(); 18 while (it.hasNext()) { 19 Item item = (Item) it.next(); 20 buffer.append(item.makeHTML()); 21 } 22 buffer.append("</ul>\n"); 23 buffer.append("<hr><address>" + author + "</address>"); 24 buffer.append("</body></html>\n"); 25 return buffer.toString(); 26 } 27 }
ListPage类和ListTray类差不多。
当只有一个具体工厂的时候,是完全没有必要划分“抽象类”与“具体类”的。还要增加一个tablefactoty将链接以表格形式展示出来。
代码结构如上图,这里就不写tablefactory部分的代码了。
<html><head><title>LinkPage</title></head> <body> <h1>LinkPage</h1> <ul> <li> 日报 <ul> <li><a href="http://www.people.com.cn/">人民日报</a></li> <li><a href="http://www.gmw.cn/">光明日报</a></li> </ul> </li> <li> 检索引擎 <ul> <li> Yahoo! <ul> <li><a href="http://www.yahoo.com/">Yahoo!</a></li> <li><a href="http://www.yahoo.co.jp/">Yahoo!Japan</a></li> </ul> </li> <li><a href="http://www.excite.com/">Excite</a></li> <li><a href="http://www.google.com/">Google</a></li> </ul> </li> </ul> <hr><address>杨文轩</address></body></html>
执行javac Main.java bigjunoba.bjtu.listfactory/ListFactory.java后的结果如上。
<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.people.com.cn/">人民日报</a></td> <td><a href="http://www.gmw.cn/">光明日报</a></td> </tr></
执行javac Main.java bigjunoba.bjtu.tablefactory/TableFactory.java后的结果如上。
抽象工厂模式的类图。
抽象工厂的特点:
1.易于增加具体的工厂。假如要增加新的具体工厂,那么需要做的就是编写Factory、Link、Tray和Page这4个类的子类,并实现它们定义的抽象方法。这样,无论增加多少个具体工厂,都无需修改抽象工厂和Main部分。
2.难以增加新的零件。假如要在factory包中增加一个表示图像的Picture零件,那就必须要对所有的具体工厂进行相应的修改才行。例如,需要在listfactory包中就必须做到假如createPicture方法和新增ListPictrue类,这样编写完成的具体工厂越多,修改的工作量就会越大。