当前位置:  首页>> 技术小册>> 经典设计模式PHP版

工厂方法模式
是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。


 
假设你正在开发一款物流管理应用。最初版本只能处理卡车 运输,因此大部分代码都在位于名为 卡车 的类中。 一段时间后,这款应用变得极受欢迎。你每天都能收到十几 次来自海运公司的请求,希望应用能够支持海上物流功能。

:-: 如果代码其余部分与现有类已经存在耦合关系,那么向程序 中添加新类其实并没有那么容易。

这可是个好消息。但是代码问题该如何处理呢?目前,大部 分代码都与 卡车 类相关。在程序中添加 轮船 类需要修改 全部代码。更糟糕的是,如果你以后需要在程序中支持另外 一种运输方式,很可能需要再次对这些代码进行大幅修改。
 
最后,你将不得不编写繁复的代码,根据不同的运输对象类, 在应用中进行不同的处理。


解决方案

工厂方法模式建议使用特殊的工厂方法代替对于对象构造函 数的直接调用(即使用 new 运算符)。不用担心,对象仍将 通过 new 运算符创建,只是该运算符改在工厂方法中调用 罢了。工厂方法返回的对象通常被称作“产品”。

:-: 子类可以修改工厂方法返回的对象类型。

乍看之下,这种更改可能毫无意义:我们只是改变了程序中 调用构造函数的位置而已。但是,仔细想一下,现在你可以 在子类中重写工厂方法,从而改变其创建产品的类型。
 
但有一点需要注意:仅当这些产品具有共同的基类或者接口 时,子类才能返回不同类型的产品,同时基类中的工厂方法 还应将其返回类型声明为这一共有接口。

:-: 所有产品都必须使用同一接口。

举例来说, 卡车 Truck 和 轮船 Ship 类都必须实现 运输 Transport 接口, 该接口声明了一个名为 deliver 交 付 的 方 法。 每 个 类 都 将 以 不 同 的 方 式 实 现 该 方 法: 卡 车 走 陆 路 交 付 货 物, 轮 船 走 海 路 交 付 货 物。 陆路运输 RoadLogistics 类中的工厂方法返回卡车对象,而 海路运输 SeaLogistics 类则返回轮船对象。

调用工厂方法的代码(通常被称为客户端代码)无需了解不 同子类返回实际对象之间的差别。客户端将所有产品视为抽 象的 运输 。 客户端知道所有运输对象都提供 交付 方法, 但是并不关心其具体实现方式。


  1. 产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。

  2. 具体产品(Concrete Products)是产品接口的不同实现。

  3. 创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。

你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。
 
注意,尽管它的名字是创建者,但他最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。
 
工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。

  1. 具体创建者(Concrete Creators) 将会重写基础工厂方法,使其返回不同类型的产品。注意,并不一定每次调用工厂方法都会创建新的实例。工厂

方法也可以返回缓存、对象池或其他来源的已有对象。


