应用举例

yii\db\Schema抽象类中:

//获取数据表元数据
public function getTableSchema($name, $refresh = false)
{
if (array_key_exists($name, $this->_tables) && !$refresh) {
return $this->_tables[$name];
} $db = $this->db;
$realName = $this->getRawTableName($name); if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
/* @var $cache Cache */
$cache = is_string($db->schemaCache) ? Yii::$app->get($db->schemaCache, false) : $db->schemaCache;
if ($cache instanceof Cache) {
$key = $this->getCacheKey($name);
if ($refresh || ($table = $cache->get($key)) === false) {
//通过工厂方法loadTableSchema()去获取TableSchema实例
$this->_tables[$name] = $table = $this->loadTableSchema($realName);
if ($table !== null) {
$cache->set($key, $table, $db->schemaCacheDuration, new TagDependency([
'tags' => $this->getCacheTag(),
]));
}
} else {
$this->_tables[$name] = $table;
} return $this->_tables[$name];
}
}
//通过工厂方法loadTableSchema()去获取TableSchema实例
return $this->_tables[$name] = $this->loadTableSchema($realName);
} //获取TableSchema实例,让子类去实现
abstract protected function loadTableSchema($name);

这里使用了工厂方法模式。loadTableSchema()就是工厂方法,它负责提供一个具体的TableSchema类以供getTableSchema()使用。而要提供具体的TableSchema类,显然要到各个Schema的子类中去实现。

工厂方法模式

模式定义

工厂方法模式(Factory Method Pattern)定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类吧实例化推迟到子类。

什么意思?说起来有这么几个要点:

  • 对象不是直接new产生,而是交给一个类方法去完成。比如loadTableSchema()方法

  • 这个方法是抽象的,且必须被子类所实现

  • 这个提供实例的抽象方法需要参与到其他逻辑中,去完成另一项功能。比如loadTableSchema()方法出现在getTableSchema()方法中,参与实现获取数据表元数据的功能

代码实现

现在,我们打算用利用面向对象的手段——工厂方法模式为你开一家披萨连锁店。

假如你现在打算开一家披萨连锁店,分店以加盟的方式加入。加盟店提供加盟费以及利润提成,而总店提供配方,烹饪方法,以及选址和其他方面的建议。这种场景够常见了吧?什么奶茶加盟店、花甲加盟店、火锅加盟店等等都是这个套路。那么,现在你打算怎么做?

首先,我希望为各个加盟店制定一定的规范以保证基本口味和品牌形象,比如配方、原料、加工方式都我来提供

其次,我也允许加盟店可以适当的扩展以增加其灵活性和丰富性,比如切块和包装可以采用自己的

如果你想明白这两点,那恭喜你!你已经基本搞清楚了抽象和具体的关系了。在这里总店就是是抽象,加盟店就是具体

第一步,我们的披萨是什么样子的,配方原料加工方式是什么。因此我们要有个抽象的Pizza类,规定要做披萨的原料有面团、酱汁、各种配菜;加工程序有准备、烘烤、切块、包装。

abstract class Pizza
{
/**
* @var
*/
protected $name;
/**
* @var
*/
protected $dough;
/**
* @var
*/
protected $sauce;
/**
* @var array
*/
protected $toppings = []; public function prepare()
{
print_r('Preparing '.$this->name.'<br>');
print_r('Tossing dough...'.'<br>');
print_r('Adding sauce...'.'<br>');
print_r('Adding toppings...'.'<br>');
foreach ($this->toppings as $topping) {
print_r("$topping".'<br>');
}
} public function bake()
{
print_r('Bake: Bake for 25 minutes at 350'.'<br>');
} public function cut()
{
print_r('Cut: Cutting the pizza into diagonal slices'.'<br>');
} public function box()
{
print_r('Box: Place pizza in official PizzaStore box'.'<br>');
} /**
* @return mixed
*/
public function getName()
{
return $this->name;
}
}

你是总店,你可以规定哪些工序可以用我总店的,而哪些必须你自己去实现。比如,加盟店选择开在哪里必须加盟店自己去完成,而原料和做法则总店来决定。

