前言:
你好,希望你会喜欢我的文章,能从中学到东西坚持看完,或者有共鸣、建议、指点,期待交流!
OOP及面向对象,封装、继承、多态为此三大特性。
为了开发中更加快速、优雅、简明、方便,因此在OOP的基础上诞生了各种设计模式,本质上其实是OOP编写代码的行为方式,巧妙运用OOP来开发各种应用程序,我们使用它可以帮助我们更加深入理解OOP。
也可看做是OOP思想一种历史文化的衍生,但不要被这种思想所限制,你可以在它的基础上开拓或创新(个人观点)。所谓推陈出新随着版本更替,相信今后也会有更多设计模式。
23种设计模式分为三类,创建型、结构型、行为型。在不同语言中,设计模式的实现虽说大同小异但也存在区别,下面主要描述PHP OOP中的一些设计模式。
设计模式:
单例模式、工厂模式、观察者模式、职责链模式、适配器模式、装饰器模式、桥接模式…等。
一、单例模式
属于创建型,一种创建对象相对完美的方式。
该模式仅仅提供一个唯一入口,为了解决频繁使用的类多次实例化调用和多次销毁;为了约束及良好利用系统资源;为了控制实例化的次数和数量。
遵循三私一公原则:
1.私有构造化,让其不能被外部实例化。
2.私有克隆法,或者在函数方法里自定义返回错误。
3.私有一个静态成员属性保存本身实例或其它实例对象。当有需求通过唯一入口访问进来,此静态成员属性如果包含需求所要的实例对象就直接返回此对象,反之则通过“路径”(目前框架普遍的自加载PSR-4规则)实例化这个需求类,然后将这个实例化对象保存进静态成员属性,供下次调用时直接获取不必再次实例化。
4.一个公共静态方法,访问本类的唯一入口。
如下:
<?php
/* 单例模式
* 静态属性在程序的运行到结束中,只加载一次,常驻内存
*/
class single{
private static $single = []; //静态成员属性 保存本身或其它实例
private function __construct(){} //私有构造方法
private function __clone(){} //私有克隆法
public static function getObj($class){
var_dump(self::$single);
//判断需求对象$class是否存在 存在就返回,反之则实例化这个类
if(isset(self::$single[$class])){
return self::$single[$class];
}else{
//假如我们需要此类本身($class为single)
$obj = new $class(); //同new single();
self::$single[$class] = $obj;//保存这个对象
}
return self::$single[$class];
}
}
single::getObj('single'); //输出 array(0) { }
echo '<hr>';
single::getObj('single'); //输出 array(1) { ["single"]=> object(single)#1 (0) { } }
二、工厂模式
也是属于创建型,顾名思义也是创建对象的方式。
当你要生成一个复杂对象,及在不同条件下需要不同实例对象,就可以用工厂模式。它为你省去在需要不同实例对象的时候多次’new class’,只用给其需求类的类名(参数),就能返回所需实例对象。同时具备可扩展性、直面接口、隐蔽性让其调用者不必知道具体代码逻辑。
此模式的弊点在于每当你有新需求,都需要扩展一个需求实体类,使之项目中类个数增多而且繁杂。
三类:
1.抽象类:定义其子类(实体类)的规则。
2.实体类:继承抽象类必须实现其定义的方法属性,及实现需求的具体逻辑代码。
3.工厂类:提供实体类的对象。
如下:
<?php
/* 工厂模式
* 所有接口类(interface)都是抽象类(abstract),且都不能被实例化
* interface只能有方法体不能有成员属性,一个类可以继承多个interface,而abstract只能被单继承
* 定义interface的时候不加'class',而abstract需要
* 在interface中的抽象方法只能是public的,默认也是public权限
* interface不能有方法体({}换成;结束),而abstract可以有方法体
* 当定义一个抽象方法其类也必须是抽象类
* abstract中可以有普通函数(有方法体) 抽象函数没有方法体({}换成;结束)
* 在子类实现abstract的方法时,其子类的可见性必须大于或等于抽象方法的定义
*/
interface role{
//假设做一个游戏有多种人物角色,规定其必须实现的方法
function roleName(); //不能有方法体 {}换成;结束
function weapon();
function genius();
}
//实体类
class warrior implements role{ //接口类必须使用implements继承 多继承以,隔开
public function roleName(){
return '战豪';
}
public function weapon(){
return '剑、戟、枪';
}
public function genius(){
return '每次攻击回复100气血';
}
}
class mage implements role{
public function roleName(){
return 'N维空间';
}
public function weapon(){
return '法杖、水晶球、毛笔';
}
public function genius(){
return '每释放一个法术便增加5%移速持续10s,最多叠加6层';
}
}
//工厂
class factory{
public static function getObj($class){
switch ($class){
case 'warrior':
$obj = new warrior();
break;
case 'mage':
$obj = new mage();
break;
default:
$obj = null;
}
return $obj;
}
}
$obj1 = factory::getObj('warrior');
$obj2 = factory::getObj('mage');
echo '战士:'.$obj1->roleName().'使用武器 '.$obj1->weapon().',天赋是'.$obj1->genius().'<hr>';
//输出 战士:战豪使用武器 剑、戟、枪,天赋是每次攻击回复100气血
echo '法师:'.$obj2->roleName().'使用武器 '.$obj2->weapon().',天赋是'.$obj2->genius();
//输出 法师:N维空间使用武器 法杖、水晶球、毛笔,天赋是每释放一个法术便增加5%移速持续10s,最多叠加6层
三、观察者模式
属于行为型模式,一个对象状态发生改变时就会通知所依赖的对象。
通俗点说就是当一个事件发生,就会触发跟这个事件牵连的一系列操作。
例如:
当某用户登录后,需要给其推送邮件,又要按喜好给其推荐文章,还要通知其粉丝上线消息…等等一系列依赖操作;
当某商城有用户下单,需要给用户发优惠券,又要给用户增添积分,还要通知商家订单信息…等等又是一系列依赖操作。
这时候我们可以用观察者模式很方便、优雅、便于管理的解决这些问题。例子中用户登录、下单这些事件就是被观察者,而依赖这个事件的发邮件、推送文章、发优惠券…都是观察者,我们联想到OOP中来,被观察者这个对象发生改变时,就可以通知所有依赖它的观察者对象。
当一个对象改变,依赖其对象很多的时候(及一对多关系),就可以使用观察者模式。
1.观察者:做依赖操作的实体类。
2.被观察者:发生事件的对象,得依项目场景来看,可以是一个类或一句代码。
3.通知:给观察者做维护的类,注册保存对象、删除对象、执行对象。
如下:
<?php
/*观察者模式*/
abstract class observer //定义观察者规则 及必须实现的方法
{ //假如用户下单,需要给商户推送短信、微信推送、邮箱推送
abstract protected function style($merchant); //推送信息的模板
abstract protected function push($obj); //推送逻辑代码
}
/*观察者 微信推送和短信推送*/
//微信推送
class wechat extends observer{
public function style($merchant){
$this->push($merchant);
}
protected function push($obj){
echo '给'.$obj.'微信推送成功 ';
}
}
//短信推送
class short extends observer{
public function style($merchant){
$this->push($merchant);
}
protected function push($obj){
echo '给'.$obj.'短信推送成功 ';
}
}
//邮箱推送
class email extends observer{
public function style($merchant){
$this->push($merchant);
}
protected function push($obj){
echo '给'.$obj.'邮件推送成功';
}
}
/*通知 维护观察者及存放 删除 执行*/
class subject{
private $observerList = []; //保存观察者对象
public function register($class){
//注册观察者 本质就是把观察者对象存到$observerList中
$this->observerList[] = $class;
}
public function runPush($merchant){
//一次性统一执行(通知)所有依赖(观察者)
$info = $this->observerList;
foreach($info as $v){
$v->style($merchant);
}
}
}
/*被观察者 用户下订单*/
//订单
class order{
static public function place($user){
echo $user.'下了一个订单';
}
}
$obj = new subject();
//注册(存放)观察者
$obj->register(new wechat());
$obj->register(new short());
$obj->register(new email());
order::place('用户'); // 输出 用户下了一个订单
$obj->runPush('商户'); // 输出 给商户微信推送成功 给商户短信推送成功 给商户邮件推送成功
四、职责链模式
还可以称为责任链模式,也是属于行为型模式,接收者以链式处理请求。
该模式将请求对象和接收对象分离。当一个请求过来,接收对象如果处理不了该请求就会传给下一个接收对象(也可以说是传给上级)。
其实很像我们经常使用的if sele/switch语句,当请求过来条件不满足就会往下传,“明明一个判断语句能解决的问题,为什么我们要用该模式了?”。
该模式远比普通判断语句灵活,判断语句是固定的从上往下传,但是职责链模式可以将请求传给任意一个接收者,而且不用担心此请求处理不了使系统奔溃;并且对于业务变动,普通判断语句会牵涉到很多地方需要修改代码。职责链模式却不会,它将请求和接收分离,两者之间不存在耦合。
1.抽象类:抽象定义接收者的上级,抽象规定接收者需实现属性,一个将请求传递到上级的普通方法。该抽象类解决耦合性。
2.实体类:也就是接收者对象,具体处理请求代码。
如下:
<?php
/*职责链模式
*假设去鞋店买一款鞋子,鞋店没有找代理商 代理商也没有 最终找厂家
*(鞋店->代理商->厂家)我们把鞋子分为三种类型(就以1,2,3代替)
*鞋店只有‘1’类型,代理商有‘1’‘2’,厂家有全部类型
*/
abstract class duty //抽象接收者
{
protected $superior; //申明下个接收对象
public function pass($obj){
$this->superior = $obj; //设置下个接收对象
}
abstract public function request($request); //处理请求函数
}
//鞋店
class shop extends duty{
public function request($request){
if($request == '1'){ //鞋店只有1号鞋子
echo '鞋店有('.$request.')号鞋子,成功出货'; return;
}else if(empty($this->superior)){ //如果下个接收者不存在 则设置
$this->superior = new agent(); //设置下个接收者(代理商)
}
$this->superior->request($request); //传个下个接收者(代理商)
}
}
//代理商
class agent extends duty{
public function request($request){
if($request == '1'||$request == '2'){
echo '代理商有('.$request.')号鞋子,成功出货'; return;
}else if(empty($this->superior)){
$this->superior = new factoay(); //设置下个接收者(厂家)
}
$this->superior->request($request); //传给下个接收者(厂家)
}
}
//厂家
class factoay extends duty{
public function request($request){
if($request == '1'||$request == '2'||$request == '3'){
echo '厂家有('.$request.')号鞋子,成功出货';
}else{
echo '抱歉没有这号鞋子';
}
}
}
//买1号鞋子
$obj = new shop();
$obj->request('1'); //鞋店有(1)号鞋子,成功出货
//买2号鞋子
$obj->request('2'); //代理商有(2)号鞋子,成功出货
//买3号鞋子
$obj->request('3'); //厂家有(3)号鞋子,成功出货
$obj = new agent();
$obj->request('3'); //厂家有(3)号鞋子,成功出货
$obj = new shop();
$obj->pass(new factoay()); //如果没货直接找厂家
$obj->request('3'); //厂家有(3)号鞋子,成功出货
$obj = new factoay();
$obj->request('3'); //厂家有(3)号鞋子,成功出货
五、适配器模式
属于结构型模式,给两个不兼容的接口提供桥梁。
当我们系统已经做好某个功能的API对接,此时出现了新需求而加入新的API对接,其中有可能会牵涉到很多代码修改,那么如何保证不需修改大量代码,又可以在两个或多个接口间随意调用。
该模式会提供一个公共接口(桥梁 / 适配器),实则就是提供一个单一类,通过该类来调用任意一个独立接口,它负责在接口间的切换。
适合用于一个功能涉及到多种相似接口的时候,如支付(微信、支付宝、银联)、登录(QQ、新浪)…等。
1.接口/实体类:调起接口/功能类的具体逻辑代码。
2.适配器:单一类,接口间/类间切换。
如下:
<?php
/* 适配器模式
* 假如业务上需满足QQ、新浪登录
* 主要简述在软件应用系统中,适配器的实现过程
*/
interface loginType{ //定义适配器接口规范
function login(); //调起登录
function callback(); //获取回调信息
}
//适配器
class loignAdaptor implements loginType
{
public $adaptor;
public function __construct($obj){
$this->adaptor = $obj; //将对象(接口) 放入$adaptor中
}
public function login(){
$this->adaptor->login(); //使用$adaptor中已放入对象的login()方法
}
public function callback(){
$this->adaptor->callback(); //同上
}
}
//QQ登录
class qqLogin{
public function login(){
echo '调起QQ登录';
}
public function callback(){
echo 'QQ登录成功,获取回调信息';
}
}
//新浪登录
class sinaLogin{
public function login(){
echo '调起新浪登录';
}
public function callback(){
echo '新浪登录成功,获取回调信息';
}
}
//qq登录
$obj = new loignAdaptor(new qqLogin());
$obj->login(); //输出 调起QQ登录
$obj->callback(); //输出 QQ登录成功,获取回调信息
//新浪登录
$obj = new loignAdaptor(new sinaLogin());
$obj->login(); //输出 调起新浪登录
$obj->callback(); //输出 新浪登录成功,获取回调信息
六、装饰器模式
属于结构型模式,对一个类进行装饰。
该模式在一个现有类的基础上扩展新功能,听起来类似子类继承。
不同之处在于此模式中装饰类和被装饰类都是独立个体,互不影响、不耦合,另外装饰器模式相对较灵活,但也复杂。
当你想扩展一个类,又不想影响其现有类,就可以使用它。
1.实体类:当前现有类。
2.装饰基类:装饰类父类,可以是一个抽象类
3.装饰类:实现装饰类的具体逻辑代码。
如下
<?php
/* 装饰器模式
* 假如我们描述一个果盘,现有类里有苹果,然后再分别定义两个装饰类 梨和橘
*/
abstract class compote{ //首先定义一个抽象类 果盘
abstract function fruits();
}
//现有实体类
class apple extends compote {
public function fruits(){
echo '果盘里有苹果';
}
}
//定义装饰抽象类(基类)
//因为是同样是抽象类 所以直接继承父类抽象属性
abstract class decorator extends compote { //继承果盘抽象类 和定义装饰类规则
protected $classObj;
public function getObj($obj){
$this->classObj = $obj;
}
public function objCompote(){
if(!empty($this->classObj)) {
$this->classObj->fruits(); //执行classObj中的 fruits();
}
}
}
//装饰类 梨
class pear extends decorator{
public function fruits(){
$this->objCompote();
echo ' 梨子';
}
}
//装饰类 橘
class orange extends decorator{
public function fruits(){
$this->objCompote();
echo ' 橘子';
}
}
$obj = new apple();
$obj->fruits(); //输出 果盘里有苹果
//添加装饰 梨子
$obj1 = new pear();
$obj1->getObj($obj); //当前$obj1对象中$classObj属性包含$obj对象
$obj1->fruits(); //输出 果盘里有苹果 梨子
//添加装饰 橘子
$obj2 = new orange();
$obj2->getObj($obj1); //当前$obj2中$classObj属性包含$obj1对象,且$obj1中$classObj又包含了$obj
$obj2->fruits(); //输出 果盘里有苹果 梨子 橘子
/* 综上模式中 利用基类方法getObj()包含存入对象,且呈现了一种叠加方式
* 当我们最终使用fruits()时,其中调用基类方法objCompote() 将存储对象的$classObj属性一层层剥开。
*
* 记录一个知识点:
* “当两个类继承同一个基类(父类)时,其两者各自的父类也是相对独立存在”,
* 就以上例子可以看出 成员属性$classObj赋值没有被重定义,而是实现了叠加。
*/
七、桥接模式
属于结构型模式,通过桥接结构解耦抽象化和实体,使两者独立。
其中抽象化并不是指抽象类,这里说的是一种相对的抽象方,此模式中抽象方通过桥接引用实体方。
当处理两者或两者以上的种类,每个种类又有多个类型,这种多维多级多变化的复杂结构问题时,该模式通过桥接手段使每种类型可以独立变化,减少互相之间的影响和耦合,且易于扩展。
1.抽象方基类:定义一个成员属性保存实体方对象,实现桥接引用。
2.抽象方:继承基类并实现抽象方具体逻辑代码的实体类。
3.实体方:实现实体方具体逻辑代码的实体类。
如下:
<?php
/* 桥接模式
* 假如一件产品有塑料、合金、压缩复合纸三种材质类型,
* 又对应有圆形、方形、三角形,还分别具有红、蓝两种颜色
* 把三材质看成抽象方,形状和颜色为实体方
*/
//定义材质(抽象方) 抽象类
abstract class texture{
public $shape; //定义一个保存形状对象的属性 做为桥接引用
public $color; //定义一个保存颜色对象的属性 做为桥接引用
public function __construct($shapeObj,$colorObj){
$this->shape = $shapeObj;
$this->color = $colorObj;
}
abstract public function texType();
}
//塑料
class plastic extends texture{
public function texType(){
echo '塑料材质、'.$this->shape->shaType().'、'.$this->color->colType();
}
}
//合金
class metal extends texture{
public function texType(){
echo '合金材质、'.$this->shape->shaType().'、'.$this->color->colType();
}
}
//压缩复合纸
class paper extends texture{
public function texType(){
echo '压缩复合纸材质、'.$this->shape->shaType().'、'.$this->color->colType();
}
}
//实体方
//定义形状抽象类
abstract class shape{
abstract public function shaType();
}
//圆形
class round extends shape{
public function shaType(){
return '圆形';
}
}
//方形
class square extends shape{
public function shaType(){
return '方形';
}
}
//三角形
class triangle extends shape{
public function shaType(){
return '三角形';
}
}
//定义颜色抽象类
abstract class color{
abstract public function colType();
}
//红色
class red extends color{
public function colType(){
return '红色';
}
}
//蓝色
class blue extends color{
public function colType(){
return '蓝色';
}
}
//塑料材质
$obj = new plastic(new round(),new blue());
$obj->texType(); //输出 塑料材质、圆形、蓝色
//合金材质
$obj = new metal(new square(),new red());
$obj->texType(); //输出 合金材质、方形、红色
//压缩复合纸材质
$obj = new paper(new triangle(),new blue());
$obj->texType(); //输出 压缩复合纸材质、三角形、蓝色
/*******************本文持续更新*******************/