一、定义
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器模式或者多态工厂模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
二、模式结构
工厂方法包含如下角色:
1.Product(抽象产品)
抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的共同父类或接口
2.ConcreteProduct(具体产品)
具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间一一对应。
3.Factory(抽象工厂)
在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,它与应用程序无关。任何在模式中创建对象的工厂类都必须实现该接口。
4.ConcreteFactory(具体工厂)
具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。在具体工厂类中包含与应用程序密切相关的逻辑,并且接受应用程序调用以创建产品。
三、优缺点
1.优点:
(1)工厂方法向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
(2)工厂可用自主确定创建何种产品对象
(3)完全符合“开闭原则”。
2.缺点:
(1)系统中的类的个数将成对增加,在一定程度上增加了系统的复杂度。
(2)增加了系统的抽象性和理解难度
四、试用环境
1.一个类不知道它所需要的对象的类
2.一个类通过其子类来指定创建哪个对象
3.将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可用无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
五、案例-日志记录器
题目描述:某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,请使用工厂方法模式设计该系统。
全部代码:点击这里
实例类图:
抽象工厂:LogFactory.php
<?php
interface LogFactory{
public function createLog();
}
抽象产品:Logger.php
<?php
interface Logger{
public function writeLog();
}
具体产品:数据库日志记录器 DatabaseLog.php
<?php
require_once "Logger.php";
class DatabaseLog implements Logger
{
public function writeLog(){
echo "数据库记录日志中...";
}
}
具体产品:文件日志记录器 FileLog.php
<?php
require_once "Logger.php";
class FileLog implements Logger
{
public function writeLog(){
echo "文件记录日志中...";
}
}
具体工厂:数据库工厂 DatabaseLogFactory.php
<?php
require_once "LogFactory.php";
require_once "DatabaseLog.php";
class DatabaseLogFactory implements LogFactory
{
public function createLog(){
return new DatabaseLog();
}
}
具体工厂:文件工厂 FileLogFactory.php
<?php
require_once "LogFactory.php";
require_once "FileLog.php";
class FileLogFactory implements LogFactory
{
public function createLog(){
return new FileLog();
}
}
配置文件:用户通过修改配置文件中的工厂名称可以灵活地更换日志记录方式 config.php
<?php
return[
'a'=>'FileLogFactory',
'b'=>'DatabaseLogFactory',
'c'=>'aLogFactory'
];
客户端:Client.php
<?php
class Client{
public static function main(){
$config=require_once "config.php";
foreach ($config as $val){
$file=$val.".php";
if(is_file("./$file")){//config文件里的文件名存在时
include "$val.php";
$file=new $val();
$files=$file->createLog();
echo $files->writeLog();
echo "\n";
}else{
echo "您输入的工厂名未记录,是否创建新的工厂呢?";
fwrite(STDOUT, "请输入‘Y’or‘N’ ");
$lan = trim(fgets(STDIN));//输入的字符
if($lan=='Y'){
//判断输入的工厂名称是否合法
if(substr($val,-10,10)!=='LogFactory'){
echo "您输入的工厂名称有问题,请以LogFactory结尾!";
}else{
$nameA=substr($val,0,strlen($val)-10);//将工厂名切割后的前一部分名字
if($lan=='Y'){
$fileLog=$nameA."Log";//具体产品
$fileFactory=$nameA."LogFactory";//具体工厂
//创建具体产品
$a= fopen($fileLog.".php", "w") or die("Unable to open file!");
//写入文件内容
$fileLogText="<?php
require_once 'Logger.php';
class $fileLog implements Logger
{
public function writeLog(){
echo '$fileLog 工作中';
}
}";
fwrite($a, $fileLogText);//写入
//创建具体工厂
$b= fopen($fileFactory.".php", "w") or die("Unable to open file!");
$fileFactoryText="<?php
require_once \"LogFactory.php\";
require_once \"$fileLog.php\";
class $fileFactory implements LogFactory
{
public function createLog(){
return new $fileLog();
}
}";
fwrite($b,$fileFactoryText);
echo "创建工厂成功\n";
}
}
}elseif ($lan=='N'){
echo "谢谢使用";
}else{
echo "请输入Y OR N 哦";
}
}
}
}
}
Client::main();
说明:客户端会根据配置文件中传入的工厂名做出相应的判断,若工厂名已经存在则进行相应的正确反馈。若不存在则询问客户是否需要新建工厂,客户回答Y则会判断传入的工厂名是否合法(以LogFactory结尾),合法则创建具体工厂和具体产品。不合法则给出提示。用户回答N则退出询问。用户回答其它答案则给出提示后退出询问。具体实现还有许多不足,若有更好的方法望提出。
祝天天开心~