设计模式学习(九):Abstract Factory抽象工厂模式

简介: Abstract的意思是“抽象的”,Factory的意思是“工厂”。在Abstract Factory模式中,不仅有“抽象工厂”,还有“抽象零件”和“抽象产品”。抽象工厂的工作是将“抽象零件”组装为“抽象产品”。

一、什么是Abstract Factory模式



Abstract的意思是“抽象的”,Factory的意思是“工厂”。在Abstract Factory模式中,不仅有“抽象工厂”,还有“抽象零件”和“抽象产品”。抽象工厂的工作是将“抽象零件”组装为“抽象产品”。


请大家先回忆一下面向对象编程中的“抽象”这个词的具体含义。它指的是“不考虑具体怎样实现,而是仅关注接口(API )"的状态。例如,抽象方法(Abstract Method)并不定义方法的具体实现,而是仅仅只确定了方法的名字和签名(参数的类型和个数)。


关于“忘记方法的具体实现(假装忘记),使用抽象方法进行编程”的设计思想,我们在Template Method模式和 Builder模式中已经稍微提及了一些。


设计模式学习(六):Template Method模板方法模式_玉面大蛟龙的博客-CSDN博客


在Abstract Factory模式中将会出现抽象工厂,它会将抽象零件组装为抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口(API )。我们仅使用该接口(API)将零件组装成为产品。


在Tempate Method模式和Builder模式中,子类这一层负责方法的具体实现。在 AbstractFactory模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。


用一句话概况就是:将关联零件组装成产品。


dbd9a82fcd3e4b68bc6cd234bdc54f14.png


二、Abstract Factory示例代码



示例程序的功能是将带有层次关系的链接的集合制作成HTML文件。最后制作完成的HTML文件如图8-1所示,在浏览器中查看到的结果如图8-2所示。


2.1 类之间的关系


类的功能:

228df0c0f2684145bbd01af1148762b7.png


类图:

image.png


源文件结构:


3fd94cac0f5045f1856d3f4bd2562bba.png


2.2 抽象的零件:ltem类


Item类是Link类和Tray类的父类( Item有“项目”的意思)。这样,Link类和Tray类就具有可替换性了。


package factory;
public abstract class Item {
    //项目的“标题”
    protected String caption;
    public Item(String caption) {
        this.caption = caption;
    }
    /**
     * 返回HTML文件的内容(需要子类去实现)
     */
    public abstract String makeHTML();
}


2.3 抽象的零件:Link类


Link类是抽象地表示HTML的超链接的类。


乍一看,在Link类中好像一个抽象方法都没有,但实际上并非如此。由于Link类中没有实现父类(Item类)的抽象方法(makeHTML),因此它也是抽象类。


package factory;
public abstract class Link extends Item {
    //超链接所指向的地址
    protected String url;
    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}


2.4 抽象的零件:Tray类


Tray类表示的是一个含有多个Link类和Tray类的容器(Tray有托盘的意思。请想象成在托盘上放置着一个一个项目)。


Tray类使用add方法将Link类和Tray类集合在一起。为了表示集合的对象是“Link类和Tray类”,我们设置add方法的参数为Link类和Tray类的父类Item类。


虽然Tray类也继承了Item类的抽象方法makeHTML,但它并没有实现该方法。因此,Tray类也是抽象类。


我们可以发现,tray示例是protected类型的,如果改为private类型,优点是Tray的子类(即具体的零件)不会依赖于tray字段的实现;缺点是必须重新编写一些方法,让外部可以访问自身。通常,与将字段的可见性设置为protected相比,将字段的可见性设置为private,然后编写用于访问字段的方法会更安全。


package factory;
public abstract class Tray extends Item{
    //Link类和Tray类的集合
    protected ArrayList tray = new ArrayList();
    public Tray(String caption) {
        super(caption);
    }
    /**
     * 将Link类和Tray类集合在一起
     */
    public void add(Item item) {
        tray.add(item);
    }
}


2.5 抽象的产品: Page类

   

Page类是抽象地表示HTML页面的类。如果将Link和Tray比喻成抽象的“零件”,那么Page类就是抽象的“产品”。


其中,我们可以去掉 writer.write(this.makeHTML()) 中的this,但为了强调调用的是Page类自己的makeHTML方法,我们显式地加上了this。这里调用的makeHTML方法是一个抽象方法。output方法是一个简单的Template Method模式的方法。


