🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# :-: 一、单例模式 * 一个类, 仅允许实例化一次,即仅允许创建一个实例 * 应用场景: 数据库的连接对象, HTTP请求, 游戏中的主角等 ```php class People{ } $obj1 = new People(); $obj2 = new People(); var_dump($obj1); echo '<br>'; var_dump($obj2); // Temp 类被实例化了二次,创建了二个完全不同的对象 var_dump($obj1 === $obj2); // false, 说明这二个对象完全不相同 ``` * 如何时才能确保一个类仅被实例化一次呢? 使用单例模式可以实现 ```php class Demo{ // 将构造方法私有化, 使用户在类的外部,无法通过"new"关键字来实例化该类 private function __construct(){ } // 此时类外部无法通过new实例化, 所以类的实例化工作只能在类中完成了 // 声明一个静态属性,用来保存当前类的实例 private static $instance = null; // 如果不赋值,默认值就是null, 此处用null初始化仅仅是使代码更清晰 // 声明一个实例当前类的静态方法, 思考一下为什么必须设置静态的?(因为外部已无法实例化该类创建对象了) public static function getInstance(){ // 如果当前类实例为null,说明当前类尚未被实例化过,那我们就就需要先将类实例化后,再返回调用者 if (is_null(self::$instance)) { self::$instance = new self(); } // 返回当前类实例 return self::$instance; } // 当获取到当前类实例之后, 外部是可以通过clone方法来快速复制该对象的, 所以类内部也应该将__clone()禁用 // 只需要将__clone()方法的访问限方式定为私有private即可 private function __clone(){ } } //$obj1 = new Demo(); // 报错, 因构造方法私有化, 外部已无法用new 来完成类的实例化 $obj1 = Demo::getInstance(); $obj2 = Demo::getInstance(); var_dump($obj1); echo '<br>'; var_dump($obj2); echo '<br>'; // 验证Demo类是否仅实例化了一次 var_dump($obj1 === $obj2); // true , 完全符合要求 echo '<hr>'; ``` >[info] 数据库连接为例, 演示单例模式的应用 ```php class Db{ private static $pdo = null; public static function getInstance(...$connectParams){ if (is_null(self::$pdo)) { // 因为构造函数没有返回值, 所以实例当前类并赋值给静态属性的过程,只能在构造方法中完成 // self::$pdo = new self(...$connectParams); new self(...$connectParams); } return self::$pdo; } // 当前构造方法是私有的, 仅表示在类外部不允许调用, 但是在类的内部仍然有效的 private function __construct(...$connectParams){ // 当前$connectParams 是一个索引数组,每一个元素对应着不同的连接参数 $dsn = $connectParams[0]; $username = $connectParams[1]; $password = $connectParams[2]; // 在私有的构造方法中完成类实例的创建过程 // 创建一个PDO类实例, 并赋值给当前类实例self::$pdo self::$pdo = new \PDO($dsn, $username, $password); } // 私有化克隆方法 private function __clone(){ } } // 为简化代码, 使用剩余参数来传参 $connectParams = ['mysql:host=localhost;dbname=ouyangke', 'root', 'root']; $pdo = Db::getInstance(...$connectParams); // 做一个数据表查询来演示一下, 数据的格式化大家自己完成 print_r($pdo->query('select * from user')->fetchAll()); ``` ***** # :-: 二、工厂模式 * 主要用于批量创建对象,使创建对象的过程剥离出来,标准化 * 适合于一个类有多个实例, 而这些实例会在不同的地方被创建和引用 * 使用工厂模式来创建对象, 可以实现, 一处修改, 全局生效 ```php namespace admin; class Demo{ } $obj = new Demo(); $obj = new Demo(); $obj = new Demo(); # 由于业务需要, Demo2类的类名需要改变,那么所有引用到这个类名的代码全部需要修改 ``` >[info] Demo的实例化过程剥离出来,由一个专门的类去完成, 将会避免这种情况发生 ```php namespace admin; class Test1{ public function __construct($arg1){ echo '对象创建成功, 参数是: ' . $arg1; } } class Test2{ public function __construct($arg1, $arg2){ echo '对象创建成功, 参数是: ' . implode(', ', [$arg1, $arg2]); } } class Test3{ public function __construct($arg1, $arg2, $arg3){ echo '对象创建成功, 参数是: ' . implode(', ', [$arg1, $arg2, $arg3]); } } class Test4{ public function __construct(){ echo '对象创建成功, 无参数'; } } // 工厂类: 专用于创建类实例 class Factory{ /** * @param [String] 需要实例化的类名称 * @param [Array] 实例化时需要传入的参数,使用剩余参数,可以自适应数量变化 * @return [Object] 类实例 */ public static function create($className, ...$arguments){ // 剩余参数的展开规则: $arguuments 是数组,...可以将它展开 return new $className(...$arguments); } } Factory::create(Test1::class, 100); echo '<hr>'; Factory::create(Test2::class, 100, 200); echo '<hr>'; Factory::create(Test3::class, 100, 200, 300); echo '<hr>'; Factory::create(Test4::class); ``` ***** # :-: 三、MVC模式的原理与实现 :-: ![mvc](http://kanyun.8car.net/php/mvc.gif) >[info] Model.php 文件 ```php //模型类: 用于数据库操作 class Model{ public function getData(){ return [ ['uid'=>1, 'name'=>'欧阳克','phone'=>'18011112222','age'=>18], ['uid'=>2, 'name'=>'黄蓉','phone'=>'13011113333','age'=>16], ['uid'=>3, 'name'=>'郭靖','phone'=>'18722224444','age'=>22], ]; } } ``` >[info] View.php 文件 ```php //视图类: 渲染数据 //为简化代码,这里忽略数据库操作,直接以二维数组模拟数据库查询的结果集 class View{ public function fetch($data){ $table = '<table border="1" cellspacing="0" width="400">'; $table .= '<caption>用户信息表</caption>'; $table .= '<tr bgcolor="lightblue"><th>ID</th><th>姓名</th><th>手机</th><th>年龄</th></tr>'; foreach ($data as $user) { $table .= '<tr>'; $table .= '<td>' . $user['uid'] . '</td>'; $table .= '<td>' . $user['name'] . '</td>'; $table .= '<td>' . $user['phone'] . '</td>'; $table .= '<td>' . $user['age'] . '</td>'; $table .= '</tr>'; // Heredoc语法实现多行字符串,类似双引号功能 // $table .= <<< PRODUCT // <tr> // <td>{$user['uid']}</td> // <td>{$user['name']}</td> // <td>{$user['phone']}</td> // <td>{$user['age']}</td> // </tr> // PRODUCT; } $table .= '</table>'; return $table; } } ``` >[info] Controller.php 文件 ```php /** * mvc 思想 * 任务:将商品信息表展示出来 */ // 加载'模型类' require 'Model.php'; // 加载'视图类' require 'View.php'; // 控制器 class Controller{ public function index(){ //获取数据 $model = new Model(); $data = $model->getData(); //渲染模板 $view = new View(); return $view->fetch($data); } } //客户端调用 $controller = new Controller(); echo $controller->index(); ``` * 存在的问题 1. Model类和View类的实例化,都在Controller类中完成,导致Controller类严重依赖Model类和View类; 2. Model类和View类的构造方式的变化,将会直接改变实例化的过程,导致Controller类不能独立于这二个类; 3. 即,Controller类,严重依赖Model类和View类,这种现象,就是我们常说的的: 代码之间的耦合度太高 4. 比较好的解决方案是,将Model和View类的实例化过程放在Controller类之外完成,而将他们的对象以参数方式传入到 * 解决方案 1. 依赖注入:对象可以像其它普通类型参数一样,进行传递 2. 依赖注入本意:将当前类依赖的其它类实例,以方法参数的形式,注入到当前类中,简称:"依赖注入" >[info] 依赖注入:普通方法 >> Controller.php 文件 1. 将外部对象以参数形式注入到控制器的方法中 2. 调用控制器时,先将Model和View实例化,再调用控制器的方法 3. 将Model和View实例做为控制器方法中的参数传入 ```php // 加载'模型类' require 'Model.php'; // 加载'视图类' require 'View.php'; // 控制器 class Controller{ // 1. 将外部对象以参数形式注入到控制器的方法中; public function index(Model $model, View $view){ //获取数据 $data = $model->getData(); //渲染模板 return $view->fetch($data); } } // 2. 调用控制器时,先将Model和View实例化,再调用控制器的方法 $model = new Model(); $view = new View(); //客户端调用 $controller = new Controller(); //3. 将Model和View实例做为控制器方法中的参数传入 echo $controller->index($model, $view); ``` * 依赖注入可以很好的解决类之间的代码耦合 * 其实依赖注入时,对象即可以注入到控制器方法中,也可以注入到控制器构造方法中 * 直接注入到构造方法中,可以极大的简化代码,特别是在多个方法中都要用到这些外部对象时 >[info] 依赖注入:构造方法 >> Controller.php 文件 1. Controller中设置一个对象容器属性用来保存外部注入的对象 2. Controller类中创建构造方法,并将外部对象,做为构造方法的参数注入到类中 3. 修改调用方法index(),删除注入参数,修改调用语句 4. 调用控制器时,先将Model和View实例化,再调用控制器的方法 5. 客户端调用时,Controller类直接使用外部对象为参数进行实例化 ```php // 加载'模型类' require 'Model.php'; // 加载'视图类' require 'View.php'; // 控制器 class Controller { // 1. Controller中设置一个对象容器属性用来保存外部注入的对象 protected $model = null; protected $view = null; //2. Controller类中创建构造方法,并将外部对象,做为构造方法的参数注入到类中 public function __construct(Model $model, View $view){ $this->model = $model; $this->view = $view; } //3. 修改调用方法index(),删除注入参数,修改调用语句 public function index(){ //获取数据 $data = $this->model->getData(); //渲染模板 return $this->view->fetch($data); } } // 4. 调用控制器时,先将Model和View实例化,再调用控制器的方法 $model = new Model(); $view = new View(); //5. 客户端调用时,Controller类直接使用外部对象为参数进行实例化 $controller = new Controller($model, $view); echo $controller->index(); ```