工厂方法模式
是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
假设你正在开发一款物流管理应用。最初版本只能处理卡车 运输,因此大部分代码都在位于名为 卡车 的类中。 一段时间后,这款应用变得极受欢迎。你每天都能收到十几 次来自海运公司的请求,希望应用能够支持海上物流功能。
:-: 如果代码其余部分与现有类已经存在耦合关系,那么向程序 中添加新类其实并没有那么容易。
这可是个好消息。但是代码问题该如何处理呢?目前,大部 分代码都与 卡车 类相关。在程序中添加 轮船 类需要修改 全部代码。更糟糕的是,如果你以后需要在程序中支持另外 一种运输方式,很可能需要再次对这些代码进行大幅修改。
最后,你将不得不编写繁复的代码,根据不同的运输对象类, 在应用中进行不同的处理。
解决方案
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函 数的直接调用(即使用 new 运算符)。不用担心,对象仍将 通过 new 运算符创建,只是该运算符改在工厂方法中调用 罢了。工厂方法返回的对象通常被称作“产品”。
:-: 子类可以修改工厂方法返回的对象类型。
乍看之下,这种更改可能毫无意义:我们只是改变了程序中 调用构造函数的位置而已。但是,仔细想一下,现在你可以 在子类中重写工厂方法,从而改变其创建产品的类型。
但有一点需要注意:仅当这些产品具有共同的基类或者接口 时,子类才能返回不同类型的产品,同时基类中的工厂方法 还应将其返回类型声明为这一共有接口。
:-: 所有产品都必须使用同一接口。
举例来说, 卡车 Truck 和 轮船 Ship 类都必须实现 运输 Transport 接口, 该接口声明了一个名为 deliver 交 付 的 方 法。 每 个 类 都 将 以 不 同 的 方 式 实 现 该 方 法: 卡 车 走 陆 路 交 付 货 物, 轮 船 走 海 路 交 付 货 物。 陆路运输 RoadLogistics 类中的工厂方法返回卡车对象,而 海路运输 SeaLogistics 类则返回轮船对象。
调用工厂方法的代码(通常被称为客户端代码)无需了解不 同子类返回实际对象之间的差别。客户端将所有产品视为抽 象的 运输 。 客户端知道所有运输对象都提供 交付 方法, 但是并不关心其具体实现方式。
产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。
具体产品(Concrete Products)是产品接口的不同实现。
创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。
你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。
注意,尽管它的名字是创建者,但他最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。
工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。
方法也可以返回缓存、对象池或其他来源的已有对象。
<?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", "******"));