/**
 * Page类是抽象地表示HTML页面的类。如果将Link和Tray比喻成抽象的“零件”,那么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;
    }
    /**
     * 向页面中增加Item。增加的Item将会在页面中显示出来
     * @param item Link或Tray
     */
    public void add(Item item) {
        content.add(item);
    }
    /**
     * 首先根据页面标题确定文件名,接着调用makeHTML方法将自身保存的HTML内容写入到文件中
     */
    public void output() {
        try {
            String filename = title + ".html";
            Writer writer = new FileWriter(filename);
            //为了强调调用的是Page类自己的makeHTML方法,我们显式地加上了this
            writer.write(this.makeHTML());
            writer.close();
            System.out.println(filename + "编写完成。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public abstract String makeHTML();
}


2.6 抽象的工厂:Factory类


前面我们学习了抽象零件和抽象产品的代码,现在来看看抽象工厂。


createLink、createTray、createPage等方法是用于在抽象工厂中生成零件和产品的方法。这些方法都是抽象方法,具体的实现被交给了Factory类的子类。不过,这里确定了方法的名字和签名。


/**
 * 抽象工厂
 */
public abstract class Factory {
    /**
     * 根据指定的类名生成具体工厂的实例
     * @param classname 具体工厂的类名所对应的字符串
     * @return 抽象工厂类型
     */
    public static Factory getFactory(String classname) {
        Factory factory = null;
        try {
            factory = (Factory) Class.forName(classname).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } 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);
}


2.7 使用工厂将零件组装称为产品:Main类


Main类使用抽象工厂生产零件并将零件组装成产品。Main类中只引入了factory包,从这一点可以看出,该类并没有使用任何具体零件、产品和工厂。


具体工厂的类名是通过命令行来指定的。例如,如果要使用listfactory包中的ListFactory类,可以在命令行中输入以下命令:


java Main listfactory.ListFactory


Main类会使用getFactory方法生成该参数( arg [0] )对应的工厂,并将其保存在factory变量中。


之后,Main类会使用factory生成Link 和 Tray,然后将Link和Tray都放入Tray中,最后生成Page并将生成结果输出至文件。


import factory.*;
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.prople.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.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", "哈哈哈");
        page.add(traynews);
        page.add(traysearch);
        page.output();
    }
}


2.8 具体的工厂:ListFactory类


之前我们学习了抽象类的代码,现在让我们将视角切换到具体类。首先,我们来看看listfactory包中的工厂———ListFactory类。


ListFactory类实现了Factory类的createLink方法、createTray方法以及createPage方法。当然,各个方法内部只是分别简单地new出了ListLink类的实例、ListTray类的实例以及ListPage类的实例(根据实际需求,这里可能需要用Prototype模式来进行clone )。


/**
 * 具体工厂
 */
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);
    }
}


2.9 具体的零件:ListLink类


ListLink类是Link类的子类。在ListLink类中必须实现的方法是在父类中声明的makeHTML抽象方法。ListLink类使用<li>标签和<a>标签来制作HTML片段。这段HTML片段也可以与ListTary和ListPag的结果合并起来,就如同将螺栓和螺母拧在一起一样。


/**
 * 具体工厂
 */
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);
    }
}


2.10 具体的零件:ListTray类

     

ListTray类是Tray类的子类。这里我们重点看一下makeHTML方法是如何实现的。tray字段中保存了所有需要以HTML格式输出的Item,而负责将它们以HTML格式输出的就是makeHTML方法了。


请注意,buffer.append(item.makeHTML()) 这里并不关心变量item中保存的实例究竟是ListLink的实例还是ListTray的实例,只是简单地调用了item.makeHTML ()语句而已。这里不能使用switch语句或if语句去判断变量item中保存的实例的类型,否则就是非面向对象编程了。变量item是Item类型的,而Item类又声明了makeHTML方法,而且ListLink类和ListTray类都是Item类的子类,因此可以放心地调用。之后item会帮我们进行处理。至于item究竟进行了什么样的处理,只有item的实例(对象)才知道。这就是面向对象的优点。


public class ListTray extends Tray {
    public ListTray(String caption) {
        super(caption);
    }
    @Override
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        //使用<li>标签输出标题( caption )
        buffer.append("<li>\n");
        buffer.append(caption + "\n");
        buffer.append("<ul>\n");
        //使用<ul>和<li>标签输出每个Item
        Iterator it = tray.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            //输出为HTML格式
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</li>\n");
        return buffer.toString();
    }
}


2.11 具体的产品:ListPage类

     

