**工厂方法模式**
是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
*****
假设你正在开发一款物流管理应用。最初版本只能处理卡车 运输,因此大部分代码都在位于名为 卡车 的类中。 一段时间后,这款应用变得极受欢迎。你每天都能收到十几 次来自海运公司的请求,希望应用能够支持海上物流功能。
![](images/1_1636251660415.png)
如果代码其余部分与现有类已经存在耦合关系,那么向程序 中添加新类其实并没有那么容易。
这可是个好消息。但是代码问题该如何处理呢?目前,大部 分代码都与 卡车 类相关。在程序中添加 轮船 类需要修改 全部代码。更糟糕的是,如果你以后需要在程序中支持另外 一种运输方式,很可能需要再次对这些代码进行大幅修改。
最后,你将不得不编写繁复的代码,根据不同的运输对象类, 在应用中进行不同的处理。
*****
**解决方案**
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函 数的直接调用(即使用 new 运算符)。不用担心,对象仍将 通过 new 运算符创建,只是该运算符改在工厂方法中调用 罢了。工厂方法返回的对象通常被称作“产品”。
![](images/1_1636251786022.png)
子类可以修改工厂方法返回的对象类型。
乍看之下,这种更改可能毫无意义:我们只是改变了程序中 调用构造函数的位置而已。但是,仔细想一下,现在你可以 在子类中重写工厂方法,从而改变其创建产品的类型。
但有一点需要注意:仅当这些产品具有共同的基类或者接口 时,子类才能返回不同类型的产品,同时基类中的工厂方法 还应将其返回类型声明为这一共有接口。
![](images/1_1636252140282.png)
所有产品都必须使用同一接口。
举例来说, 卡车 Truck 和 轮船 Ship 类都必须实现 运输 Transport 接口, 该接口声明了一个名为 deliver 交 付 的 方 法。 每 个 类 都 将 以 不 同 的 方 式 实 现 该 方 法: 卡 车 走 陆 路 交 付 货 物, 轮 船 走 海 路 交 付 货 物。 陆路运输 RoadLogistics 类中的工厂方法返回卡车对象,而 海路运输 SeaLogistics 类则返回轮船对象。
![](images/1_1636252991360.png)
调用工厂方法的代码(通常被称为客户端代码)无需了解不 同子类返回实际对象之间的差别。客户端将所有产品视为抽 象的 运输 。 客户端知道所有运输对象都提供 交付 方法, 但是并不关心其具体实现方式。
*****
![](images/1_1636253019808.png)
1. 产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。
2. 具体产品(Concrete Products)是产品接口的不同实现。
3. 创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。
你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。
注意,尽管它的名字是创建者,但他最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。
工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。
4. 具体创建者(Concrete Creators) 将会重写基础工厂方法,使其返回不同类型的产品。注意,并不一定每次调用工厂方法都会创建新的实例。工厂
方法也可以返回缓存、对象池或其他来源的已有对象。
*****
### **代码示例**
JAVA版:
## 生成跨平台的 GUI 元素
在本例中,按钮担任产品的角色,对话框担任创建者的角色。
不同类型的对话框需要其各自类型的元素。因此我们可为每个对话框类型创建子类并重写其工厂方法。
现在,每种对话框类型都将对合适的按钮类进行初始化。对话框基类使用其通用接口与对象进行交互,因此代码更改后仍能正常工作。
buttons/Button.java: 通用产品接口
```
package design_pattern.factory_method.example.buttons;
/**
* Common interface for all buttons.
*/
public interface Button {
void render();
void onClick();
}
```
buttons/HtmlButton.java: 具体产品
```
package design_pattern.factory_method.example.buttons;
/**
* HTML button implementation.
*/
public class HtmlButton implements Button {
public void render() {
System.out.println("<button>Test Button</button>");
onClick();
}
public void onClick() {
System.out.println("Click! Button says - 'Hello World!'");
}
}
```
buttons/WindowsButton.java: 另一个具体产品
```
package design_pattern.factory_method.example.buttons;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Windows button implementation.
*/
public class WindowsButton implements Button {
JPanel panel = new JPanel();
JFrame frame = new JFrame();
JButton button;
public void render() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello World!");
label.setOpaque(true);
label.setBackground(new Color(235, 233, 126));
label.setFont(new Font("Dialog", Font.BOLD, 44));
label.setHorizontalAlignment(SwingConstants.CENTER);
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
frame.getContentPane().add(panel);
panel.add(label);
onClick();
panel.add(button);
frame.setSize(320, 200);
frame.setVisible(true);
onClick();
}
public void onClick() {
button = new JButton("Exit");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
System.exit(0);
}
});
}
}
```
factory/Dialog.java: 基础创建者
```
package design_pattern.factory_method.example.factory;
import design_pattern.factory_method.example.buttons.Button;
/**
* Base factory class. Note that "factory" is merely a role for the class. It
* should have some core business logic which needs different products to be
* created.
*/
public abstract class Dialog {
public void renderWindow() {
// ... other code ...
Button okButton = createButton();
okButton.render();
}
/**
* Subclasses will override this method in order to create specific button
* objects.
*/
public abstract Button createButton();
}
```
factory/HtmlDialog.java: 具体创建者
```
package design_pattern.factory_method.example.factory;
import design_pattern.factory_method.example.buttons.Button;
import design_pattern.factory_method.example.buttons.HtmlButton;
/**
* HTML Dialog will produce HTML buttons.
*/
public class HtmlDialog extends Dialog {
@Override
public Button createButton() {
return new HtmlButton();
}
}
```
factory/WindowsDialog.java: 另一个具体创建者
```
package design_pattern.factory_method.example.factory;
import design_pattern.factory_method.example.buttons.Button;
import design_pattern.factory_method.example.buttons.WindowsButton;
/**
* Windows Dialog will produce Windows buttons.
*/
public class WindowsDialog extends Dialog {
@Override
public Button createButton() {
return new WindowsButton();
}
}
```
Demo.java: 客户端代码
```
package design_pattern.factory_method.example;
import design_pattern.factory_method.example.factory.Dialog;
import design_pattern.factory_method.example.factory.HtmlDialog;
import design_pattern.factory_method.example.factory.WindowsDialog;
/**
* Demo class. Everything comes together here.
*/
public class Demo {
private static Dialog dialog;
public static void main(String[] args) {
configure();
runBusinessLogic();
}
/**
* The concrete factory is usually chosen depending on configuration or
* environment options.
*/
static void configure() {
if (System.getProperty("os.name").equals("Windows 10")) {
dialog = new WindowsDialog();
} else {
dialog = new HtmlDialog();
}
}
/**
* All of the client code should work with factories and products through
* abstract interfaces. This way it does not care which factory it works
* with and what kind of product it returns.
*/
static void runBusinessLogic() {
dialog.renderWindow();
}
}
```
OutputDemo.txt: 执行结果 (HtmlDialog)
```
<button>Test Button</button>
Click! Button says - 'Hello World!'
```
![](https://img.kancloud.cn/65/bd/65bd7e18262a1a19ff241c4477f63b66_306x193.png)
*****
PHP版:
```
<?php
/**
* Factory Method Design Pattern
*
* Intent: Provides an interface for creating objects in a superclass, but
* allows subclasses to alter the type of objects that will be created.
*
* Example: In this example, the Factory Method pattern provides an interface
* for creating social network connectors, which can be used to log in to the
* network, create posts and potentially perform other activities—and all of
* this without coupling the client code to specific classes of the particular
* social network.
*/
/**
* The Creator declares a factory method that can be used as a substitution for
* the direct constructor calls of products, for instance:
*
* - Before: $p = new FacebookConnector();
* - After: $p = $this->getSocialNetwork;
*
* This allows changing the type of the product being created by
* SocialNetworkPoster's subclasses.
*/
abstract class SocialNetworkPoster
{
/**
* The actual factory method. Note that it returns the abstract connector.
* This lets subclasses return any concrete connectors without breaking the
* superclass' contract.
*/
abstract public function getSocialNetwork(): SocialNetworkConnector;
/**
* When the factory method is used inside the Creator's business logic, the
* subclasses may alter the logic indirectly by returning different types of
* the connector from the factory method.
*/
public function post($content): void
{
// Call the factory method to create a Product object...
$network = $this->getSocialNetwork();
// ...then use it as you will.
$network->logIn();
$network->createPost($content);
$network->logout();
}
}
/**
* This Concrete Creator supports Facebook. Remember that this class also
* inherits the 'post' method from the parent class. Concrete Creators are the
* classes that the Client actually uses.
*/
class FacebookPoster extends SocialNetworkPoster
{
private $login, $password;
public function __construct(string $login, string $password)
{
$this->login = $login;
$this->password = $password;
}
public function getSocialNetwork(): SocialNetworkConnector
{
return new FacebookConnector($this->login, $this->password);
}
}
/**
* This Concrete Creator supports LinkedIn.
*/
class LinkedInPoster extends SocialNetworkPoster
{
private $email, $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function getSocialNetwork(): SocialNetworkConnector
{
return new LinkedInConnector($this->email, $this->password);
}
}
/**
* The Product interface declares behaviors of various types of products.
*/
interface SocialNetworkConnector
{
public function logIn(): void;
public function logOut(): void;
public function createPost($content): void;
}
/**
* This Concrete Product implements the Facebook API.
*/
class FacebookConnector implements SocialNetworkConnector
{
private $login, $password;
public function __construct(string $login, string $password)
{
$this->login = $login;
$this->password = $password;
}
public function logIn(): void
{
echo "Send HTTP API request to log in user $this->login with " .
"password $this->password\n";
}
public function logOut(): void
{
echo "Send HTTP API request to log out user $this->login\n";
}
public function createPost($content): void
{
echo "Send HTTP API requests to create a post in Facebook timeline.\n";
}
}
/**
* This Concrete Product implements the LinkedIn API.
*/
class LinkedInConnector implements SocialNetworkConnector
{
private $email, $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function logIn(): void
{
echo "Send HTTP API request to log in user $this->email with " .
"password $this->password\n";
}
public function logOut(): void
{
echo "Send HTTP API request to log out user $this->email\n";
}
public function createPost($content): void
{
echo "Send HTTP API requests to create a post in LinkedIn timeline.\n";
}
}
/**
* The client code can work with any subclass of SocialNetworkPoster since it
* doesn't depend on concrete classes.
*/
function clientCode(SocialNetworkPoster $creator)
{
// ...
$creator->post("Hello world!");
$creator->post("I had a large hamburger this morning!");
// ...
}
/**
* During the initialization phase, the app can decide which social network it
* wants to work with, create an object of the proper subclass, and pass it to
* the client code.
*/
echo "Testing ConcreteCreator1:\n";
clientCode(new FacebookPoster("john_smith", "******"));
echo "\n\n";
echo "Testing ConcreteCreator2:\n";
clientCode(new LinkedInPoster("john_smith@example.com", "******"));
```
- 前言:为什么要学数据结构和算法?
- 第一章:数据结构和算法
- 什么是数据结构?
- 什么是算法?
- 1.从接口开始
- 2.算法分析
- 3.ArrayList
- 4.LinkedList
- 5.双链表
- 6.树的遍历
- 7.到达的哲学
- 8.索引器
- 9.Map接口
- 10.哈希
- 11.HashMap
- 12.TreeMap-二叉树
- 13.二叉搜索树
- 14.数据持久化
- 15.排序
- 第二章:经典算法解析
- 1.两数之和
- 2.两数相加
- 3.无重复字符的最长子字符串
- 4.两个排序数组的中值
- 5.最长回文子串
- 6.锯齿形变换
- 7.反转整数
- 8.合并K个排序列表
- 9.链表循环
- 10.除Self之外的数组乘积
- 11.4的威力
- 12.蛙跳
- 13.将交叉口大小设置为至少两个
- 14.最大的块,使其分类
- 15.到达点
- 16.阶乘零点函数的前像大小
- 17.建造一个大的岛屿
- 18.唯一字母串
- 19.树的距离之和
- 20.猜词游戏
- 21.节点的最短路径
- 22.矩形区域II
- 23.K-相似字符串
- 24.雇佣K工人的最低成本
- 25.至少为K的最短子阵
- 26.获取所有key的最短路径
- 27.加油站的最小数量
- 28.有利可图的计划
- 29.细分图中的可达节点
- 30.超级蛋掉落
- 31.最大频率叠加
- 32.有序队列
- 33.最多N个给定数字集的数字
- 34.DI序列的有效置换
- 35.猫和老鼠
- 第三章:高级算法解析
- 找出数组中重复的数字
- 不修改数组找出重复的数字
- 二维数组中的查找
- 替换空格
- 从尾到头打印链表
- 重建二叉树
- 二叉树的下一个结点
- 用两个栈实现队列
- 用两个队列实现栈用两个队列实现栈
- 斐波那契数列
- 跳台阶
- 变态跳台阶
- 矩形覆盖
- 旋转数组的最小数字
- 矩阵中的路径
- 机器人的移动范围
- 剪绳子
- 二进制中 1 的个数
- 数值的整数次方
- 打印从 1 到最大的 n 位数
- 在O(1)时间内删除链表节点
- 删除链表中重复的节点
- 正则表达式匹配
- 表示数值的字符串
- 调整数组顺序使奇数位于偶数前面
- 链表中倒数第k个结点
- 链表中环的入口结点
- 反转链表
- 合并两个排序的链表
- 树的子结构
- 二叉树的镜像
- 对称的二叉树
- 顺时针打印矩阵
- 包含min函数的栈
- 栈的压入、弹出序列
- 不分行从上到下打印二叉树
- 把二叉树打印成多行
- 按之字形打印二叉树
- 二叉搜索树的后序遍历序列
- 二叉树中和为某一值的路径
- 复杂链表的复制
- 二叉搜索树与双向链表
- 序列化二叉树
- 字符串的排列
- 数组中出现次数超过一半的数字
- 获取数组中最小的k个数
- 数据流中的中位数
- 连续子数组的最大和
- 整数中1出现的次数
- 数字序列中某一位的数字
- 把数组排成最小的数
- 把数字翻译成字符串
- 礼物的最大价值
- 最长不含重复字符的子字符串
- 丑数
- 第一个只出现一次的字符
- 字符流中第一个不重复的字符
- 两个链表的第一个公共结点
- 数字在排序数组中出现的次数
- 0到n-1中缺失的数字
- 数组中数值和下标相等的元素
- 二叉搜索树的第k个结点
- 二叉树的深度
- 平衡二叉树
- 数组中只出现一次的两个数字
- 数组中唯一只出现一次的数字
- 和为S的两个数字
- 和为S的连续正数序列
- 翻转单词顺序
- 左旋转字符串
- 滑动窗口的最大值
- 扑克牌的顺子
- 第四章:设计模式
- 设计模式概述
- 创建型模式
- 工厂方法
- 抽象工厂
- 生成器
- 原型
- 单例
- 结构型模式
- 适配器
- 桥接
- 组合
- 装饰器
- 外观
- 享元
- 代理
- 行为模式
- 责任链
- 命令
- 迭代器
- 中介者
- 备忘录
- 观察者
- 状态
- 策略
- 模板方法
- 访问者
- 第五章:服务器运维
- 1.从vim编辑器开始
- 2.文本浏览器
- 3.Bash:Shell、.profile、.bashrc、.bash_history
- 4.Bash:处理文件,pwd,ls,cp,mv,rm,touch
- 5.Bash:环境变量,env,set,export
- 6.Bash:语言设置,LANG,locale,dpkg-reconfigure locales
- 7.Bash:重定向,stdin,stdout,stderr,tee,pv
- 8.更多的重定向和过滤:head,tail,awk,grep,sed
- 9.Bash:任务控制,jobs,fg
- 10.Bash:程序退出代码(返回状态)
- 11:总结
- 12.文档:man,info
- 13.文档:Google
- 14.包管理:Debian 包管理工具aptitude
- 15.系统启动:运行级别,/etc/init.d,rcconf,update-rc.d
- 16.处理进程,ps,kill
- 17.任务调度:cron,at
- 18.日志:/var/log,rsyslog,logger
- 19.文件系统:挂载,mount,/etc/fstab
- 20.文件系统:修改和创建文件系统,tune2fs,mkfs
- 21.文件系统:修改根目录,chroot
- 22.文件系统:移动数据,tar,dd
- 23.文件系统:权限,chown,chmod,umask
- 24.接口配置,ifconfig,netstat,iproute2,ss,route
- 25.网络:配置文件,/etc/network/interfaces
- 26.网络:封包过滤配置,iptables
- 27.安全 Shell,ssh,sshd,scp
- 28.性能:获取性能情况,uptime,free,top
- 29.内核:内核消息,dmesg
- 最后:打磨、洗练、重复:总复习
- 最终章:深入学习
- 算法思维导图
- 学习目标
- 学习路线
- 学习要点
- 学习大纲
- 资源推荐