Abstract Factory(抽象工厂) 模式

8.1 Abstract Factory 模式

  通常,我们不会将 “抽象的” 这个词与 “工厂” 这个词联系到一起。所谓工厂,就是将零件组装成产品的地方,这是一项具体的工作。大在 Abstract Factory 模式中,不仅有 “抽象工厂” ,还有 “抽象零件” 和 “抽象产品” 。抽象工厂的工作是将 “抽象零件” 组装为 “抽象产品”。
  在面向对象的编程中 “抽象” 这个词的具体含义指的是 “不考虑具体怎样实现,而是仅关注接口(API)” 的状态。例如,抽象方法并不定义方法的具体实现,而是仅只确定了方法的名字和签名(参数的类型和个数)。
  在 Abstract Factory 模式中将会出现抽象工厂,并不关心零件的具体实现,而是只关心接口(API)。我们仅使用该接口(API)将零件组装成为产品。
  在 Template Method 模式和 Builder 模式中,子类这个一层负责方法的具体实现。在 Abstract Factory 模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。

8.2 示例程序

  功能:将带有层次关系的链接的集合制作成 HTML 文件。
  在示例程序中,类被划分为以下3个包。

  • factroy 包:包含抽象工厂、零件、产品的包
  • 无名包:包含 Main 的包
  • listfactory 包:包含具体工厂、零件、产品的包
    Abstract Factory(抽象工厂) 模式_第1张图片
示例程序UML类图
|| 抽象的零件:Item 类

  Item 类是 Link 类和 Tray 类的父类,这样 Link 类和 Tray 类就有了可替换性。
  caption 字段表示项目的 “标题”。
  makeHTML 方法是抽象方法,需要子类来实现这个方法。该方法返回 HTML 文件的内容。

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

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

    public abstract String makeHTML();
}
|| 抽象的零件:Link 类

  Link 类是抽象地表示 HTML 的超链接的类。
  因为其继承了 Item 类,且未实现 makeHTML 方法,所以其也是一个抽象类。

/**
* 抽象零件:Link 类
*/
public abstract class Link extends Item{
    protected String url;

    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}
|| 抽象的零件:Tray 类

  Tray 类表示的是一个含有多个 Link 类和 Tray 类的容器(Tray 有托盘的意思)。
  Tray 类使用 add 方法jiang Link 类和 Tray 类集合在一起。为了表示集合的对象是 “Link类和 Tray类”,我们设置add 方法的参数是两者的父类 Item。
  同理,也是一个抽象类。

/**
* 抽象零件:Tray
*/
public abstract class Tray extends Item{
    protected List trays = new ArrayList<>();

    public Tray(String caption) {
        super(caption);
    }

    public void add(Item item) {
        trays.add(item);
    }
}
|| 抽象的产品:Page 类

  Page 类是抽象地表示 HTML 页面的类。如果将 Link 和 Tray 比喻成抽象的零件,那么 Page 类就是抽象地产品。
  可以使用 add 方法想页面中增加 Item(即 Link 或 Tray)。增加的 Item 将会在页面中显示出来。
  output 方法首先根据页面标题确定文件名,接着调用 makeHTML 方法将自身保存的 HTML 内容写入到文件中。

public abstract class Page {
    protected String title;
    protected String author;
    protected List 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";
            FileWriter writer = new FileWriter(fileName);
            writer.write(makeHTML()); // 这里是子类的方法
            writer.close();
            System.out.println(fileName + " 编写完成。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public abstract String makeHTML();
}
|| 抽象的工厂:Factory 类

  getFactory 方法根据指定的类名生成具体工厂的实例(反射原理)。通过调用 Class 类的 forName 方法来动态地读取类信息,接着使用 newInstance 方法生成该类的实例,并将其作为返回值返回给调用者。
  虽然 getFactory 方法生成的是具体的工厂实例,但是返回值的类型是抽象工厂类型。
  其他三个方法用于在抽象工厂中生成零件和产品的方法。这些方法都是抽象方法,具体的实现交给了 Factory 类的子类,不过,这里确定了方法的名字和签名。

/**
* 抽象工厂:Factory
*/
public abstract class Factory {

