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

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 设计模式学习(九):Abstract Factory抽象工厂模式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

目录

一、什么是Abstract Factory模式

 二、Abstract Factory示例代码

2.1 类之间的关系

2.2 抽象的零件:ltem类

2.3 抽象的零件:Link类

2.4 抽象的零件:Tray类

2.5 抽象的产品: Page类

2.6 抽象的工厂:Factory类

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

2.8 具体的工厂:ListFactory类

2.9 具体的零件:ListLink类

2.10 具体的零件:ListTray类

2.11 具体的产品:ListPage类

2.12 运行结果

三、拓展思路的要点

3.1 易于增加具体的工厂

3.2 难以增加新的零件

四、相关的设计模式

4.1 Builder模式

4.2 Factory Method模式

4.3 Composite模式

4.4 Singleton模式


一、什么是Abstract Factory模式

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

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

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

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

        在Abstract Factory模式中将会出现抽象工厂,它会将抽象零件组装为抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口(API )。我们仅使用该接口(API)将零件组装成为产品。
        在Tempate Method模式和Builder模式中,子类这一层负责方法的具体实现。在 AbstractFactory模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。

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

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

 二、Abstract Factory示例代码

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

2.1 类之间的关系

        类的功能:

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

        类图:

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

        源文件结构:

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

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

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文件: 

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

三、拓展思路的要点

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模式。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/124944.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!