体现在上面的代码,就是abstract Pizza类所有方法都可以根据需要改成abstract的。当你要求必须子类去完成的就是abstract的;当你提供了默认的行为,可让子类继承直接使用的就是具体的。

规定了披萨如何构成的,总店第二步还需要指导下分店怎么把披萨做出来,因此还需要有个抽象的披萨店类PizzaStore:

abstract class PizzaStore
{
/**
* @var Pizza
*/
protected $pizza; /**
* @param $type
*
* @return Pizza
*/
public function orderPizza($type)
{
// create a pizza
$this->pizza = $this->createPizza($type); // handle the pizza
$this->pizza->prepare();
$this->pizza->bake();
$this->pizza->cut();
$this->pizza->box(); // return the prepared pizza
return $this->pizza;
} /**
* Create a Pizza.
*
* @param $type
*
* @return Pizza
*/
abstract protected function createPizza($type);
}

总店在抽象的PizzaStore规定了披萨加工的基本流程,所有的加盟店加工披萨都必须按照准备->烘烤->切块->装盒这几个固定的工序进行。至于谁提供披萨,这些披萨原料是啥,烘烤多久,切成啥形状,包装成啥样子,这些都是具体的Pizza本身所的细节,由各个加盟店自己决定的。

因此,各个加盟店必须要继承抽象的createPizza()方法,去具体实现自己的细节,做出不同口味的披萨来。

最后一步,我们可以让别人来加盟了。

有人打算在纽约开一家披萨加盟店:

class NYPizzaStore extends PizzaStore
{
/**
* Create a Pizza.
*
* @param $type
*
* @return Pizza
*/
public function createPizza($type)
{
if ($type == 'cheese') {
return new NYStyleCheesePizza();
} elseif ( $type == 'clam') {
return new NYStyleClamPizza();
}
}
}

纽约店暂时提供两种口味的披萨:奶酪味和蛤蜊味。

//奶酪味
class NYStyleCheesePizza extends Pizza
{
/**
* NYStyleCheesePizza constructor.
*/
public function __construct()
{
$this->name = 'NY Style Sauce and Cheese Pizza';
$this->dough = 'Thin Crust Dough';
$this->sauce = 'Marinara Sauce'; $this->toppings[] = 'Grated Reggiano Cheese';
} /**
* {@inheritdoc}
*/
public function box()
{
print_r('Box: Place pizza in NY PizzaStore box'.'<br>');
}
} //蛤蜊味
class NYStyleClamPizza extends Pizza
{
/**
* NYStyleClamPizza constructor.
*/
public function __construct()
{
$this->name = 'NY Style Sauce and Clam Pizza';
$this->dough = 'Thin Crust Dough';
$this->sauce = 'Marinara Sauce'; $this->toppings[] = 'Grated Reggiano Clam';
} /**
* {@inheritdoc}
*/
public function box()
{
print_r('Box: Place pizza in NY PizzaStore box'.'<br>');
}
}

纽约店两种口味披萨的特点是:

  • 奶酪味:薄面团、Marinara酱料,配菜是Grated Reggiano Cheese(一种奶酪),采用自己的包装,烘烤和切块采用总店的

  • 蛤蜊味:薄面团、Marinara酱料,配菜是Grated Reggiano Clam(一种蛤蜊),采用自己的包装,烘烤和切块采用总店的

看来纽约喜欢的披萨面团要薄一点....

不久,芝加哥又想加盟一家披萨店,和纽约人不同的是,芝加哥人希望披萨的面团厚一些,所谓一方一俗吧。于是我们把店先开起来,然后再做披萨:

class ChicagoPizzaStore extends PizzaStore
{
/**
* @param $type
*
* @return Pizza
*/
public function createPizza($type)
{
if ($type == 'cheese') {
return new ChicagoStyleCheesePizza();
} elseif ($type' == 'clam) {
return new ChicagoStyleClamPizza();
}
}
}

