《图解设计模式》之抽象工厂模式
抽象工厂模式——将关联零件组装成产品
不关心零件的具体实现,而只关心接口(API)。仅使用接口(API)将零件组装成产品。
抽象工厂的工作是将”抽象零件“组装成”抽象产品“。
示例程序
示例程序的功能是将带有层次关系的链接的集合制作成HTML文件。
类名 | 说明 |
---|---|
Factory | 表示抽象工厂的类(制作Link,Tray,Page) |
Item | 方便统一处理Link和Tray类 |
Link | 抽象零件:表示HTML的链接类 |
Tray | 抽象零件:表示含有Link和Tray的类 |
Page | 抽象零件:表示HTML页面的类 |
Main | 测试程序行为的类 |
ListFactory | 表示具体工厂的类(制作listLink,listTray,listPage) |
ListLink | 具体零件:表示HTML的链接类 |
ListTray | 具体零件:表示含有Link和Tray的类 |
ListPage | 具体零件:表示HTML页面的类 |
示例程序的类图
示例程序的包结构:
示例代码
Item类:Link类和Tray类的父类
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
Link类
public abstract class Link extends Item{
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
Tray类
public abstract class Tray extends Item {
protected ArrayList tray = new ArrayList();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
tray.add(item);
}
}
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;
}
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();
}
}
protected abstract String makeHTML();
}
Factory类
Factory类中的getFactory()方法通过Class.forName()来动态地读取类信息,接着使用newInstance()生成该类的实例,然后将它作为返回值返回。
这一点很重要,正因为这样,才能够通过继承抽象的Factory类生成各种类型的具体的工厂类,以实现不同的需求。
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类是如何使用抽象工厂生产零件并将零件组装成产品。
Main类
值得注意的是Main类中实际上只引入了factory包,从这可以看出来,它并没有使用任何具体零件、产品和工厂。
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println();
System.out.println();
System.out.println();
System.exit(0);
}
//在启动参数里配置需要生成哪个具体工厂类。
Factory factory = Factory.getFactory(args[0]);
Link people = factory.createLink("人民日报", "https://www.people.com.cn/");
Link gmw = factory.createLink("光明日报", "https://www.gmw.cn/");
Link us_yahoo = factory.createLink("Yahoo!", "https://www.yahoo.com/");
Link jp_yahoo = factory.createLink("Yahoo!", "https://www.yahoo.co.jp/");
Link excite = factory.createLink("Yahoo!", "https://www.excite.com/");
Link google = factory.createLink("Yahoo!", "https://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", "Brian");
page.add(trayNews);
page.add(traySearch);
page.output();
}
}
如上图,将具体工厂类地址配置到启动参数。
具体的工厂、零件和产品
上面是抽象类的代码,现在将视角切换到具体类。
ListFactory类
继承Factory类,简单地new出了几个实例。
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);
}
}
ListLink类
重写Item类的makeHTML()方法
,编写HTML片段。
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
@Override
public String makeHTML() {
return " " + caption + " \n ";
}
}
ListTray类
重写Item类的makeHTML()方法
,编写HTML片段。 值得注意的是,ListLink类的makeHTML()方法
是在此时在ListTray类的makeHTML()方法
中调用的。也就是说ListTray类编写的HTML片段是由ListLink类编写的HTML片段和它自己写的HTML片段组成的。
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("\n");
buffer.append(caption + "\n");
buffer.append("\n");
Iterator it = tray.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
buffer.append(item.makeHTML());
}
buffer.append("
\n");
buffer.append(" \n");
return buffer.toString();
}
}
ListPage类
重写Item类的makeHTML()方法
,编写HTML页面。同样值得注意的是,ListTray类的makeHTML()方法
也是在此时在ListPage类的makeHTML()方法
中调用的。也就是说ListPage类编写的HTML页面是由ListTray类编写的HTML片段和它自己写的HTML片段组成的。
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
@Override
protected String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("" + title + " \n");
buffer.append("\n");
buffer.append("" + title + "
\n");
buffer.append("\n");
Iterator it = content.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
buffer.append(item.makeHTML());
}
buffer.append("
\n");
buffer.append("
" + author + "");
buffer.append("+\n");
return buffer.toString();
}
}
抽象工厂模式中登场的角色
-
AbstractProduct(抽象产品)
AbstractProduct 角色负责定义 AbstractFactory 角色所生成的抽象零件和产品的接口(API)。在示例程序中,由 Link 类、Tray 类和 Page 类扮演此角色。
-
AbstractFactory(抽象工厂)
AbstractFactory 角色负责定义用于生成抽象产品的接口(API)。在示例程序中,由 Factory
类扮演此角色。 -
Client( 委托者 )
Client 角色仅会调用 AbstractFactory 角色和 AbstraictProduct 角色的接口(API)来进行工作,对于具体的零件、产品和工厂一无所知。在示例程序中,由Main 类扮演此角色。类图中省略此角色
抽象工厂模式的类图
-
ConcreteProduct(具体产品)
ConcreteProduct 角色负责实现 AbstractProduct 角色的接口(API)。在示例程序中,由listFactory包中的类扮演此角色。
-
Concrete Factory(具体工厂)
ConcreteFactory 角色负责实现 AbstractFactory 角色的接口(API)。在示例程序中,由ListFactory类扮演此角色。
要点
-
易于增加具体的工厂
在上文中说到,在抽象工厂模式中增加具体的工厂是很容易的。只需要将抽象工厂、抽象零件、抽象产品全部具体化即可。这样无论要增加或者修改具体工厂的bug,都无需修改抽象工厂和 Main 类的代码。
(例如原书中另一个具体工厂类TableFactory类,其源码在我GitHub仓库,有兴趣可以自行下载,地址在文章末尾。)
-
难以增加新的零件
但是如果抽象工厂模式想要增加新的零件时怎么办呢?例如,现在要在factory包下增加一个表示图像的Picture抽象零件。这时不仅需要增加一个抽象零件,还要对所有具体工厂类进行修改才行。已经编写完成的具体工厂类越多,修改的工作量就越大。
源码地址:
KeepLearning/DesignPatterns
刚开始写博客,有什么问题大家请指出,谢谢。
无限进步