静态工厂模式(Static Factory)
模式定义
使用静态方法来创建一系列相关或相互依赖的对象(这些对象通常实现了同一个接口)。
UML 图
代码
抽象产品 FormatterInterface
<?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* 抽象产品 FormatterInterface
*/
interface FormatterInterface
{
public function format(string $input): string;
}
具体产品 FormatString
<?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* 具体产品 FormatString
*/
class FormatString implements FormatterInterface
{
public function format(string $input): string
{
return $input;
}
}
具体产品 FormatNumber
<?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* 具体产品 FormatNumber
*/
class FormatNumber implements FormatterInterface
{
public function format(string $input): string
{
return number_format((int) $input);
}
}
静态工厂 StaticFactory
<?php
namespace DesignPatterns\Creational\StaticFactory;
use Exception;
/**
* 静态工厂 StaticFactory
* 使用单一静态方法,根据传入的参数,通过条件语句确定要创建的对象
* 甚至不需要外部传参,直接在工厂内部读取应用的配置项来确定要创建的对象
*/
final class StaticFactory
{
/**
* @param string $type
* @return FormatterInterface
* @throws Exception
*/
public static function factory(string $type): FormatterInterface
{
if ($type == 'number') {
return new FormatNumber();
} elseif ($type == 'string') {
return new FormatString();
}
throw new Exception('Unknown format given');
}
}
测试
<?php
namespace DesignPatternsTest\Creational\StaticFactory;
use DesignPatterns\Creational\StaticFactory\FormatNumber;
use DesignPatterns\Creational\StaticFactory\FormatString;
use DesignPatterns\Creational\StaticFactory\StaticFactory;
use PHPUnit\Framework\TestCase;
use Exception;
class StaticFactoryTest extends TestCase
{
/**
* @throws Exception
*/
public function testCanCreateNumberFormatter()
{
$this->assertInstanceOf(FormatNumber::class, StaticFactory::factory('number'));
}
/**
* @throws Exception
*/
public function testCanCreateStringFormatter()
{
$this->assertInstanceOf(FormatString::class, StaticFactory::factory('string'));
}
/**
* @throws Exception
*/
public function testException()
{
$this->expectException(Exception::class);
StaticFactory::factory('object');
}
}
优缺点
缺点:
多次调用会生成多个不同的实例(需要多次调用生成相同的实例的场景请使用单例模式或多例模式)。
当需要扩展新的产品时需要修改工厂类,不符合开闭原则(有一种静态工厂模式的变体,通过约定命名规则的方式可以实现扩展新的产品时不需要修改工厂类)
优点:
- 可以全局使用且不需要事先实例化
其他
静态工厂模式还有一些其他形式的变体:
使用多个的静态方法创建不同的对象
当需要扩展新的类时需要修改工厂类,同样不符合开闭原则
<?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* 静态工厂 StaticFactoryMultipleCreate 使用多个的静态方法创建不同的对象
*/
final class StaticFactoryMultipleCreate
{
/**
* @return FormatterInterface
*/
public static function createString(): FormatterInterface
{
return new FormatString();
}
/**
* @return FormatterInterface
*/
public static function createNumber(): FormatterInterface
{
return new FormatNumber();
}
}
使用单一静态方法,根据传入参数,通过字符串拼接类名,创建对应的对象
当需要扩展新的类时不需要修改工厂类,不过需要类名符合命名规则
<?php
namespace DesignPatterns\Creational\StaticFactory;
use Exception;
/**
* 静态工厂 StaticFactorySpecialFormat
* 使用单一静态方法,根据传入参数,通过字符串拼接类名,创建对应的对象
* 甚至不需要外部传参,直接在工厂内部读取应用的配置项来确定要创建的对象
* 使用此方法可以方便的扩展新的类而不需要修改工厂类(类的命名需要遵从特定的格式)
*/
final class StaticFactorySpecialFormat
{
/**
* @param string $type
*
* @return FormatterInterface
* @throws Exception
*/
public static function factory(string $type): FormatterInterface
{
$className = __NAMESPACE__ . '\\Format' . ucfirst(strtolower($type));
if (!class_exists($className) || !is_subclass_of($className, FormatterInterface::class, true)) {
throw new Exception('Unknown format given');
}
return new $className;
}
}
使用魔术方法 __callStatic 创建方法名对应的对象
当需要扩展新的类时不需要修改工厂类,不过需要类名符合命名规则
<?php
namespace DesignPatterns\Creational\StaticFactory;
use Exception;
/**
* 静态工厂 StaticFactoryCallStatic 使用魔术方法 __callStatic 创建方法名对应的对象
*
* @method static FormatterInterface string()
* @method static FormatterInterface number()
*/
final class StaticFactoryCallStatic
{
/**
* @param $name
* @param $args
* @return FormatterInterface
* @throws Exception
*/
public static function __callStatic($name, $args) : FormatterInterface
{
$className = __NAMESPACE__ . '\\Format' . ucfirst(strtolower($name));
if (!class_exists($className) || !is_subclass_of($className, FormatterInterface::class, true)) {
throw new Exception('Unknown format given');
}
return new $className;
}
}
简单工厂模式
简单工厂模式与静态工厂模式非常类似,区别仅仅是静态和非静态(网上部分文章干脆直接把静态工厂模式叫做简单工厂模式)
简单工厂模式的缺点与静态工厂模式相同:
多次调用会生成多个不同的实例(需要多次调用生成相同的实例的场景请使用单例模式或多例模式)。
当需要扩展新的产品时需要修改工厂类,不符合开闭原则(有一种静态工厂模式的变体,通过约定命名规则的方式可以实现扩展新的产品时不予修改工厂类)
简单工厂模式的优点点与静态工厂模式不同:
静态工厂模式是静态的,它的优势是可以全局使用且不需要事先实例化。
简单工厂模式是非静态的,因此一个工厂可以有多个实例,还可以通过传递参数使它们各不相同。还可以通过继承扩展工厂来改变工厂的行为,并用它来创建测试时会使用到的模拟对象(mock)
工厂方法模式
模式定义
工厂方法分离了创建者类与其负责创建的产品类。为每一个产品提供一个独立的工厂类,通过不同的工厂实例来创建不同的产品实例
创建者是一个工厂接口,它定义了用于生成产品对象的抽象方法,每个创建者子类负责实例化一个相应的产品子类。
工厂方法模式遵循 S.O.L.I.D 原则中的 「依赖倒置原则」。客户端代码依赖工厂接口,而不是依赖具体的产品类
UML 图
代码
抽象产品 Logger
<?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* 抽象产品 Logger
*/
interface Logger
{
public function log(string $message);
}
具体产品 FileLogger
<?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* 具体产品 FileLogger
*/
class FileLogger implements Logger
{
protected $fileName;
public function __construct(string $fileName)
{
$this->fileName = $fileName;
}
public function log(string $message)
{
file_put_contents($this->fileName, $message . PHP_EOL, FILE_APPEND);
}
}
抽象工厂 LoggerFactory
<?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* 抽象工厂 LoggerFactory
*/
interface LoggerFactory
{
public function createLogger(): Logger;
}
具体工厂 FileLoggerFactory
<?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* 具体工厂 FileLoggerFactory
*/
class FileLoggerFactory implements LoggerFactory
{
protected $fileName;
public function __construct(string $fileName)
{
$this->fileName = $fileName;
}
public function createLogger(): Logger
{
return new FileLogger($this->fileName);
}
}
测试
<?php
namespace DesignPatternsTest\Creational\FactoryMethod;
use DesignPatterns\Creational\FactoryMethod\FileLoggerFactory;
use DesignPatterns\Creational\FactoryMethod\Logger;
use PHPUnit\Framework\TestCase;
class FactoryMethodTest extends TestCase
{
public function testCanCreateFileLogger()
{
$loggerFactory = new FileLoggerFactory(sys_get_temp_dir());
$logger = $loggerFactory->createLogger();
$this->assertInstanceOf(Logger::class, $logger);
}
public function testFileLoggerWritten()
{
$fileName = sys_get_temp_dir() . '/test.log';
$message = date('Y-m-d H:i:s');
$loggerFactory = new FileLoggerFactory($fileName);
$loggerFactory->createLogger()->log($message);
$fp = fopen($fileName, 'r');
fseek($fp,-1,SEEK_END);
$str = '';
while(($c = fgetc($fp)) !== false)
{
if($c == "\n" && $str) break;
$str = $c . $str;
fseek($fp, -2, SEEK_CUR);
}
fclose($fp);
$this->assertEquals($str, $message . PHP_EOL);
}
}
优缺点
优点:
避免创建者和具体产品之间的紧密耦合。
单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型
抽象工厂模式
产品族
产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。
更详细的产品族概念参见 https://blog.csdn.net/wjlsxl_whb/article/details/52934941
模式定义
为每一个产品族提供一个独立的工厂类,通过不同的工厂实例来创建不同的产品族中的每个产品实例
代码
抽象产品 ChairInterface
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 抽象产品 ChairInterface
*/
interface ChairInterface
{
public function sitOn();
}
具体产品 ModernChair
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 具体产品 ModernChair
*/
class ModernChair implements ChairInterface
{
public function sitOn()
{
return '坐在现代风格的椅子上';
}
}
具体产品 VictorianChair
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 具体产品 VictorianChair
*/
class VictorianChair implements ChairInterface
{
public function sitOn()
{
return '坐在维多利亚风格的椅子上';
}
}
抽象产品 DeskInterface
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 抽象产品 DeskInterface
* @package DesignPatterns\Creational\AbstractFactory
*/
interface DeskInterface
{
public function study();
}
具体产品 ModernDesk
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 具体产品 ModernDesk
*/
class ModernDesk implements DeskInterface
{
public function study()
{
return '在现代风格的书桌上学习';
}
}
具体产品 VictorianDesk
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 具体产品 VictorianDesk
*/
class VictorianDesk implements DeskInterface
{
public function study()
{
return '在维多利亚风格的书桌上学习';
}
}
抽象工厂 AbstractFactory
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 抽象工厂 AbstractFactory
*/
interface AbstractFactory
{
public function createChair(): ChairInterface;
public function createDesk(): DeskInterface;
}
具体工厂 ModernFactory
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 具体工厂 ModernFactory
*/
class ModernFactory implements AbstractFactory
{
public function createChair(): ChairInterface
{
return new ModernChair();
}
public function createDesk(): DeskInterface
{
return new ModernDesk();
}
}
具体工厂 victorianFactory
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 具体工厂 victorianFactory
*/
class victorianFactory implements AbstractFactory
{
public function createChair(): ChairInterface
{
return new victorianChair();
}
public function createDesk(): DeskInterface
{
return new victorianDesk();
}
}
测试
<?php
namespace DesignPatternsTest\Creational\AbstractFactory;
use DesignPatterns\Creational\AbstractFactory\ChairInterface;
use DesignPatterns\Creational\AbstractFactory\ModernFactory;
use DesignPatterns\Creational\AbstractFactory\victorianFactory;
use PHPUnit\Framework\TestCase;
class AbstractFactoryTest extends TestCase
{
public function testCreateModernChair()
{
$modernFactory = new ModernFactory();
$this->assertInstanceOf(ChairInterface::class, $modernFactory->createChair());
}
public function testSitOnVictorianChair()
{
$victorianFactory = new victorianFactory();
$this->assertEquals($victorianFactory->createChair()->sitOn(), '坐在维多利亚风格的椅子上');
}
public function testStudyOnVictorianDesk()
{
$victorianFactory = new victorianFactory();
$this->assertEquals($victorianFactory->createDesk()->study(), '在维多利亚风格的书桌上学习');
}
}
工厂模式的对比
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。
三种工厂的比较:
简单工厂 :仅有一个具体工厂类,生产同一等级结构中的任意产品。增加新的产品,需要修改工厂类
工厂方法 :有多个具体工厂类,每个工厂类只能生产同一等级结构中的固定产品。增加新的产品,不需要修改工厂类
抽象工厂 :有多个具体工厂类,每个工厂类可以生产同一产品族的所有产品(同一产品族的每个产品分属各自产品树的不同等级结构)。增加新的产品,需要修改工厂类;但是增加产品族,不需要修改工厂类