    public static Factory getFactory(String className) {
        Factory factory = null;
        try {
            factory = (Factory)Class.forName(className).newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            System.out.println("没有找到 " + className + " 类。");
        }
        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 包,该类并没有使用任何具体零件、产品和工厂。
  Main 类会使用 getFactory 方法生成该参数(arg[0])对应的工厂,并将其保存在 factory变量中。
  之后,Main 类会使用 factory 生成 Link 和 Tray,然后将 Link 和 Tray 都放入 Tray 中,最后生成 Page 并将生成的结果输出至文件。

public class Main {

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java Main class.name.of.ConcreteFactory");
            System.out.println("Example 1: java Main listfactory.ListFactory");
            System.out.println("Example 2: java Main tablefactory.TableFactory");
            System.exit(0);
        }
        Factory factory = Factory.getFactory(args[0]);
        Link people = factory.createLink("人民日报", "http://www.people.com.cn");
        Link gmw = factory.createLink("光明日报", "http://www.gmw.cn/");

        Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
        Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/");

        Link excite = factory.createLink("Excite", "http://www.excite.com/");
        Link google = factory.createLink("Google", "http://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", "mingchenxu");
        page.add(trayNews);
        page.add(traySearch);

        page.output();
    }
}
|| 具体的工厂:ListFactory 类

  ListFactory 类实现了 Factory 类的 createLink 方法、createTray 方法以及 createPage 方法。各个方法内部只是分别简单地 new 出了各自的实例(根据实际情况,可能需要 Prototype 模式来进行 clone)。

/**
* 具体的工厂:ListFactory
*/
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 类

  继承了 Link 类,实现了 makeHTML 抽象方法。使用

  • 标签和 标签来制作 HTML 片段。

    /**
    * 具体的零件:ListLink
    */
    public class ListLink extends Link {
        public ListLink(String caption, String url) {
            super(caption, url);
        }
    
