设计模式六大原则
- 开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
- 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象.
- 依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
- 单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
- 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
- 迪米特法则:一个对象应该对其他对象保持最少的了解。
1.单例设计模式(Singleton)
所谓单例模式,即在应用程序中最多只有该类的一个实例存在,一旦创建,就会一直存在于内存中!
应用场景:
单例设计模式常应用于数据库类设计,采用单例模式,只连接一次数据库,防止打开多个数据库连接。
一个单例类应具备以下特点:
单例类不能直接实例化创建,而是只能由类本身实例化。因此,要获得这样的限制效果,构造函数必须标记为private,从而防止类被实例化。
需要一个私有静态成员变量来保存类实例和公开一个能访问到实例的公开静态方法。
在PHP中,为了防止他人对单例类实例克隆,通常还为其提供一个空的私有__clone()
方法。
单例模式的例子:
1 <?PHP 2 3 /** 4 * Singleton of Database 5 */ 6 class Database 7 { 8 // We need a static private variable to store a Database instance. 9 privatestatic $instance; 10 11 // Mark as private to prevent it from being instanced. 12 private function__construct() 13 { 14 // Do nothing. 15 } 16 17 private function__clone() 18 { 19 // Do nothing. 20 } 21 22 public static function getInstance() 23 { 24 if (!(self::$instance instanceof self)) { 25 self::$instance = new self(); 26 } 27 28 return self::$instance; 29 } 30 } 31 32 $a =Database::getInstance(); 33 $b =Database::getInstance(); 34 35 // true 36 var_dump($a === $b);
2.工厂设计模式
要是当操作类的参数变化时,只用改相应的工厂类就可以
工厂设计模式常用于根据输入参数的不同或者应用程序配置的不同来创建一种专门用来实例化并返回其对应的类的实例。
使用场景:使用方法 new实例化类,每次实例化只需调用工厂类中的方法实例化即可。
优点:由于一个类可能会在很多地方被实例化。当类名或参数发生变化时,工厂模式可简单快捷的在工厂类下的方法中 一次性修改,避免了一个个的去修改实例化的对象。
我们举例子,假设矩形、圆都有同样的一个方法,那么我们用基类提供的API来创建实例时,通过传参数来自动创建对应的类的实例,他们都有获取周长和面积的功能。
例子
1 <?PHP 2 3 interface InterfaceShape 4 { 5 function getArea(); 6 function getCircumference(); 7 } 8 9 /** 10 * 矩形 11 */ 12 class Rectangle implements InterfaceShape 13 { 14 private $width; 15 private $height; 16 17 public function __construct($width,$height) 18 { 19 $this->width = $width; 20 $this->height = $height; 21 } 22 23 public function getArea() 24 { 25 return $this->width* $this->height; 26 } 27 28 public function getCircumference() 29 { 30 return 2 * $this->width + 2 * $this->height; 31 } 32 } 33 34 /** 35 * 圆形 36 */ 37 class Circle implements InterfaceShape 38 { 39 private $radius; 40 41 function __construct($radius) 42 { 43 $this->radius = $radius; 44 } 45 46 47 public function getArea() 48 { 49 return M_PI * pow($this->radius,2); 50 } 51 52 public function getCircumference() 53 { 54 return 2 * M_PI * $this->radius; 55 } 56 } 57 58 /** 59 * 形状工厂类 60 */ 61 class FactoryShape 62 { 63 public static function create() 64 { 65 switch (func_num_args()) { 66 case1: 67 return newCircle(func_get_arg(0)); 68 case2: 69 return newRectangle(func_get_arg(0),func_get_arg(1)); 70 default: 71 # code... 72 break; 73 } 74 } 75 } 76 77 $rect =FactoryShape::create(5,5); 78 // object(Rectangle)#1 (2) { ["width":"Rectangle":private]=> int(5) ["height":"Rectangle":private]=> int(5) } 79 var_dump($rect); 80 echo "<br>"; 81 82 // object(Circle)#2 (1) { ["radius":"Circle":private]=> int(4) } 83 $circle =FactoryShape::create(4); 84 var_dump($circle); 85
3.观察者设计模式
观察者模式是挺常见的一种设计模式,使用得当会给程序带来非常大的便利,使用得不当,会给后来人一种难以维护的想法。
- 很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、MysqL优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的加群(点击→)677079770
使用场景:用户登录,需要写日志,送积分,参与活动 等使用消息队列,把用户和日志,积分,活动之间解耦合
什么是观察者模式?一个对象通过提供方法允许另一个对象即观察者 注册自己)使本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。这些观察者使用该信息执行的操作与可观察的对象无关。结果是对象可以相互对话,而不必了解原因。观察者模式是一种事件系统,意味着这一模式允许某个类观察另一个类的状态,当被观察的类状态发生改变的时候,观察类可以收到通知并且做出相应的动作;观察者模式为您提供了避免组件之间紧密耦。看下面例子你就明白了!
1 <?PHP 2 3 /* 4 观察者接口 5 */ 6 interface InterfaceObserver 7 { 8 function onListen($sender,$args); 9 function getObserverName(); 10 } 11 12 // 可被观察者接口 13 interface InterfaceObservable 14 { 15 function addObserver($observer); 16 function removeObserver($observer_name); 17 } 18 19 // 观察者抽象类 20 abstract class Observer implements InterfaceObserver 21 { 22 protected $observer_name; 23 24 function getObserverName() 25 { 26 return $this->observer_name; 27 } 28 29 function onListen($sender,$args) 30 { 31 32 } 33 } 34 35 // 可被观察类 36 abstract class Observable implements InterfaceObservable 37 { 38 protected $observers = array(); 39 40 public function addObserver($observer) 41 { 42 if ($observerinstanceofInterfaceObserver) 43 { 44 $this->observers[] = $observer; 45 } 46 } 47 48 public function removeObserver($observer_name) 49 { 50 foreach ($this->observersas $index => $observer) 51 { 52 if ($observer->getObserverName() === $observer_name) 53 { 54 array_splice($this->observers,$index,1); 55 return; 56 } 57 } 58 } 59 } 60 61 // 模拟一个可以被观察的类 62 class A extends Observable 63 { 64 public function addListener($listener) 65 { 66 foreach ($this->observersas $observer) 67 { 68 $observer->onListen($this,$listener); 69 } 70 } 71 } 72 73 // 模拟一个观察者类 74 class B extends Observer 75 { 76 protected $observer_name = 'B'; 77 78 public function onListen($sender,$args) 79 { 80 var_dump($sender); 81 echo "<br>"; 82 var_dump($args); 83 echo "<br>"; 84 } 85 } 86 87 // 模拟另外一个观察者类 88 class C extends Observer 89 { 90 protected $observer_name = 'C'; 91 92 public function onListen($sender,$args) 93 { 94 var_dump($sender); 95 echo "<br>"; 96 var_dump($args); 97 echo "<br>"; 98 } 99 } 100 101 $a = new A(); 102 // 注入观察者 103 $a->addObserver(new B()); 104 $a->addObserver(new C()); 105 106 // 可以看到观察到的信息 107 $a->addListener('D'); 108 109 // 移除观察者 110 $a->removeObserver('B'); 111 112 // 打印的信息: 113 // object(A)#1 (1) { ["observers":protected]=> array(2) { [0]=> object(B)#2 (1) { ["observer_name":protected]=> string(1) "B" } [1]=> object(C)#3 (1) { ["observer_name":protected]=> string(1) "C" } } } 114 // string(1) "D" 115 // object(A)#1 (1) { ["observers":protected]=> array(2) { [0]=> object(B)#2 (1) { ["observer_name":protected]=> string(1) "B" } [1]=> object(C)#3 (1) { ["observer_name":protected]=> string(1) "C" } } } 116 // string(1) "D"
4.适配器模式
将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本的由于接口不兼容而不能一起工作的那些类可以一起工作。
应用场景:老代码接口不适应新的接口需求,或者代码很多很乱不便于继续修改,或者使用第三方类库。
例如:PHP连接数据库的方法:MysqL,MysqLi,pdo,可以用适配器统一
1 //老的代码 2 3 class User { 4 5 private $name; 6 7 function __construct($name) { 8 9 $this->name = $name; 10 11 } 12 13 public function getName() { 14 15 return $this->name; 16 17 } 18 19 } 20 //新代码,开放平台标准接口 21 22 interface UserInterface { 23 24 function getUserName(); 25 26 } 27 28 class UserInfo implements UserInterface { 29 30 protected $user; 31 32 function __construct($user) { 33 34 $this->user = $user; 35 36 } 37 38 public function getUserName() { 39 40 return $this->user->getName(); 41 42 } 43 44 } 45 $olduser = new User('张三'); 46 47 echo $olduser->getName()."n"; 48 49 $newuser = new UserInfo($olduser); 50 51 echo $newuser->getUserName()."n"; 52
5.策略模式
将一组特定的行为和算法封装成类,以适应某些特定的上下文环境。
使用场景:个人理解,策略模式是依赖注入,控制反转的基础
例如:一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有广告位展示不同的广告
MaleUserStrategy.PHP
1 <?PHP 2 3 namespace IMooc; 4 class MaleUserStrategy implements UserStrategy { 5 function showAd() 6 { 7 echo "IPhone6"; 8 } 9 10 function showCategory() 11 { 12 echo "电子产品"; 13 } 14 }
FemaleUserStrategy.PHP
1 <?PHP 2 3 namespace IMooc; 4 5 class FemaleUserStrategy implements UserStrategy { 6 function showAd() 7 { 8 echo "2014新款女装"; 9 } 10 function showCategory() 11 { 12 echo "女装"; 13 } 14 } 15
UserStrategy.PHP
1 <?PHP 2 3 namespace IMooc; 4 5 interface UserStrategy { 6 function showAd(); 7 function showCategory(); 8 } 9 10 11 <?PHP 12 interface FlyBehavior{ 13 public function fly(); 14 } 15 16 class FlyWithWings implements FlyBehavior{ 17 public function fly(){ 18 echo "Fly With Wings \n"; 19 } 20 } 21 22 class FlyWithNo implements FlyBehavior{ 23 public function fly(){ 24 echo "Fly With No Wings \n"; 25 } 26 } 27 class Duck{ 28 private $_flyBehavior; 29 public function performFly(){ 30 $this->_flyBehavior->fly(); 31 } 32 33 public function setFlyBehavior(FlyBehavior $behavior){ 34 $this->_flyBehavior = $behavior; 35 } 36 } 37 38 class RubberDuck extends Duck{ 39 } 40 // Test Case 41 $duck = new RubberDuck(); 42 43 /* 想让鸭子用翅膀飞行 */ 44 $duck->setFlyBehavior(new FlyWithWings()); 45 $duck->performFly(); 46 47 /* 想让鸭子不用翅膀飞行 */ 48 $duck->setFlyBehavior(new FlyWithNo()); 49 $duck->performFly();
6.装饰器模式
使用场景:当某一功能或方法draw,要满足不同的功能需求时,可以使用装饰器模式;实现方式:在方法的类中建addDecorator(添加装饰器),beforeDraw,afterDraw 3个新方法, 后2个分别放置在要修改的方法draw首尾.然后创建不同的装器类(其中要包含相同的,afterDraw方法)能过addDecorator添加进去,然后在beforeDraw,afterDraw中循环处理,与观察者模式使用有点相似
1.装饰器模式(Decorator),可以动态地添加修改类的功能
2.一个类提供了一项功能,如果要在修改并添加额外的功能,传统的编程模式,需要写一个子类继承它,并重新实现类的方法
3.使用装饰器模式,仅需在运行时添加一个装饰器对象即可实现,可以实现最大的灵活性
DrawDecorator.PHP
1 <?PHP 2 namespace IMooc; 3 4 interface DrawDecorator 5 { 6 function beforeDraw(); 7 function afterDraw(); 8 }
Canvas.PHP
1 <?PHP 2 namespace IMooc; 3 4 class Canvas 5 { 6 public $data; 7 protected $decorators = array(); 8 9 //Decorator 10 function init($width = 20,$height = 10) 11 { 12 $data = array(); 13 for($i = 0; $i < $height; $i++) 14 { 15 for($j = 0; $j < $width; $j++) 16 { 17 $data[$i][$j] = '*'; 18 } 19 } 20 $this->data = $data; 21 } 22 23 function addDecorator(DrawDecorator $decorator) 24 { 25 $this->decorators[] = $decorator; 26 } 27 28 function beforeDraw() 29 { 30 foreach($this->decorators as $decorator) 31 { 32 $decorator->beforeDraw(); 33 } 34 } 35 36 function afterDraw() 37 { 38 $decorators = array_reverse($this->decorators); 39 foreach($decorators as $decorator) 40 { 41 $decorator->afterDraw(); 42 } 43 } 44 45 function draw() 46 { 47 $this->beforeDraw(); 48 foreach($this->data as $line) 49 { 50 foreach($line as $char) 51 { 52 echo $char; 53 } 54 echo "<br />\n"; 55 } 56 $this->afterDraw(); 57 } 58 59 function rect($a1,$a2,$b1,$b2) 60 { 61 foreach($this->data as $k1 => $line) 62 { 63 if ($k1 < $a1 or $k1 > $a2) continue; 64 foreach($line as $k2 => $char) 65 { 66 if ($k2 < $b1 or $k2 > $b2) continue; 67 $this->data[$k1][$k2] = ' '; 68 } 69 } 70 } 71 }
ColorDrawDecorator.PHP
1 <?PHP 2 namespace IMooc; 3 4 class ColorDrawDecorator implements DrawDecorator 5 { 6 protected $color; 7 function __construct($color = 'red') 8 { 9 $this->color = $color; 10 } 11 function beforeDraw() 12 { 13 echo "<div style='color: {$this->color};'>"; 14 } 15 function afterDraw() 16 { 17 echo "</div>"; 18 } 19 }
index.PHP