设计模式(一)
什么是设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
我们说在编程中要有面向对象的思维。而如果没有学过设计模式,那么对于面向对象的理解多半都是肤浅和片面的。下面我们通过对设计模式的学习,来加深对面向对象的认识。
1、简单工厂模式(静态工厂模式)
属于创建型模式,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
举个栗子,要实现计算器的功能,参考代码如下:
1 <?php 2 3 // 运算类-计算器 4 class Operation 5 { 6 private $_numberA = 0; 7 private $_numberB = 0; 8 9 public function numberA($value = '') 10 { 11 if (isset($value) && !empty($value)) { 12 $this->_numberA = $value; 13 return $this; 14 } 15 16 return $this->_numberA; 17 } 18 19 public function numberB($value = '') 20 { 21 if (isset($value) && !empty($value)) { 22 $this->_numberB = $value; 23 return $this; 24 } 25 return $this->_numberB; 26 } 27 28 public function getResult() 29 { 30 $result = 0; 31 return $result; 32 } 33 34 } 35 36 // 加减乘除类 37 // 加法类,继承运算类 38 class OperationAdd extends Operation 39 { 40 public function getResult() 41 { 42 $result = $this->numberA() + $this->numberB(); 43 return $result; 44 } 45 } 46 47 // 减法类,继承运算类 48 class OperationSub extends Operation 49 { 50 public function getResult() 51 { 52 $result = $this->numberA() - $this->numberB(); 53 return $result; 54 } 55 } 56 57 // 乘法类,继承运算类 58 class OperationMul extends Operation 59 { 60 public function getResult() 61 { 62 $result = $this->numberA() * $this->numberB(); 63 return $result; 64 } 65 } 66 67 // 除法类,继承运算类 68 class OperationDiv extends Operation 69 { 70 public function getResult() 71 { 72 if ($this->numberB() == 0) { 73 throw new Exception("除数不能为0。"); 74 } 75 $result = $this->numberA() / $this->numberB(); 76 return $result; 77 } 78 } 79 80 // 简单工厂模式 81 class OperationFactory 82 { 83 public static function createOperate(string $operate) 84 { 85 switch ($operate) { 86 case "+": 87 $oper = new OperationAdd(); 88 break; 89 case "-": 90 $oper = new OperationSub(); 91 break; 92 case "*": 93 $oper = new OperationMul(); 94 break; 95 case "/": 96 $oper = new OperationDiv(); 97 break; 98 default: 99 throw new Exception("运算符号必须是+-*/中的其中一个"); 100 } 101 return $oper; 102 } 103 } 104 105 $oper = OperationFactory::createOperate("*"); 106 $oper->numberA(2); 107 $oper->numberB(5); 108 echo $oper->getResult(); 109 110 // 输出结果 111 // 10
说明:
1)接下来,只需要输入运算符号,工厂就实例化出合适的对象,通过多态,返回父类的方式实现了计算器的结果。
2)如果之后,需要增加各种复杂的运算,比如平发根,立方根等,只要增加相应的运算子类,并且还要去修改运算类工厂,在switch中增加分支。
3)说明:对于调用工厂的客户端来说实现了开闭原则而工厂本身没有实现开闭原则。后面可以通过反射的机制来解决switch分支的问题。
2、工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。
还是以上面实现计算器的功能来举例,参考代码如下:
1 <?php 2 3 // 运算类-计算器 4 class Operation 5 { 6 private $_numberA = 0; 7 private $_numberB = 0; 8 9 public function numberA($value = '') 10 { 11 if (isset($value) && !empty($value)) { 12 $this->_numberA = $value; 13 return $this; 14 } 15 16 return $this->_numberA; 17 } 18 19 public function numberB($value = '') 20 { 21 if (isset($value) && !empty($value)) { 22 $this->_numberB = $value; 23 return $this; 24 } 25 return $this->_numberB; 26 } 27 28 public function getResult() 29 { 30 $result = 0; 31 return $result; 32 } 33 34 } 35 36 37 // 加减乘除类 38 39 class OperationAdd extends Operation 40 { 41 public function getResult() 42 { 43 $result = $this->numberA() + $this->numberB(); 44 return $result; 45 } 46 } 47 48 class OperationSub extends Operation 49 { 50 public function getResult() 51 { 52 $result = $this->numberA() - $this->numberB(); 53 return $result; 54 } 55 } 56 57 class OperationMul extends Operation 58 { 59 public function getResult() 60 { 61 $result = $this->numberA() * $this->numberB(); 62 return $result; 63 } 64 } 65 66 class OperationDiv extends Operation 67 { 68 public function getResult() 69 { 70 if ($this->numberB() == 0) { 71 throw new Exception("除数不能为0。"); 72 } 73 $result = $this->numberA() / $this->numberB(); 74 return $result; 75 } 76 } 77 78 79 interface iFactory 80 { 81 public function createOperation(); 82 } 83 84 85 // 加法类工厂 86 class AddFactory implements iFactory 87 { 88 public function createOperation() 89 { 90 return new OperationAdd(); 91 } 92 } 93 94 // 减法类工厂 95 class SubFactory implements iFactory 96 { 97 public function createOperation() 98 { 99 return new OperationSub(); 100 } 101 } 102 103 // 乘法类工厂 104 class MulFactory implements iFactory 105 { 106 public function createOperation() 107 { 108 return new OperationMul(); 109 } 110 } 111 112 // 除法类工厂 113 class DivFactory implements iFactory 114 { 115 public function createOperation() 116 { 117 return new OperationDiv(); 118 } 119 } 120 121 // 客户端的实现 122 $operFactory=new AddFactory(); 123 $oper=$operFactory->createOperation(); 124 $oper->numberA(2); 125 $oper->numberB(8); 126 echo $oper->getResult(); 127 128 // 输出结果 129 // 10
说明:工厂方法本身是符合开闭原则的,但要说到客户端,客户总得自己决定创建什么样的对象吧,这个在服务端没法帮客户确定,所以客户端代码需要改是不可避免的。
3、抽象工厂模式 (Abstract Factory)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
举个栗子,要实现调用不同类型数据库,以及使用各数据库下的user和departmen表,参考代码如下:
1 <?php 2 3 class User 4 { 5 private $_id; 6 private $_name; 7 8 public function id($value = '') 9 { 10 if (isset($value) && !empty($value)) { 11 $this->_id = $value; 12 return $this; 13 } 14 return $this->_id; 15 } 16 17 public function name() 18 { 19 if (isset($value) && !empty($value)) { 20 $this->_name = $value; 21 return $this; 22 } 23 return $this->_name; 24 } 25 } 26 27 class Department 28 { 29 private $_id; 30 31 public function id($value = '') 32 { 33 if (isset($value) && !empty($value)) { 34 $this->_id = $value; 35 return $this; 36 } 37 return $this->_id; 38 } 39 } 40 41 interface iUser 42 { 43 public function insert(User $user); 44 45 public function getUser(int $id); 46 } 47 48 interface iDepartment 49 { 50 public function insert(Department $department); 51 52 public function getDepartment(int $id); 53 } 54 55 class SqlserverUser implements iUser 56 { 57 public function insert(User $user) 58 { 59 echo "在SQL Server中给User表增加一条记录<br/>"; 60 } 61 62 public function getUser(int $id) 63 { 64 echo "在SQL Server中根据ID=" . $id . "得到User表一条记录<br/>"; 65 } 66 67 } 68 69 class AccessUser implements iUser 70 { 71 public function insert(User $user) 72 { 73 echo "在Access中给User表增加一条记录<br/>"; 74 } 75 76 public function getUser(int $id) 77 { 78 echo "在Access中根据ID=" . $id . "得到User表一条记录<br/>"; 79 } 80 } 81 82 class SqlserverDepartment implements iDepartment 83 { 84 public function insert(Department $department) 85 { 86 echo "在SQL Server中给Department表增加一条记录<br/>"; 87 } 88 89 public function getDepartment(int $id) 90 { 91 echo "在SQL Server中根据ID=" . $id . "得到Department表一条记录<br/>"; 92 } 93 94 } 95 96 class AccessDepartment implements iDepartment 97 { 98 public function insert(Department $department) 99 { 100 echo "在Access中给Department表增加一条记录<br/>"; 101 } 102 103 public function getDepartment(int $id) 104 { 105 echo "在Access中根据ID=" . $id . "得到Department表一条记录<br/>"; 106 } 107 } 108 109 // 抽象工厂模式 110 interface iFactory 111 { 112 public function createUser(); 113 114 public function createDepartment(); 115 } 116 117 class SqlserverFactory implements iFactory 118 { 119 public function createUser() 120 { 121 return new SqlserverUser(); 122 } 123 124 public function createDepartment() 125 { 126 return new SqlserverDepartment(); 127 } 128 } 129 130 class AccessFactory implements iFactory 131 { 132 public function createUser() 133 { 134 return new AccessUser(); 135 } 136 137 public function createDepartment() 138 { 139 return new AccessDepartment(); 140 } 141 } 142 143 144 // 客户端代码如下: 145 $user = new User(); 146 $dep = new Department(); 147 $factory = new AccessFactory(); 148 // 生成User类的实例 149 $su = $factory->createUser(); 150 $su->insert($user); 151 $su->getUser(1); 152 // 生成Department类的实例 153 $depFac = $factory->createDepartment(); 154 $depFac->insert($dep); 155 $depFac->getDepartment(1);
4、策略模式(strategy)
定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
举个栗子,要实现商场收银软件系统,参考代码如下:
<?php // 现金收费抽象类 abstract class CashSuper { public abstract function acceptCash($money); } // 正常收费子类 class CashNormal extends CashSuper { public function acceptCash($money) { return $money; } } // 打折收费子类 class CashRebate extends CashSuper { private $moneyRebate = 1; public function __construct($moneyRebate) { $this->moneyRebate = $moneyRebate; } public function acceptCash($money) { return $money * $this->moneyRebate; } } // 返利收费子类 class CashReturn extends CashSuper { private $moneyCondition = 0; private $moneyReturn = 0; public function __construct($moneyCondition, $moneyReturn) { $this->moneyCondition = $moneyCondition; $this->moneyReturn = $moneyReturn; } public function acceptCash($money) { $result = $money; if ($money >= $this->moneyCondition) { $result = $money - floor($money / $this->moneyCondition) * $this->moneyReturn; } return $result; } } class CashContext { // 声明一个CashSuper对象 private $cs; // 策略模式与简单工厂结合 public function __construct($type) { switch ($type) { case "1": // 正常收费 $this->cs = new CashNormal(); break; case "2": // 满300返100 $this->cs = new CashReturn(300, 100); break; case "3": //打8折 $this->cs = new CashRebate(0.8); break; default: throw new Exception("该收费类型不存在。"); } } // 获得计算费用的结果 public function getResult($money) { return $this->cs->acceptCash($money); } } // 客户端主要代码 class Client { public function btnClick($type, $money) { // 选择所用具体实现的职责由客户端对象承担,并转给策略模式的context对象 $cashContext = new CashContext($type); return $cashContext->getResult($money); } } $client = new Client; echo $client->btnClick(3, 1000); // 输出结果 // 800
分析:商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算是随时都可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式。
5、装饰模式(decorator)
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
1 <?php 2 3 // 装饰模式 4 class Person 5 { 6 private $name; 7 8 public function __construct($name) 9 { 10 $this->name = $name; 11 } 12 13 public function show() 14 { 15 echo "装扮的" . $this->name; 16 } 17 18 } 19 20 // 服饰类(Decorator) 21 class Finery extends Person 22 { 23 protected $component; 24 25 public function __construct() 26 { 27 } 28 29 //打扮 30 public function decorate(Person $component) 31 { 32 $this->component = $component; 33 } 34 35 public function show() 36 { 37 if ($this->component != null) { 38 $this->component->show(); 39 } 40 } 41 } 42 43 // 具体服饰类(ConcreteDecorator) 44 class TShirts extends Finery 45 { 46 public function show() 47 { 48 echo "大T恤 "; 49 parent::show(); 50 } 51 } 52 53 class BigTrouser extends Finery 54 { 55 public function show() 56 { 57 echo "垮裤 "; 58 parent::show(); 59 } 60 } 61 62 class Sneakers extends Finery 63 { 64 public function show() 65 { 66 echo "破球鞋 "; 67 parent::show(); 68 } 69 } 70 71 class Suit extends Finery 72 { 73 public function show() 74 { 75 echo "西装 "; 76 parent::show(); 77 } 78 } 79 80 class Tie extends Finery 81 { 82 public function show() 83 { 84 echo "领带 "; 85 parent::show(); 86 } 87 } 88 89 class LeatherShoes extends Finery 90 { 91 public function show() 92 { 93 echo "皮鞋 "; 94 parent::show(); 95 } 96 } 97 98 # 客户端代码 99 $xc = new Person("小菜"); 100 echo "第一种装扮:<br>"; 101 102 $pqx = new Sneakers(); 103 $kk = new BigTrouser(); 104 $dtx = new TShirts(); 105 $pqx->decorate($xc); 106 $kk->decorate($pqx); 107 $dtx->decorate($kk); 108 $dtx->show(); 109 110 echo "<hr>"; 111 112 echo "第二种装扮:<br>"; 113 $px = new LeatherShoes(); 114 $ld = new Tie(); 115 $xz = new Suit(); 116 $px->decorate($xc); 117 $ld->decorate($px); 118 $xz->decorate($ld); 119 $xz->show(); 120 121 // 输出结果 122 // 第一种装扮: 123 // 大T恤 垮裤 破球鞋 装扮的小菜 124 // ———————————————————————————————— 125 // 第二种装扮: 126 // 西装 领带 皮鞋 装扮的小菜
把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为是,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。
6、建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
1 <?php 2 3 // 产品类,由多个部件组成 4 class Product 5 { 6 public $arr = []; 7 8 public function add(string $part) 9 { 10 // 添加产品部件 11 array_push($this->arr, $part); 12 } 13 14 public function show() 15 { 16 echo " 产品 创建----"; 17 // 列举所有的产品部件 18 foreach ($this->arr as $part) { 19 echo $part; 20 } 21 } 22 } 23 24 // 抽象建造者类:确定产品由两个部件PartA 和 PartB组成,并声明一个得到产品建造后结果的方法getResult 25 abstract class Builder 26 { 27 public abstract function buildPartA(); 28 29 public abstract function buildPartB(); 30 31 public abstract function getResult(); 32 } 33 34 // 具体建造者类-建造具体的两个部件是部件A和部件B 35 class ConcreteBuilder1 extends Builder 36 { 37 private $product; 38 39 public function __construct(Product $product) 40 { 41 $this->product = $product; 42 } 43 44 public function buildPartA() 45 { 46 $this->product->add("部件A"); 47 } 48 49 public function buildPartB() 50 { 51 $this->product->add("部件B"); 52 } 53 54 55 public function getResult() 56 { 57 return $this->product; 58 } 59 } 60 61 62 class ConcreteBuilder2 extends Builder 63 { 64 private $product; 65 66 public function __construct(Product $product) 67 { 68 $this->product = $product; 69 } 70 71 public function buildPartA() 72 { 73 $this->product->add("部件X"); 74 } 75 76 public function buildPartB() 77 { 78 $this->product->add("部件Y"); 79 } 80 81 82 public function getResult() 83 { 84 return $this->product; 85 } 86 } 87 88 // 指挥者类-用来指挥建造过程 89 class Director 90 { 91 public function construct(Builder $builder) 92 { 93 $builder->buildPartA(); 94 $builder->buildPartB(); 95 } 96 } 97 98 // 客户端代码(客户端不知道具体的建造过程) 99 $product = new Product(); 100 $director = new Director(); 101 $b1 = new ConcreteBuilder1($product); 102 $b2 = new ConcreteBuilder2($product); 103 104 // 指挥者用ConcreteBuilder1的方法来建造产品 105 $director->construct($b1); 106 $p1 = $b1->getResult(); 107 $p1->show(); 108 109 // 指挥者用ConcreteBuilder2的方法来建造产品 110 $director->construct($b2); 111 $p2 = $b2->getResult(); 112 $p2->show();
说明:
1)如果我们用了建造者模式,那么用户就只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需知道了。
2)建造者模式跟装饰模式很像,都是通过内部组装完毕,然后再显示出来。它们之间最大的区别是建造者模式要求建造的过程必须是稳定的。而装饰模式的建造过程是不稳定的。
7、代理模式(proxy)
为其他对象提供一种代理以控制对这个对象的访问。
1 <?php 2 3 interface iGiveGift 4 { 5 public function giveDolls(); 6 7 public function giveFlowers(); 8 9 public function giveChocolate(); 10 } 11 12 // 追求者类 13 class Pursuit implements iGiveGift 14 { 15 public $mm; 16 17 public function __construct(SchoolGirl $mm) 18 { 19 $this->mm = $mm; 20 } 21 22 public function giveDolls() 23 { 24 echo $this->mm->name() . " 送你洋娃娃<br/>"; 25 } 26 27 public function giveFlowers() 28 { 29 echo $this->mm->name() . " 送你鲜花<br/>"; 30 } 31 32 public function giveChocolate() 33 { 34 echo $this->mm->name() . " 送你巧克力<br/>"; 35 } 36 } 37 38 class Proxy implements iGiveGift 39 { 40 public $gg; 41 42 public function __construct(SchoolGirl $mm) 43 { 44 $this->gg = new Pursuit($mm); 45 } 46 47 public function giveDolls() 48 { 49 $this->gg->giveDolls(); 50 } 51 52 public function giveFlowers() 53 { 54 $this->gg->giveFlowers(); 55 } 56 57 public function giveChocolate() 58 { 59 $this->gg->giveChocolate(); 60 } 61 } 62 63 // 被追求者类 64 class SchoolGirl 65 { 66 private $name; 67 68 public function name($value = '') 69 { 70 if (isset($value) && !empty($value)) { 71 $this->name = $value; 72 return $this; 73 } 74 return $this->name; 75 } 76 } 77 78 // 客户端调用代码 79 $sc=new SchoolGirl(); 80 $sc->name("李娇娇"); 81 $proxy=new Proxy($sc); 82 $proxy->giveDolls(); 83 $proxy->giveFlowers(); 84 $proxy->giveChocolate();
8、单例模式(singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1 <?php 2 3 // trait与单例模式结合使用 4 trait Singleton 5 { 6 private static $_instance = null; 7 8 public static function getInstance(...$args) 9 { 10 if (self::$_instance == null) { 11 // 注意:此处使用new static而不是new self的原因是-new static则是由调用者决定的,而new self()返回的实例是不会变的 12 self::$_instance = new static(...$args); 13 } 14 return self::$_instance; 15 } 16 17 public function test() 18 { 19 echo "调试方法成功"; 20 } 21 } 22 23 class MySingleton 24 { 25 use Singleton; 26 } 27 28 $mySingleton = MySingleton::getInstance(); 29 $mySingleton->test(); 30 $mySingleton2 = MySingleton::getInstance();
9、观察者模式(observer)
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
1 <?php 2 3 // 当用户注册成功时,邮件观察者或短信观察者则收到相应的通知,发送邮件和短信给用户 4 5 // 通知者-用户类 6 class User implements SplSubject 7 { 8 public $name; 9 public $email; 10 public $mobile; 11 // 当前观察者集合 12 private $observers = []; 13 14 public function __construct() 15 { 16 $this->observers = new SplObjectStorage(); 17 } 18 19 // 模拟注册 20 public function register($name, $email, $mobile) 21 { 22 $this->name = $name; 23 $this->email = $email; 24 $this->mobile = $mobile; 25 26 // business handle and register success 27 $reg_result = true; 28 if ($reg_result) { 29 $this->notify(); // 注册成功 所有的观察者将会收到此主题的通知 30 return true; 31 } 32 33 return false; 34 } 35 36 public function attach(SplObserver $observer) 37 { 38 $this->observers->attach($observer); 39 } 40 41 public function detach(SplObserver $observer) 42 { 43 $this->observers->detach($observer); 44 } 45 46 public function notify() 47 { 48 if ($this->observers) { 49 foreach ($this->observers as $observer) { 50 $observer->update($this); 51 } 52 } 53 } 54 } 55 56 class EmailObserver implements SplObserver 57 { 58 public function update(SplSubject $user) 59 { 60 echo "send email to " . $user->email . PHP_EOL; 61 } 62 } 63 64 class SmsObserver implements SplObserver 65 { 66 public function update(SplSubject $user) 67 { 68 echo "send sms to " . $user->mobile . PHP_EOL; 69 } 70 } 71 72 // 业务 73 $user = new User(); 74 $emailObserver = new EmailObserver(); 75 $smsObserver = new SmsObserver(); 76 77 // 添加观察者 78 $user->attach($emailObserver); 79 $user->attach($smsObserver); 80 81 // 删除Sms 观察者 82 $user->detach($smsObserver); 83 84 // 根据注册结果通知观察者,观察者做相应的处理 85 $user->register("小明", "123@qq.com", "18502365895");
观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。这其实也是依赖倒置原则的体现。
但是上面的写法还有一点不足,尽管已经用了依赖倒置原则,但是抽象通知者还是依赖于抽象观察者,也就是说万一没有了抽象观察者这样的接口,我这通知的功能就完不成了。另外就是每个具体观察者,它不一定是更新的方法要调用。所以在上面基础上,我们再做一下改进:
1 <?php 2 3 // 事件委托实现 4 // 看股票的同事 5 class StockObserver 6 { 7 private $name; 8 private $sub; 9 10 public function __construct(string $name, Subject $sub) 11 { 12 $this->name = $name; 13 $this->sub = $sub; 14 } 15 16 // 关闭股票行情 17 public function closeStockMarket() 18 { 19 echo $this->sub->subjectState() . PHP_EOL . $this->name . " 关闭股票行情,继续工作!<br/>"; 20 } 21 } 22 23 // 看NBA的同事 24 class NBAObserver 25 { 26 private $name; 27 private $sub; 28 29 public function __construct(string $name, Subject $sub) 30 { 31 $this->name = $name; 32 $this->sub = $sub; 33 } 34 35 // 关闭NBA直播 36 public function closeNBADirectSeeding() 37 { 38 echo $this->sub->subjectState() . PHP_EOL . $this->name . " 关闭NBA直播,继续工作!<br/>"; 39 } 40 } 41 42 interface Subject 43 { 44 public function notify(); 45 46 public function subjectState(); 47 } 48 49 // 老板作为通知者 50 class Boss implements Subject 51 { 52 public $action; 53 public $event = 'update'; 54 55 // 通知 56 public function notify() 57 { 58 call_user_func($this->event, $this); 59 } 60 61 // 老板状态 62 public function subjectState($value = '') 63 { 64 if (isset($value) && !empty($value)) { 65 $this->action = $value; 66 return $this; 67 } 68 return $this->action; 69 } 70 } 71 72 // 客户端代码如下: 73 74 $boss = new Boss(); 75 function update($boss) 76 { 77 $coworker1 = new StockObserver("小明", $boss); 78 $coworker2 = new NBAObserver("小华", $boss); 79 $coworker1->closeStockMarket(); 80 $coworker2->closeNBADirectSeeding(); 81 } 82 $boss->subjectState("老板回来了"); 83 $boss->notify();
参考资料:
程杰《大话设计模式》