ListPage类是Page类的子类。
public class ListPage extends Page {
    public ListPage(String title, String author) {
        super(title, author);
    }
    @Override
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><head><title>" + title + "</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<hl>" + title + "</hl>\n");
        buffer.append("<ul>\n");
        //content继承自Page类的字段
        Iterator it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        //作者名(author)用<address>标签输出
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}


2.12 运行结果

编译和运行方法如下:


javac Main.java listfactory/ListFactory.java
java Main listfactory.ListFactory


编写出的html文件:

2dbe50bc35414301a3655abf276199cc.png


三、拓展思路的要点



3.1 易于增加具体的工厂


在Abstract Factory模式中增加具体的工厂是非常容易的。这里说的“容易”指的是需要编写哪些类和需要实现哪些方法都非常清楚。


假设现在我们要在示例程序中增加新的具体工厂,那么需要做的就是编写Factory、Link、Tray、Page这4个类的子类,并实现它们定义的抽象方法。也就是说将factory包中的抽象部分全部具体化即可。


这样一来,无论要增加多少个具体工厂(或是要修改具体工厂的Bug ),都无需修改抽象工厂和Main部分。


3.2 难以增加新的零件


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


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


四、相关的设计模式



4.1 Builder模式


Abstract Factory模式通过调用抽象产品的接口(API )来组装抽象产品,生成具有复杂结构的实例。Builder模式则是分阶段地制作复杂实例。


4.2 Factory Method模式


有时Abstract Factory模式中零件和产品的生成会使用到Factory Method模式。


设计模式学习(七):Factory Method工厂模式_玉面大蛟龙的博客-CSDN博客 


4.3 Composite模式


有时Abstract Factory模式在制作产品时会使用Composite模式。


4.4 Singleton模式


有时Abstract Factory模式中的具体工厂会使用Singleton模式。


相关文章
|
3月前
|
设计模式
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
这篇文章详细解释了工厂模式,包括简单工厂、工厂方法和抽象工厂三种类型。每种模式都通过代码示例展示了其应用场景和实现方法,并比较了它们之间的差异。简单工厂模式通过一个工厂类来创建各种产品;工厂方法模式通过定义一个创建对象的接口,由子类决定实例化哪个类;抽象工厂模式提供一个创建相关或依赖对象家族的接口,而不需要明确指定具体类。
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
|
3月前
|
设计模式 Java
Java设计模式-抽象工厂模式(5)
Java设计模式-抽象工厂模式(5)
|
4月前
|
设计模式 Java
Java 设计模式之谜:工厂模式与抽象工厂模式究竟隐藏着怎样的神奇力量?
【8月更文挑战第30天】在Java编程中,设计模式为常见问题提供了高效解决方案。工厂模式与抽象工厂模式是常用的对象创建型设计模式,能显著提升代码的灵活性、可维护性和可扩展性。工厂模式通过定义创建对象的接口让子类决定实例化哪个类;而抽象工厂模式则进一步提供了一个创建一系列相关或相互依赖对象的接口,无需指定具体类。这种方式使得系统更易于扩展和维护。
43 1
|
4月前
|
设计模式 XML 存储
【三】设计模式~~~创建型模式~~~抽象工厂模式(Java)
文章详细介绍了抽象工厂模式,这是一种创建型设计模式,用于提供一个接口以创建一系列相关或相互依赖的对象,而不指定它们具体的类。通过代码示例和结构图,文章展示了抽象工厂模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了如何通过配置文件和反射机制实现工厂的动态创建。
【三】设计模式~~~创建型模式~~~抽象工厂模式(Java)
|
4月前
|
设计模式 Java C语言
设计模式-----------工厂模式之抽象工厂模式(创建型)
抽象工厂模式是一种创建型设计模式,它提供了一个接口用于创建一系列相关或相互依赖的对象,而无需指定具体类,从而增强了程序的可扩展性并确保客户端只使用同一产品族的产品。
设计模式-----------工厂模式之抽象工厂模式(创建型)
|
4月前
|
设计模式 存储 XML
[设计模式]创建型模式-抽象工厂模式
[设计模式]创建型模式-抽象工厂模式
|
6月前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
47 2
|
6月前
|
设计模式 uml
设计模式学习心得之前置知识 UML图看法与六大原则(下)
设计模式学习心得之前置知识 UML图看法与六大原则(下)
44 2
|
6月前
|
设计模式 安全 Java
设计模式学习心得之五种创建者模式(1)
设计模式学习心得之五种创建者模式(1)
40 0
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式