芝加哥的分店暂时也只提供两种口味:奶酪味和蛤蜊味。

//奶酪味
class ChicagoStyleCheesePizza extends Pizza
{
/**
* ChicagoStyleCheesePizza constructor.
*/
public function __construct()
{
$this->name = 'Chicago Style Deep Dish Cheese Pizza';
$this->dough = 'Extra Thick Crust Dough';
$this->sauce = 'Plum Tomato Sauce'; $this->toppings[] = 'Shredded Mozzarella Cheese';
} public function cut()
{
print_r('Cut: Cutting the pizza into square slices').'<br>';
}
} //蛤蜊味
class ChicagoStyleClamPizza extends Pizza
{
/**
* ChicagoStyleClamPizza constructor.
*/
public function __construct()
{
$this->name = 'Chicago Style Deep Dish Clam Pizza';
$this->dough = 'Extra Thick Crust Dough';
$this->sauce = 'Plum Tomato Sauce'; $this->toppings[] = 'Shredded Mozzarella Clam';
} public function cut()
{
print_r('Cut: Cutting the pizza into square slices'.'<br>');
}
}

芝加哥店两种口味披萨的特点是:

  • 奶酪味:加厚面团、Plum Tomato酱料,配菜是Shredded Mozzarella Cheese(一种奶酪),切成方块,烘烤和包装采用总店的

  • 蛤蜊味:加厚面团、Marinara酱料,配菜是Shredded Mozzarella Clam(一种蛤蜊),切成方块,烘烤和包装采用总店的

现在我们就有了两家分店,四种不同口味披萨了。你已经等了很久了,来吃些披萨吧:

class Test
{
public function run()
{
$nyStore = new NYPizzaStore();
print_r("Terry ordered a NY Style Cheese Pizza" . '<br>');
$pizza1 = $nyStore->orderPizza('cheese'); echo '<br><br>'; $chicagoStore = new ChicagoPizzaStore();
print_r("Json ordered a Chicago Style Clam Pizza" . '<br>');
$pizza2 = $chicagoStore->orderPizza('clam');
}
}

我想尝尝纽约奶酪风味的,那我首先要有个纽约店,再由纽约店给我提供奶酪味的;我想尝尝芝加哥蛤蜊味的,那我首先要有个芝加哥店,再有芝加哥店给我提供蛤蜊味的。

认识工厂方法模式

所有的工厂模式都是用来封装对象的创建。工厂方法模式通过让子类来决定创建的对象是什么,从而达到将对象创建的过程封装的目的。

披萨店通过orderPizza()提供最终的披萨,在orderPizza()看来,我需要一个工厂方法createPizza()给我提供一个未加工的,然后我来做准备、烘烤、切块、包装,最终返回。orderPizza()无需了解披萨具体细节,因为反正所有的披萨都这么个过程。而在抽象的PizzaStore中也createPizza()也不确定细节,它只能保证自己要提供这么一个。具体的细节是其子类去规定。

因此,表现在代码中,就是在abstract class PizzaStore中,我还没有一个Pizza的实例呢,但prepare()->bake()->cut()->box()->return 也照样这么做了下来。

public function orderPizza($type)
{
// create a pizza
$this->pizza = $this->createPizza($type); // handle the pizza
$this->pizza->prepare();
$this->pizza->bake();
$this->pizza->cut();
$this->pizza->box(); // return the prepared pizza
return $this->pizza;
}

此时的$this->pizza是实现abstract class Pizza的规定的一种抽象,而还不是一个具体的实例。

这就是依赖抽象而不依赖具体

工厂方法模式的另一种认识,就是将orderPizza()和一个工厂方法createPizza()联合起来,加上其他的prepare()/bake()等逻辑,就组成了一个框架,一种规范。子类继承这个抽象类也就获得了这个规范。如果说createPizza()是子类自由发挥的部分,那么orderPizza()就给你规定了自由发挥的一些前提,从而是有限度的自由。这是总店希望看到的,希望你在这个框框里面去开你的分店,而不要自由发挥得太离谱。

工厂方法和简单工厂

