工厂方法
意图
《设计模式》一书对工厂方法的描述如下
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延伸到其子类。
定义一个用于创建对象的接口
<?php
abstract class AbstractCreator
{
abstract public function factoryMethod() : AbstractProduct;
}
让子类决定实例化哪个类
<?php
class ConcreteCreator extends AbstractCreator
{
public function factoryMethod(): ConcreteProduct
{
return new ConcreteProduct();
}
}
最直观的区别就是在未使用工厂模式时,我们通常在客户端直接对类进行实例化
<?php
function client(){
$product = new ConcreteProduct();
}
而工厂方法则将实例化对象转移到对应的工厂子类中
<?php
function client(AbstractCreator $creator){
$creator->someOperation();
}
client(new ContreteCreator());
假如你想要使用不同的产品,如果不使用工厂方法,那么你就要修改客户端的代码。而工厂方法可以让你在工厂类中轻松的重写工厂方法,而不改变客户端的代码。
注意,工厂方法实例化对象不一定需要使用构造函数返回新的实例,也可以结合单例模式来返回之前的实例。而这一切,对客户端都不会造成影响。
由此可见,工厂方法体现了诸多设计原则
- 单一职责。用专门的工厂类来创建产品。
- 开闭原则。在不修改客户端的情况下,可以增加新的产品类型。
- 依赖倒置。客户端只需要依赖抽象的类,而不需要依赖具体的类。
工厂方法针对的粒度是「单个类的创建」,而在这个基础上进行扩展,就变成了其他设计模式,例如,针对多个相关类创建的抽象工厂,针对复杂对象的分步骤生成的生成器模式等等。
实现
Product
- 要创建的产品的接口
<?php
interface Product
{
public function operation() : string;
}
ConcreteProduct
- 具体的产品
<?Php
class ConcreteProduct1 implements Product
{
public function operation(): string
{
return "ConcreteProduct1";
}
}
class ConcreteProduct2 implements Product
{
public function operation(): string
{
return "ConcreteProduct2";
}
}
Creator
- 抽象的工厂类,定义的工厂方法用来返回 Product
<?php
abstract class Creator
{
abstract public function factoryMethod() : Product;
public function someOperation()
{
$product = $this->factoryMethod();
return $product->operation();
}
}
ConcreteCreator
- 具体的工厂类,返回具体的 ConcreteProduct。
<?php
class ConcreteCreator1 extends Creator
{
public function factoryMethod(): Product
{
return new ConcreteProduct1();
}
}
class ConcreateCreator2 extends Creator
{
public function factoryMethod(): Product
{
return new ConcreteProduct2();
}
}
应用
我在本地创建了一篇文章,现在要将其发布到网络平台上,对应的平台有两个,Github 和语雀,每个平台都要进行授权、发布文章的操作。如何运用工厂方法来实现呢?
- 抽象的 Creator - 抽象的博客发布类
- 具体的 Creator - GIthub 发布类和语雀发布类
- 抽象的 Product - 博客链接器,用于实现链接平台、发布文章的操作
- 具体的 Product - Github 链接器和语雀连接器
博客发布器的主要职责是发布文章,但是 connection
方法相当于工厂方法,返回对应的 Connector 实例。
<?php
// 抽象工厂
abstract class Poster
{
// 工厂方法
abstract public function connection() : Connector;
public function publish($essay)
{
$client = $this->connection();
$client->init();
$client->publish($essay);
}
}
class GithubPoster extends Poster
{
private $email;
private $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function connection() : Connector
{
return new GithubConnector($this->email, $this->password);
}
}
class YuquePoster extends Poster
{
private $token;
public function __construct(string $token)
{
$this->token = $token;
}
public function connection() : Connector
{
return new YuqueConnector($this->token);
}
}
而连接器则相当于 Product
<?php
// 产品
interface Connector
{
public function init();
public function publish($content);
}
class GithubConnector implements Connector
{
private $email;
private $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function init()
{
echo "使用邮箱{$this->email}和密码进行授权";
}
public function publish($content)
{
echo "Github 文章发布成功{$content}";
}
}
class YuqueConnector implements Connector
{
private $token;
public function __construct(string $token)
{
$this->token = $token;
}
public function init()
{
echo "使用 Token 进行授权";
}
public function publish($content)
{
echo "语雀文章发布成功:{$content}";
}
}
测试
<?php
// 客户端
function client(Poster $poster)
{
$poster->publish('测试');
}
client(new YuquePoster('token'));
通过这个例子,我们可以发现,虽然博客发布器扮演了创建者的角色,但是它的主要角色是发布文章,而不是创建对应的网络连接,由此可见,工厂方法类中往往包括了其他业务逻辑,通过工厂方法,可以让具体的业务逻辑和底层的服务分离出来。
总结
工厂方法是设计原则的体现,体现出了单一职责、开闭原则、依赖倒置等原则,因此被广泛应用。一些常用的使用场景如下:
- 实现对象的复用。例如,在工厂方法中实现对对象的复用,避免重复创建新的对象,可用于一些资源密集型的场景(数据库连接、文件系统和网络资源等)
- 实现对子类的扩展。当你应用存在多个子类,或者你希望能够对子类进行扩展时,可采用工厂方法。