抽象工厂模式,由抽象工厂,抽象零件,以及具体工厂,具体零件组成。接口不需要关心用户选择哪个工厂,只按照特定规则进行组装,所有具体零件都继承抽象零件,所有具体工厂都继承抽象工厂。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

  • 意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 主要解决:主要解决接口选择的问题。
  • 何时使用:系统有多个产品族,而系统只消费其中某一族的产品。
  • 优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
  • 缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
  • 注意事项:产品族难扩展,产品等级易扩展。
    image.png
    在抽象工厂中,产品等级就是同一个抽象产品不同的具体产品,产品族就是组成某个产品的不同零件

考虑这样一个场景:
生成一个网页,有超链接,搜索引擎,以及打包这些的一整个页面。由不同的工厂产生不同样式的页面。
image.png

其中,factory包下为抽象工厂和抽象零件,listfactory为具体的工厂以及具体的零件,Main为调用接口。

首先来看一下抽象零件Item,以及抽象的产品Page,分别代表页面内容的条目以及一整个页面

/**
 * 抽象的零件
 */
public abstract class Item {
    protected String caption;

    public Item(String caption) {
        this.caption = caption;
    }

    public abstract String makeHtml();
}

在抽象的零件Item中,有一些稍微指定明确的抽象零件,Link以及Tray,他们都继承于Item,提供一些具体明确的创建方式或者公共方法。

/**
 * 抽象的零件,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<Item> tray = new ArrayList<>(10);
    public Tray(String caption) {
        super(caption);
    }

    public void add(Item item) {
        tray.add(item);
    }
}

一个抽象的产品Page,由Item组成

/**
 * 抽象的产品,Page
 */
public abstract class Page {
    protected String title;
    protected String author;
    protected ArrayList<Item> content = new ArrayList<>();

    public Page(String title, String author) {
        this.author = author;
        this.title = title;
    }

    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());
            System.out.println(fileName + "完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 一个简单的Template Method模式的方法
     */
    public abstract String makeHtml();
}

接下来看一下抽象的工厂。抽象工厂提供一个根据参数选择具体工厂的方法getFactory(),这是抽象工厂的主要方法,以及创建具体产品的方法。

/**
 * 抽象工厂
 */
public abstract class Factory {
    public static Factory getFactory(String className) {
        Factory factory = null;
        try {
            factory = (Factory) Class.forName(className).newInstance();
        } 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);
}

抽象部分到这里结束了,下面来看一下具体的零件,产品和具体工厂

/**
 * 具体的零件
 */
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";
    }
}
/**
 * 具体的零件
 */
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).append("\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();
    }
}

具体的产品

public class ListPage extends Page {
    public ListPage(String title, String author) {
        super(title, author);
    }

    @Override
    public void add(Item item) {
        super.add(item);
    }

    @Override
    public void outPut() {
        super.outPut();
    }

    @Override
    public String makeHtml() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><head><title>").append(title).append("</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<h1>").append(title).append("</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")
                .append("<hr><address>")
                .append(author)
                .append("<address")
                .append("</body></html>\n");
        return buffer.toString();
    }
}

具体的工厂,List的工厂只生产List的零件和产品

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

客户端接口调用

public class Main {
    public static void main(String[] args) {
        Factory factory = Factory.getFactory(args[0]);

        //生产零件
        Link people = factory.createLink("人民日报", "http://www.people.com");
        Link gmw = factory.createLink("光明日报", "http://www.gmw.com");
        Link google = factory.createLink("Google", "http://www.google.com");

        //生产零件.这里add之后,在具体工厂中会循环调用Link中的makeHtml方法
        Tray trayNews = factory.createTray("日报");
        trayNews.add(people);
        trayNews.add(gmw);

        Tray search = factory.createTray("搜索");
        search.add(google);

        //组装零件.这里add之后,在具体工厂中会循环调用Tray中的makeHtml方法,一层一层的调用,最终实现产品组装
        //在具体使用中,调用什么方法逻辑都在具体工厂中实现,从零件到产品
        Page page = factory.createPage("LinkPage", "Lan");
        page.add(trayNews);
        page.add(search);
        page.outPut();
    }
}

在客户端中,实现了零件的生产,产品的组装步骤,而不必关心具体由哪个工厂生产,完全根据抽象工厂中的获取工厂的方法进行选择。