工厂方法和简单工厂的区别在于,简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是在创建一个框架,让子类决定要如何实现。比如说,在工厂方法中,orderPizza()方法提供了一般的框架,以便创建披萨,orderPizza()方法依赖工厂方法创建具体类,再经过一系列其他操作,最终制造出实际的披萨。可通过继承PizzaStore类,决定实际造出的披萨是什么。简单工厂的做法,可以将对象的创建封装起来,但是一下子给你一个完整的,没有那种子类的“推迟”,因此不具备工厂方法的弹性。

工厂方法和抽象工厂

细心的读者可能会发现一个问题,就是各家披萨店的原料都是自己提供(参看各个披萨店的__construct()),这样口味还是有较大的随意性。为了保证各分店口味大致相同,我们需要对原料做统一管理,让总店来统一供给做披萨的原料。

为了吃一个披萨,我们首先要有个披萨工厂(店),为了供给披萨原料,我们需要什么呢?——原料工厂呗!如果想到这一层,那恭喜你,已经进阶到抽象工厂的层次了。

抽象工厂将上面的dough,sauce,cheese,clams等等——凡是出现过的原料都让一个抽象方法去实现,将所有这些抽象方法集合起来放到一个类里,就是抽象工厂。例如,原料工厂应该实现下面的接口:

interface PizzaIngredientFactory
{
/**
* @return Dough
*/
public function createDough(); /**
* @return Sauce
*/
public function createSauce(); /**
* @return Cheese
*/
public function createCheese(); /**
* @return Veggie[]
*/
public function createVeggies(); /**
* @return Clams
*/
public function createClams();
}

实现了这个接口的就是具体原料工厂。在让PizzaStore创建披萨时,先往PizzaStore注入一个PizzaIngredientFactory实例,然后委托这个实例去提供各种原料。

因此,抽象工厂其实是基于工厂方法的。工厂方法定义创建一种产品,而抽象工厂负责定义常见一组产品的接口,这个接口的每个方法都像工厂方法一样创建一个具体产品,同时我们用抽象工厂的子类去提供这些具体的做法,从而最终提供一组一组的形形色色的产品来。

Yii2中的工厂方法模式

我们已经走得够远了,让我们回到Yii2框架中来。

本文开头的那个例子中,yii\db\Schema是为各个DBMS提供了一个统一的、抽象的数据库信息的基类。getTableSchema()方法是获取表的元数据,而loadTableSchema()将一个数据表填充为一个TableSchema类。

也就说说loadTableSchema()需要返回一个TableSchema的具体类。这个类包含了schema名、表名、主键foreignKeys以及代表数据表字段信息的ColumnSchema的数组colums[]。

显然,各个数据库的表和字段类型是有差异的,因此loadTableSchema()必须为抽象方法,由子类去做具体实现。mysql/mssql/cubrid/sqlite等DBMS的Schema也确实继承了yii\db\Schema类,实现了各自的loadTableSchema()具体方法。

以mysql的为例:

class Schema extends \yii\db\Schema
{
//... /**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema driver dependent table metadata. Null if the table does not exist.
*/
protected function loadTableSchema($name)
{
$table = new TableSchema;
$this->resolveTableNames($table, $name); if ($this->findColumns($table)) {
$this->findConstraints($table); return $table;
} else {
return null;
}
} //...
}

它就在loadTableSchema()方法中返回了一个具体的TableSchema实例。

