【设计模式】工厂方法和抽象工厂
一、前言
依然记得几年前面试被问到工厂方法模式和抽象工厂有什么区别时,我一脸懵逼哑口无言。本文就分别探讨下这两种设计模式。
二、Factory Method
工厂方法(Factory Method)模式,将实例的生成交给子类,父类决定实例的生成方式,但不决定所要生成的具体的类。
这样就可以将生成实例的框架(framework)和实际负责生成实例的类解耦。
工厂方法的类图如下:
Product类和Factory类都是抽象类,属于框架部分,IDCard和IDCardFactory分别为其子类。
Factory类
public abstract class Factory {
/**
* 这里用到了 模板方法模式
* 使用final修饰 防止被子类重写
* @param owner
* @return
*/
public final Product create(String owner){
Product product = createProduct(owner);
registerProduct(product);
return product;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
该类的create方法定义了生产产品的算法,用到了Template Method模式,只要是Factory Method模式在生成实例时一定会用到Template Method模式。
Product类
public abstract class Product {
public abstract void use();
}
该类用于定义产品的方法和属性
IDCard
public class IDCard extends Product {
private String owner;
/**
* 不用public修饰防止被new
* @param owner
*/
IDCard(String owner){
System.out.println("制作"+ owner + "的ID卡");
this.owner = owner;
}
@Override
public void use() {
System.out.println("使用"+ owner + "的ID卡");
}
public String getOwner() {
return owner;
}
}
这里构造方法不是public是为了防止被new直接创建实例,只能通过IDCardFactory创建。
IDCardFactory类
public class IDCardFactory extends Factory {
private List<String> owners = new ArrayList<>();
/**
* 通过生成IDCard实例来生产产品
* @param owner
* @return
*/
@Override
protected Product createProduct(String owner) {
return new IDCard(owner);
}
/**
* 通过将IDCard的owner保存到owners来注册产品
* @param product
*/
@Override
protected void registerProduct(Product product) {
owners.add(((IDCard) product).getOwner());
}
}
测试类Main
public class Main {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("小明");
Product card2 = factory.create("小红");
Product card3 = factory.create("小刚");
card1.use();
card2.use();
card3.use();
}
}
运行结果如下:
制作小明的ID卡
制作小红的ID卡
制作小刚的ID卡
使用小明的ID卡
使用小红的ID卡
使用小刚的ID卡
可以看到调用方Main类只用到了框架部分的类(Product和Factory),而不依赖其具体实现,这样当要生成其他类时,框架部分的代码不用修改。
三、Abstract Factory
抽象工厂模式(Abstract Factory),抽象工厂的作用是将“抽象零件”组装为“抽象产品”。
下面的一个示例程序是利用抽象工厂模式,生成如下的网页文件LinkPage.html
程序的类图如下:
名字 | 说明 |
---|---|
Item | 方便统一处理Link和Tray的类 |
Factory | 表示抽象工厂的类(制作Link,Tray,Page) |
Link | 抽象零件:表示HTML的链接的类 |
Tray | 抽象零件 |
Page | 抽象零件:表示HTML页面的类 |
ListFactory | 表示具体工厂的类 |
ListLink | 具体零件 |
ListTray | 具体零件 |
ListPage | 具体零件:表示HTML页面的类 |
Item
public abstract class Item {
/**
* 标题
*/
protected String caption;
public Item(String caption) {
this.caption = caption;
}
/**
* 子类来实现
* @return
*/
public abstract String makeHtml();
}
Link
public abstract class Link extends Item {
protected String url;
public Link(String url, String caption) {
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);
}
/**
* 一个简单的Template Method模式的方法
*/
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
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 url, String caption);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title, String author);
}
ListLink类
public class ListLink extends Link {
public ListLink(String url, String caption) {
super(url, caption);
}
@Override
public String makeHtml() {
return "<li><a href= \"" + url + "\">" + caption + "</a></li>\n";
}
}
ListTray
public class ListTray extends Tray {
public ListTray(String caption){
super(caption);
}
@Override
public String makeHtml() {
StringBuilder builder = new StringBuilder();
builder.append("<li>\n");
builder.append(caption +"\n");
builder.append("<ul>\n");
Iterator it = tray.iterator();
while (it.hasNext()){
Item item = (Item) it.next();
builder.append(item.makeHtml());
}
builder.append("</ul>\n");
builder.append("</li>\n");
return builder.toString();
}
}
ListPage
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
@Override
public String makeHtml() {
StringBuilder builder = new StringBuilder();
builder.append("<html><head><title>" + title + "</head></title>\n");
builder.append("<body>\n");
builder.append("<h1>" + title + "</h1>");
builder.append("<ul>\n");
Iterator it = content.iterator();
while (it.hasNext()) {
Item item = (Item) it.next();
builder.append(item.makeHtml());
}
builder.append("</ul>\n");
builder.append("<hr><address>" + author + "</address></hr>");
builder.append("</body></html>\n");
return builder.toString();
}
}
ListFactory
public class ListFactory extends Factory {
@Override
public Link createLink(String url, String caption) {
return new ListLink(url, caption);
}
@Override
public Tray createTray(String caption) {
return new ListTray(caption);
}
@Override
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}
测试类Main
public class Main {
public static void main(String[] args) {
Factory factory = Factory.getFactory("cn.sp.abstract_factory.listfactory.ListFactory");
Link people = factory.createLink("http://www.people.com.cn", "人民日报");
Link gmw = factory.createLink("http://www.gmw.cn", "光明日报");
Link us_yahoo = factory.createLink("http://www.yahoo.com/", "Yahoo!");
Link jp_yahoo = factory.createLink("http://www.yahoo.jp/","Yahoo!Japan");
Link excite = factory.createLink("http://www.excite.com/","Excite");
Link google = factory.createLink("http://www.google.com/","Google");
Tray trayNews = factory.createTray("日报");
trayNews.add(people);
trayNews.add(gmw);
Tray trayYahoo = factory.createTray("Yahoo!");
trayYahoo.add(us_yahoo);
trayYahoo.add(jp_yahoo);
Tray search = factory.createTray("检索引擎");
search.add(trayYahoo);
search.add(excite);
search.add(google);
Page page = factory.createPage("LinkPage","杨文轩");
page.add(trayNews);
page.add(search);
page.output();
}
}
运行结果得到如下文件
- <html><head><title>LinkPage</head></title>
- <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.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></hr></body></html>
假如这里需要增加其他工厂如TableFactory,则只需要添加TableLink,TableTray,TablePage(分别为Link,Tray,Page的子类)即可,顶层的抽象类不需要改变。
所以可以看出抽象工厂模式有如下特点:
- 易于增加具体的工厂
- 难以增加新零件
四、总结
它们的区别大概如下:
1.工厂方法只能生成某一种产品,且对应的工厂只有一个。而抽象工厂可以增加其他工厂,产品由很多零件组成,生成各种实例。
2.工厂方法比较简单,抽象工厂模式更加复杂和灵活。
3.工厂方法是通过new创建实例,抽象工厂模式是通过反射创建的。
最后附上完整代码地址