代码示例

  1. <?php
  2. /**
  3. * Factory Method Design Pattern
  4. *
  5. * Intent: Provides an interface for creating objects in a superclass, but
  6. * allows subclasses to alter the type of objects that will be created.
  7. *
  8. * Example: In this example, the Factory Method pattern provides an interface
  9. * for creating social network connectors, which can be used to log in to the
  10. * network, create posts and potentially perform other activities—and all of
  11. * this without coupling the client code to specific classes of the particular
  12. * social network.
  13. */
  14. /**
  15. * The Creator declares a factory method that can be used as a substitution for
  16. * the direct constructor calls of products, for instance:
  17. *
  18. * - Before: $p = new FacebookConnector();
  19. * - After: $p = $this->getSocialNetwork;
  20. *
  21. * This allows changing the type of the product being created by
  22. * SocialNetworkPoster's subclasses.
  23. */
  24. abstract class SocialNetworkPoster
  25. {
  26. /**
  27. * The actual factory method. Note that it returns the abstract connector.
  28. * This lets subclasses return any concrete connectors without breaking the
  29. * superclass' contract.
  30. */
  31. abstract public function getSocialNetwork(): SocialNetworkConnector;
  32. /**
  33. * When the factory method is used inside the Creator's business logic, the
  34. * subclasses may alter the logic indirectly by returning different types of
  35. * the connector from the factory method.
  36. */
  37. public function post($content): void
  38. {
  39. // Call the factory method to create a Product object...
  40. $network = $this->getSocialNetwork();
  41. // ...then use it as you will.
  42. $network->logIn();
  43. $network->createPost($content);
  44. $network->logout();
  45. }
  46. }
  47. /**
  48. * This Concrete Creator supports Facebook. Remember that this class also
  49. * inherits the 'post' method from the parent class. Concrete Creators are the
  50. * classes that the Client actually uses.
  51. */
  52. class FacebookPoster extends SocialNetworkPoster
  53. {
  54. private $login, $password;
  55. public function __construct(string $login, string $password)
  56. {
  57. $this->login = $login;
  58. $this->password = $password;
  59. }
  60. public function getSocialNetwork(): SocialNetworkConnector
  61. {
  62. return new FacebookConnector($this->login, $this->password);
  63. }
  64. }
  65. /**
  66. * This Concrete Creator supports LinkedIn.
  67. */
  68. class LinkedInPoster extends SocialNetworkPoster
  69. {
  70. private $email, $password;
  71. public function __construct(string $email, string $password)
  72. {
  73. $this->email = $email;
  74. $this->password = $password;
  75. }
  76. public function getSocialNetwork(): SocialNetworkConnector
  77. {
  78. return new LinkedInConnector($this->email, $this->password);
  79. }
  80. }
  81. /**
  82. * The Product interface declares behaviors of various types of products.
  83. */
  84. interface SocialNetworkConnector
  85. {
  86. public function logIn(): void;
  87. public function logOut(): void;
  88. public function createPost($content): void;
  89. }
  90. /**
  91. * This Concrete Product implements the Facebook API.
  92. */
  93. class FacebookConnector implements SocialNetworkConnector
  94. {
  95. private $login, $password;
  96. public function __construct(string $login, string $password)
  97. {
  98. $this->login = $login;
  99. $this->password = $password;
  100. }
  101. public function logIn(): void
  102. {
  103. echo "Send HTTP API request to log in user $this->login with " .
  104. "password $this->password\n";
  105. }
  106. public function logOut(): void
  107. {
  108. echo "Send HTTP API request to log out user $this->login\n";
  109. }
  110. public function createPost($content): void
  111. {
  112. echo "Send HTTP API requests to create a post in Facebook timeline.\n";
  113. }
  114. }
  115. /**
  116. * This Concrete Product implements the LinkedIn API.
  117. */
  118. class LinkedInConnector implements SocialNetworkConnector
  119. {
  120. private $email, $password;
  121. public function __construct(string $email, string $password)
  122. {
  123. $this->email = $email;
  124. $this->password = $password;
  125. }
  126. public function logIn(): void
  127. {
  128. echo "Send HTTP API request to log in user $this->email with " .
  129. "password $this->password\n";
  130. }
  131. public function logOut(): void
  132. {
  133. echo "Send HTTP API request to log out user $this->email\n";
  134. }
  135. public function createPost($content): void
  136. {
  137. echo "Send HTTP API requests to create a post in LinkedIn timeline.\n";
  138. }
  139. }
  140. /**
  141. * The client code can work with any subclass of SocialNetworkPoster since it
  142. * doesn't depend on concrete classes.
  143. */
  144. function clientCode(SocialNetworkPoster $creator)
  145. {
  146. // ...
  147. $creator->post("Hello world!");
  148. $creator->post("I had a large hamburger this morning!");
  149. // ...
  150. }
  151. /**
  152. * During the initialization phase, the app can decide which social network it
  153. * wants to work with, create an object of the proper subclass, and pass it to
  154. * the client code.
  155. */
  156. echo "Testing ConcreteCreator1:\n";
  157. clientCode(new FacebookPoster("john_smith", "******"));
  158. echo "\n\n";
  159. echo "Testing ConcreteCreator2:\n";
  160. clientCode(new LinkedInPoster("john_smith@example.com", "******"));