Yii2设计模式——工厂方法模式的更多相关文章

  1. Yii2 设计模式——工厂方法模式

    工厂方法模式 模式定义 工厂方法模式(Factory Method Pattern)定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个.工厂方法让类吧实例化推迟到子类. 什么意思?说起来有这么 ...

  2. 4. 星际争霸之php设计模式--工厂方法模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  3. C++设计模式——工厂方法模式

    本文版权归果冻说所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.» 本文链接:http://www.jellythink.com/arch ...

  4. JAVA设计模式--工厂方法模式

    工厂方法设计模式 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关.是具体工厂角色必须实现的接口或者必须继承的父类.在java中它由抽象类或者接口来实现.具体工厂角色:它含有和具体业务逻辑有关 ...

  5. 深入浅出设计模式——工厂方法模式(Factory Method)

    介绍在简单工厂模式中,我们提到,工厂方法模式是简单工厂模式的一个延伸,它属于Gof23中设计模式的创建型设计模式.它解决的仍然是软件设计中与创建对象有关的问题.它可以更好的处理客户的需求变化. 引入我 ...

  6. 设计模式--工厂方法模式(Factory method pattern)及应用

    面向对象的好处: 通过封装,继承,多态把程序的耦合度降低. 用设计模式可以使程序更加灵活,容易修改,且易于复用. 1. 工厂方法模式 Define an interface for creating ...

  7. 我的Java设计模式-工厂方法模式

    女朋友dodo闹脾气,气势汹汹的说"我要吃雪糕".笔者心里啊乐滋滋的,一支雪糕就能哄回来,不亦乐乎?! 但是,雪糕买回来了,她竟然说"不想吃雪糕了,突然想吃披萨" ...

  8. 设计模式 — 工厂方法模式(Factory Method)

    在开发系统中,经常会碰到一个问题.现在需要实现的一些功能,但是这个功能模块以后一定是需要扩展的,那么现在开发中就不仅要实现现在的功能,还要考虑以后的扩展.那么为了系统的健壮,扩展就要遵循开闭原则(简单 ...

  9. C#设计模式--工厂方法模式

    0.C#设计模式-简单工厂模式 设计模式: 工厂方法模式(Factory Method Pattern) 介绍:简单工厂模式是要在工厂类中通过数据来做个决策,在工厂类中的多个类中实例化出来其中一个要用 ...

随机推荐

  1. Python_字符串的大小写变换

    ''' lower().upper().capitalize().title().swapcase() 这几个方法分别用来将字符串转换为小写.大写字符串.将字符串首字母变为大写.将每个首字母变为大写以 ...

  2. java并发之读写锁ReentrantReadWriteLock的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  3. Tiny4412MMU内存管理

    MMU是Memory Management Unit的缩写,中文名是内存管理单元,MMU是由ARM芯片中的cp15协处理器管理,它的作用是负责虚拟内存到物理内存的映射 要将虚拟内存映射为物理内存,就要 ...

  4. Java公开课-06.单例

    一. 什么是单例模式 因程序需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计. 二. 单例模式的特点 1. 单例模式只能有一个实例. 2. 单例类必须创建 ...

  5. Deep Learning Enables You to Hide Screen when Your Boss is Approaching

    https://github.com/Hironsan/BossSensor/ 背景介绍 学生时代,老师站在窗外的阴影挥之不去.大家在玩手机,看漫画,看小说的时候,总是会找同桌帮忙看着班主任有没有来. ...

  6. tkinter属性(总结)

    一.主要控件 1.Button 按钮.类似标签,但提供额外的功能,例如鼠标掠过.按下.释放以及键盘操作事件 2.Canvas 画布.提供绘图功能(直线.椭圆.多边形.矩形) 可以包含图形或位图 3.C ...

  7. Javaweb之 servlet 开发详解1

    1.1  Tip:Servlet简介 Servlet是sun公司提供的一门用于开发动态web资源的技术. Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个 ...

  8. Android 5.x Theme 与 ToolBar 实战

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/45303349: 本文出自:[张鸿洋的博客] 1.概述 随着Material D ...

  9. Python将html转化为pdf

    前言 前面我们对博客园的文章进行了爬取,结果比较令人满意,可以一下子下载某个博主的所有文章了.但是,我们获取的只有文章中的文本内容,并且是没有排版的,看起来也比较费劲... 咋么办的?一个比较好的方法 ...

  10. Log4j2中的同步日志与异步日志

    1.背景 Log4j 2中记录日志的方式有同步日志和异步日志两种方式,其中异步日志又可分为使用AsyncAppender和使用AsyncLogger两种方式. 2.Log4j2中的同步日志 所谓同步日 ...