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

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

不关心零件的具体实现,而只关心接口(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页面的类

示例程序的类图

image

示例程序的包结构:
image

示例代码

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();
    }
}

image

如上图,将具体工厂类地址配置到启动参数。


具体的工厂、零件和产品

上面是抽象类的代码,现在将视角切换到具体类。

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 " <li><a href=\"" + url + "\">" + caption + "</a></li>\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("<li>\n");
        buffer.append(caption + "\n");
        buffer.append("<ul>\n");
        Iterator it = tray.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</li>\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("<html><head><title>" + title + "</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<h1>" + title + "</h1>\n");
        buffer.append("<ul>\n");
        Iterator it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body>+</html>\n");
        return buffer.toString();
    }
}

抽象工厂模式中登场的角色

  • AbstractProduct(抽象产品)

    AbstractProduct 角色负责定义 AbstractFactory 角色所生成的抽象零件和产品的接口(API)。在示例程序中,由 Link 类、Tray 类和 Page 类扮演此角色。

  • AbstractFactory(抽象工厂)

    AbstractFactory 角色负责定义用于生成抽象产品的接口(API)。在示例程序中,由 Factory
    类扮演此角色。

  • Client( 委托者 )

    Client 角色仅会调用 AbstractFactory 角色和 AbstraictProduct 角色的接口(API)来进行工作,对于具体的零件、产品和工厂一无所知。在示例程序中,由Main 类扮演此角色。类图中省略此角色

    抽象工厂模式的类图

    image

  • ConcreteProduct(具体产品)

    ConcreteProduct 角色负责实现 AbstractProduct 角色的接口(API)。在示例程序中,由listFactory包中的类扮演此角色。

  • Concrete Factory(具体工厂)
    ConcreteFactory 角色负责实现 AbstractFactory 角色的接口(API)。在示例程序中,由ListFactory类扮演此角色。

要点

  • 易于增加具体的工厂

    在上文中说到,在抽象工厂模式中增加具体的工厂是很容易的。只需要将抽象工厂、抽象零件、抽象产品全部具体化即可。这样无论要增加或者修改具体工厂的bug,都无需修改抽象工厂和 Main 类的代码。

    (例如原书中另一个具体工厂类TableFactory类,其源码在我GitHub仓库,有兴趣可以自行下载,地址在文章末尾。)

  • 难以增加新的零件

    但是如果抽象工厂模式想要增加新的零件时怎么办呢?例如,现在要在factory包下增加一个表示图像的Picture抽象零件。这时不仅需要增加一个抽象零件,还要对所有具体工厂类进行修改才行。已经编写完成的具体工厂类越多,修改的工作量就越大。

源码地址:
KeepLearning/DesignPatterns
刚开始写博客,有什么问题大家请指出,谢谢。

无限进步

posted @ 2021-11-20 16:57  趁我满头秀发  阅读(198)  评论(0)    收藏  举报