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

news2024/10/7 8:30:38

目录

一、什么是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示例代码

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

2.1 类之间的关系

        类的功能:

        类图:

 

        源文件结构:

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

 

三、拓展思路的要点

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173042.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux三剑客之AWK

目录 AWK是什么 AWK基本结构 a.txt的文本实例 AWK内置变量 a.txt的文本实例 AWK自定义变量 a.txt的文本实例 AWK内置函数 a.txt的文本实例 awk高级输出 a.txt的文本实例 排序输出 a.txt的文本实例 条件选择输出 a.txt的文本实例 控制语句 a.txt的文本实例 AWK是什…

Java SE 继承和多态

继承和多态 1. 继承 1.1 为什么需要继承 Java中使用类对现实世界中实体来进行描述&#xff0c;类经过实例化之后的产物对象&#xff0c;则可以用来表示现实中的实体&#xff0c;但是 现实世界错综复杂&#xff0c;事物之间可能会存在一些关联&#xff0c;那在设计程序是就需…

Elasticsearch7.8.0版本高级查询—— 指定查询字段查询文档

目录一、初始化文档数据二、指定查询字段查询文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; {"name":"张三","…

Git知识学习

主要内容&#xff1a;熟练掌握Git、GitHub、GitLab、Gitee码云的使用 文章目录1.Git概述1.1版本控制1.2版本控制工具1.3Git和代码托管中心2.Git常用命令2.1设置用户签名2.2初始化本地库2.3查看本地库状态2.3.1首次查看2.3.2新增文件2.3.3再次查看2.4添加暂存区2.4.1将工作区文件…

! LaTeX Error: File xxx.sty not found-统一解决办法

在使用一些模板时常见这个错&#xff0c;其实就是缺宏包&#xff01;解决方案如下&#xff01; 第一步&#xff1a;在网站 https://ctan.org/pkg 找到你缺失的宏包&#xff0c;下载zip文件。 第二步&#xff1a;将解压后的文件夹复制到安装路径下&#xff1a; 如&#xff…

aws ecs 理解任务和容器的资源分配

参考资料 如何在 Amazon ECS 中为任务分配内存&#xff1f; 关于 Amazon ECS 中的 CPU 分配&#xff0c;我需要了解哪些信息&#xff1f; Amazon ECS CloudWatch 指标 任务定义参数 在ecs中可以指定资源的分配逻辑&#xff0c;其实就是cpu和内存分配。 下面这张图对ecs任…

搜索本地文件

李国春 处理大量的数据集时将文件整理到一起也是一个重要的工作。本文介绍一个将本地计算机目标文件的绝对路径汇集到一个文本文件的中的脚本。以方便后续批量处理这些文件。 启动RSD&#xff0c;在脚本编辑窗口输入图1中的代码。点击工具条上的小三角开始运行&#xff0c;提…

计网必会:电子邮件、SMTP协议

文章目录SMTP概念SMTP的操作过程——发送邮件-接收邮件细品&#xff1a;发送邮件与HTTP的对比邮件报文格式和MIME邮件访问协议SMTP概念 SMTP是电子邮件中的主要协议&#xff0c;它能使用TCP可靠数据传输服务&#xff0c;从发送方的服务器向接收方发送邮件&#xff0c; SMTP&am…

第四章必备前端基础知识-第二节1:CSS概述和选择器

文章目录一&#xff1a;CSS概述&#xff08;1&#xff09;概述&#xff08;2&#xff09;语法规范&#xff08;3&#xff09;CSS引入方式二&#xff1a;选择器&#xff08;1&#xff09;基础选择器①&#xff1a;标签选择器②&#xff1a;类选择器③&#xff1a;id选择器④&…

MyISAM存储引擎中的索引方案

MyISAM存储引擎中的索引 我们知道 InnoDB存储引擎中索引即数据&#xff0c;也就是聚集索引的那棵B树的叶子节点中已经把所有完整的用户记录都包含了&#xff0c;而MyISAM引擎也使用BTree作为索引结构&#xff0c;但是却 将索引和数据分开存储&#xff0c;其特点如下&#xff1…

ElasticSearch Docker 部署实例

文章目录前言基本环境构建Java安装docker安装es部署安装ES验证安装安装kibana设置密码进入es容器安装Vim修改es容器配置文件设置es访问密码设置Kibana密码理想状态方案二修改挂载安装IK分词器安装ik分词器在线安装离线安装前言 虽然说要停更&#xff0c;但是有些东西还是需要记…

SpringMVC | SSM整合(SpringMVC+Spring+MyBatis)

0️⃣概述&#x1f47e;SSM与三层架构对应关系SpringMVC —— 表示层&#xff08;controller、view&#xff09;Spring —— 业务逻辑层&#xff08;service&#xff09;MyBatis —— 数据访问层&#xff08;dao、pojo&#xff09;&#x1f47e;配置文件spring-config.xml ——…

vue全家桶之vuex详解

文章目录Vuex 概述1.1 组件之间共享数据的方式1.2 Vuex 是什么1.3 使用 Vuex 统一管理状态的好处什么样的数据适合存储到 Vuex 中2. Vuex 的基本使用3. Vuex 的核心概念3.1 核心概念概述3.2 State3.3 Mutation3.4 Action3.5 GetterVuex 概述 1.1 组件之间共享数据的方式 父向…

NET.前端基础

均摘自C语言中文网 网页一般由三部分组成&#xff0c;分别是 HTML&#xff08;超文本标记语言&#xff09;、CSS&#xff08;层叠样式表&#xff09;和 JavaScript&#xff08;简称“JS”动态脚本语言&#xff09;&#xff0c;它们三者在网页中分别承担着不同的任务。 HTML …

74. 搜索二维矩阵

74.搜索二维矩阵一、题目描述二、解题思路2.1 二分查找行2.2 二分查找列三、提交结果一、题目描述 二、解题思路 采用两次二分的方式&#xff0c;第一次二分用于找到target在二维矩阵中的行标&#xff0c;第二次二分只需要对找到的行进行二分查找即可。 2.1 二分查找行 初始…

10.3 定制操作

文章目录向算法传递函数谓词排序算法lambda表达式可调用对象介绍lambdafind_if 和 find_each的介绍lambda的捕获和返回值捕获引用捕获隐式捕获可变lambda指定lambda返回的类型函数体参数绑定标准库bind函数占位符_n具体使用bind的参数使用bind重排参数顺序绑定引用参数向算法传…

svg动画图形绘制

先介绍下绘制图形的标签 1&#xff1a;线段&#xff1a;line 2&#xff1a;矩形&#xff1a;rect 3: 圆形&#xff1a;circle 4&#xff1a;多边形&#xff1a;polyline&#xff08;不会自动连接起点和终点&#xff09; 5: 多边形:polygon (会自动连接起点和终点&#xff09; v…

SAP FICO 理解统驭科目记账与特殊记账

统驭科目记账与特殊记账 【背景】 统驭科目通常分为三类&#xff1a;资产&#xff08;A&#xff09;、客户&#xff08;D&#xff09;和供应商&#xff08;K&#xff09;&#xff0c;在创建会计科目时可在”控制数据“选项卡下进行选择。 在创建客户/供应商主数据的时候&#…

Vue插槽Slot的使用

1、认识插槽Slot 在开发中&#xff0c;我们会经常封装一个个可复用的组件&#xff1a; 前面我们会通过props传递给组件一些数据&#xff0c;让组件来进行展示&#xff1b;但是为了让这个组件具备更强的通用性&#xff0c;我们不能将组件中的内容限制为固定的div、span等等这些…

【零基础】学python数据结构与算法笔记15-欧几里得、RSA

文章目录前言95.欧几里得算法96.RSA算法介绍97.RSA算法测试98.算法课程总结总结前言 学习python数据结构与算法&#xff0c;学习常用的算法&#xff0c; b站学习链接 95.欧几里得算法 求最大公约数 欧几里得算法&#xff1a;gcd(a,b) gcd(b,a mod b) #mod取余 例&#xff1a…