《图解设计模式》之抽象工厂模式


抽象工厂模式——将关联零件组装成产品

不关心零件的具体实现,而只关心接口(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
    刚开始写博客,有什么问题大家请指出,谢谢。

    无限进步