yii\db\Schema抽象類中:mysql
//獲取數據表元數據 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的子類中去實現。sql
工廠方法模式(Factory Method Pattern)定義了一個建立對象的接口,但由子類決定要實例化的類是哪個。工廠方法讓類吧實例化推遲到子類。數據庫
什麼意思?提及來有這麼幾個要點:數組
對象不是直接new產生,而是交給一個類方法去完成。好比loadTableSchema()方法app
這個方法是抽象的,且必須被子類所實現框架
這個提供實例的抽象方法須要參與到其餘邏輯中,去完成另外一項功能。好比loadTableSchema()方法出如今getTableSchema()方法中,參與實現獲取數據表元數據的功能yii
如今,咱們打算用利用面向對象的手段——工廠方法模式爲你開一家披薩連鎖店。this
假如你如今打算開一家披薩連鎖店,分店以加盟的方式加入。加盟店提供加盟費以及利潤提成,而總店提供配方,烹飪方法,以及選址和其餘方面的建議。這種場景夠常見了吧?什麼奶茶加盟店、花甲加盟店、火鍋加盟店等等都是這個套路。那麼,如今你打算怎麼作?spa
首先,我但願爲各個加盟店制定必定的規範以保證基本口味和品牌形象,好比配方、原料、加工方式都我來提供code
其次,我也容許加盟店能夠適當的擴展以增長其靈活性和豐富性,好比切塊和包裝能夠採用本身的
若是你想明白這兩點,那恭喜你!你已經基本搞清楚了抽象和具體的關係了。在這裏總店就是是抽象,加盟店就是具體。
第一步,咱們的披薩是什麼樣子的,配方原料加工方式是什麼。所以咱們要有個抽象的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框架中來。
本文開頭的那個例子中,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實例。