        @Override
        public String makeHTML() {
            return "
  • " + caption + "
  • \n"; } }
    || 具体的零件:ListTray 类

      ListTray类是 Tray 类的子类。tray 字段中保存了所有需要以 HTML 格式输出的 Item,而负责将它们以 HTML 格式输出的就是 makeHTML 方法了。
      makeHTML 方法首先使用

  • 标签输出标题,接着使用
    • 标签输出每个 Item。
        那么每个 Item 又是如何输出为 HTML 格式的呢?当然是调用每个 Item 的 makeHTML 方法。且这里不能使用 switch 语句或 if 语句去判断变量 item 中保存的实例的类型,否则就不是面向对象编程了。至于 item 究竟进行了什么样的处理,只有 item 的实例才知道。这就是面向对象的优点。

      public class ListTray extends Tray {
      
          public ListTray(String caption) {
              super(caption);
          }
      
          @Override
          public String makeHTML() {
              StringBuilder builder = new StringBuilder();
              builder.append("
    • \n"); builder.append(caption).append("\n"); builder.append("
        \n"); trays.forEach(tray -> builder.append(tray.makeHTML())); builder.append("
      \n"); builder.append("
    • \n"); return builder.toString(); } }
      || 具体的产品:ListPage 类

        ListPage 类是 Page 类的子类。ListPage 将字段中保存的内容输出为 HTML 格式。其中遍历语句包裹在

      之间,因为 ListLink 和 ListTray 的 makeHTML 方法,在它们的最外侧都会有
    • 标签。

      /**
      * 具体的产品: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("").append(title).append("\n");
              builder.append("\n");
              builder.append("

      ").append(title).append("

      \n"); content.forEach(e -> builder.append(e.makeHTML())); builder.append("
    \n"); builder.append("
    ").append(author).append("
    "); builder.append("\n"); return builder.toString(); } }

    运行main方法,输入结果:


    Abstract Factory(抽象工厂) 模式_第2张图片

    8.3 为示例程序增加其他工厂

      以示例程序为例,如果只是为了编写带有 HTML 超链接集合的文件,那我们的阵势未免有些过大了。当只有一个具体工厂的时候,是完全没有必要划分 “抽象类” 与 “具体类” 的。下面,我们将在示例程序的几次上再增加其他的具体工厂。
      现在我们来使用 tablefactory 将链接以表格形式展示出来。

    || 具体的工厂:TableFactory 类
    public class TableFactory extends Factory {
        @Override
        public Link createLink(String caption, String url) {
            return new TableLink(caption, url);
        }
    
        @Override
        public Tray createTray(String caption) {
            return new TableTray(caption);
        }
    
        @Override
        public Page createPage(String title, String author) {
            return new TablePage(title, author);
        }
    }
    
    || 具体的零件:TableLink 类
    public class TableLink extends Link {
    
        public TableLink(String caption, String url) {
            super(caption, url);
        }
    
        @Override
        public String makeHTML() {
            return "" + caption + "";
        }
    }
    
    || 具体的零件:TableTray 类
    public class TableTray extends Tray {
    
        public TableTray(String caption) {
            super(caption);
        }
    
        @Override
        public String makeHTML() {
            StringBuilder builder = new StringBuilder();
            builder.append("")
                    .append("")
                    .append("")
                    .append("\n");
            builder.append("\n");
            trays.forEach(e -> builder.append(e.makeHTML()));
            builder.append("
    ") .append(caption).append("
    ") .append(""); return builder.toString(); } }
    || 具体的产品:TablePage 类
    public class TablePage extends Page {
    
        public TablePage(String title, String author) {
            super(title, author);
        }
    
        @Override
        public String makeHTML() {
            StringBuilder builder = new StringBuilder();
            builder.append("")
                    .append(title)
                    .append("\n");
            builder.append("\n")
                    .append("

    ").append(title).append("

    \n"); builder.append("\n"); content.forEach(e -> builder.append("").append(e.makeHTML()).append("")); builder.append("
    \n"); builder.append("
    ").append(author).append("
    "); builder.append("\n"); return builder.toString(); } }

    运行类与示例程序的 main 类一致,更换 arg[0] 参数即可,运行结果如下:


    Abstract Factory(抽象工厂) 模式_第3张图片

    8.4 Abstact Factory 模式中的登场角色

      在 Abstract Factory 模式中有以下登场角色。

    • AbstractProduct(抽象产品)
      AbstractProduct 角色负责定义 AbstractFactory 角色所产生的抽象零件和产品的接口(API)。在示例程序中,由 Link 类、Tray 类和 Page 类扮演此角色。
    • AbstractFactory(抽象工厂)
      AbstractFactory 角色负责定义用于生成抽象产品的接口(API)。在示例程序中,由 Factroy 类扮演此角色。
    • Client(委托者)
      Client 角色仅会调用 AbstractFactory 角色和 AbstractProduct 角色的接口(API)来进行工作,对于具体的零件、产品和工厂一无所知。
    • ConcreteProduct(具体产品)
      ConcreteProduct 角色负责实现 AbstractProduct 角色的接口(API)。在示例程序中,由以下包中的类扮演此角色。
        - listfactory 包:ListLink、ListTray、ListPage
        - tablefactory 包:TableLink、TableTray、TablePage
    • ConcreteFactory(具体工厂)
      ConcreteFactory 角色负责实现 AbstractFactory 角色的接口(API)。在示例程序中由以下类扮演此角色。
        - listfactory 包:Listfactory 类
        - tablefactory 包:Tablefactory 类

    Abstract Factory(抽象工厂) 模式_第4张图片

    Abstract Factory 类图

    8.5 拓展思路的要点

    || 易于增加具体的工厂

      在 Abstract Factory 模式中增加具体的工厂是非常容易的。要编写哪些类和需要实现哪些方法都非常清楚。
      假设我们要在示例程序中增加新的具体工厂,那么需要做的就是编写 Factory、Link、Tray、Page 这4个类的子类,并实现它们定义的抽象方法。也就是说将 factory 包中的抽象部分全部具体化即可。无论要增加多少个具体工厂,都无需修改抽象工厂和 Main 部分。

    || 难以增加新的零件

      试想一下在 Abstract Factory 模式中增加新的零件时应当如何做。例如,在示例程序中,我们要在 factory 包中增加一个表示图像的 Picture 零件。这时,我们必须要对所有的具体工厂进行相应的修改才行。例如,在 listfactory 包中,我们必须做以下修改。

    • 在 ListFactory 中加入 createPicture 方法
    • 新增 ListPicture 类

    已经编写完成的具体工厂越多,修改的工作量就会越大。

  • 你可能感兴趣的:(图解设计模式)