一、单例模式
- 一个类, 仅允许实例化一次,即仅允许创建一个实例,因此可以把构造函数私有化,不允许在类外实例化。
- 应用场景: 数据库的连接对象, HTTP请求, 游戏中的主角等
<?php
class Db {
private static $pdo = null;
public static function GetInstance(...$params) {
if (empty(self::$pdo)) {// is_empty()
// 因为构造函数没有返回值, 所以实例当前类并赋值给静态属性的过程,只能在构造方法中完成
// self::$pdo = new self(...$params);
new self(...$params);
}
return self::$pdo;
}
// 当前构造方法是私有的, 仅表示在类外部不允许调用, 但是在类的内部仍然有效的
private function __construct(...$params) {
// 当前$connectParams 是一个索引数组,每一个元素对应着不同的连接参数
$dsn = $params[0];
$username = $params[1];
$password = $params[2];
// 在私有的构造方法中完成类实例的创建过程
// 创建一个PDO类实例, 并赋值给当前类实例self::$pdo
// return new \PDO($dsn, $username, $password); 此方法错误,构造函数没有返回值
self::$pdo = new \PDO($dsn, $username, $password);
}
// 私有化克隆方法
private function __clone() {
}
}
// 为简化代码, 使用剩余参数来传参
$params = ['mysql:host=localhost;dbname=sordidez', 'root', 'root'];
// 做一个数据表查询来演示一下, 数据的格式化自己完成
$pdo = Db::GetInstance(...$params);
$sql = 'SELECT *FROM ' . 'movies';
$stmt = $pdo->prepare($sql);
$stmt->execute();
echo '<pre>' . print_r($stmt->fetch(PDO::FETCH_ASSOC), true);
?>
二、工厂模式
- 主要用于批量创建对象,使创建对象的过程剥离出来,标准化
- 适合于一个类有多个实例, 而这些实例会在不同的文件中被创建和引用
- 使用工厂模式来创建对象, 可以实现, 一处修改, 全局生效
namespace admin;
class Demo{
}
$obj = new Demo();
$obj = new Demo();
$obj = new Demo();
# 由于业务需要, Demo2类的类名需要改变,那么所有引用到这个类名的代码全部需要修改
Demo的实例化过程剥离出来,由一个专门的类去完成, 将会避免这种情况发生
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的设计思想
M:Model:模型、数据,主要针对的是数据库的操作
V:View:视图、HTML文档,浏览器用户看到内容/页面
C:Controller:控制器,模型和视图都必须在视图中调用
四、MVC实战
MVC实战、依赖注入、服务容器、门面模式:Facade 一起写了
Model.php
<?php
// 模型类:操作数据库
// 从数据库中查询,返回的是数据是二维数组类型
// 模拟从数据库中取出数据(SQL语句执行)
class Model {
public function getData() {
return [
['uid' => 1, 'name' => '苹果电脑', 'model' => 'MacBook Pro', 'price' => 25800],
['uid' => 2, 'name' => '华为手机', 'model' => 'P30 Pro', 'price' => 4988],
['uid' => 3, 'name' => '小爱同学', 'model' => 'AI 音箱', 'price' => 299],
];
}
}
?>
View.php
<?php
// 视图层:渲染数据
//为简化代码,这里忽略数据库操作,直接以二维数组模拟数据库查询的结果集
class View {
public function fetch($data) {
// 用一张表格的方式把Model中的数据渲染出来,把表格拆分成字符串,字符串里面都是一些HTML标签用<table>来写
$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>';
// 循环一个数组,循环的时候每次是一行数据,所以先输出一个<tr>标签 然后在中间 输出<td>标签 把数据打印出来
foreach ($data as $user) {
$table .= '<tr>';
$table .= '<td>' . $user['uid'] . '</td>';
$table .= '<td>' . $user['name'] . '</td>';
$table .= '<td>' . $user['model'] . '</td>';
$table .= '<td>' . $user['price'] . '</td>';
$table .= '</tr>';
// Heredoc语法实现多行字符串,类似双引号功能
// $table .= <<< PRODUCT
// <tr>
// <td>{$user['uid']}</td>
// <td>{$user['name']}</td>
// <td>{$user['model']}</td>
// <td>{$user['price']}</td>
// </tr>
// PRODUCT;
}
$table .= '</table>';
// 返回表格字符串
return $table;
}
}
?>
Controller.php
将其他类的实例化直接在控制器中完成
<?php
/**
* mvc 思想
* 任务:将商品信息表展示出来
*/
// 加载'模型类'
require 'Model.php';
// 加载'视图类'
require 'View.php';
// 控制器
class Controller{
public function index(){
//1、获取数据
$model = new Model();
$data = $model->getData();
//2、渲染模板/视图
$view = new View();
return $view->fetch($data);
}
}
//客户端调用控制器
$controller = new Controller();
echo $controller->index();
?>
存在的问题:
- Model类和View类的实例化,都在Controller类中完成,导致Controller类严重依赖Model类和View类;
- Model类和View类的构造方式的变化,将会直接改变实例化的过程,导致Controller类不能独立于这二个类;
- 即,Controller类,严重依赖Model类和View类,这种现象,就是我们常说的的: 代码之间的耦合度太高
- 比较好的解决方案是,将Model和View类的实例化过程放在Controller类之外完成,而将他们的对象以参数方式传入到
解决方案:
1. 依赖注入:对象可以像其它普通类型参数一样,进行传递
2. 依赖注入本意:将当前类依赖的其它类实例,以方法参数的形式,注入到当前类中,简称:"依赖注入
"
五、依赖注入
依赖注入1.php
依赖注入:将其他类实例注入到普通方法中
- 将外部对象以参数形式注入到控制器的方法中
- 调用控制器时,先将Model和View实例化,再调用控制器的方法
- 将Model和View实例做为控制器方法中的参数传入
<?php
/**
* mvc 思想
* 任务:将商品信息表展示出来
* 将依赖注入点放在控制器中的普通方法
*/
// 加载'模型类'
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);
?>
- 依赖注入可以很好的解决类之间的代码耦合
- 其实依赖注入时,对象既可以注入到控制器方法中,也可以注入到控制器构造方法中
注入点:入口
1、控制器的普通方法
2、控制器的 - 直接注入到构造方法中,可以极大的简化代码,特别是在多个方法中都要用到这些外部对象时
依赖注入2.php
依赖注入:将其他类的实例注入到构造方法中
- Controller中设置一个对象容器属性用来保存外部注入的对象
- Controller类中创建构造方法,并将外部对象,做为构造方法的参数注入到类中
- 修改调用方法index(),删除注入参数,修改调用语句
- 调用控制器时,先将Model和View实例化,再调用控制器的方法
- 客户端调用时,Controller类直接使用外部对象为参数进行实例化
<?php
/**
* mvc 思想
* 任务:将商品信息表展示出来
* 将依赖注入点放在控制器中的构造方法
*/
// 加载'模型类'
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() {
//1、获取数据
$data = $this->model->getData();
//2、渲染模板/视图
return $this->view->fetch($data);
}
}
// 4. 在客户端调用控制器时,先在控制器的外部将Model和View实例化,再调用控制器的方法
$model = new Model();
$view = new View();
//5. 客户端调用时,Controller类直接使用外部对象为参数进行实例化
$controller = new Controller($model, $view);
echo $controller->index();
?>
六、服务容器
服务容器:Container ,用容器管理/封装对象
主要的两个功能:
1、将模型和视图的实例化过程与服务容器绑定。bind()
2、获取模型和视图实例,并返回给调用者()。make()
<?php
// 加载'模型类'
require 'Model.php';
// 加载'视图类'
require 'View.php';
/****************************************************************************/
// 创建容器类,容器中可保存很多类型,这里仅以类实例方法来演示
class Container {
// 创建容器属性,保存着类实例的创建方法
// 创建容器池/数组
protected $instance = [];
// bind()方法,将模型和视图类(Model/View)实例化的过程绑定/存入到容器中
// $alias: 类实例别名, $process: 类的实例化过程(语句)/函数/Closure:闭包类型
public function bind($alias, Closure $process) {
$this->instance[$alias] = $process;
}
// make()方法:将模型和视图类(Model/View)实例化,并将对象返回给调用者
// 创建类(Mode/View)的实例: 执行容器中的类的实例方法,$alias参数是容器中的类实例别名
public function make($alias, $params = []) {
// 实例化的过程(一个闭包函数)怎么调用呢?以回调的方式来调用这个过程,用call_user_func_array()方法,
// 第一个参数是要执行的函数或闭包,第二个参数是要传给当前这个函数或闭包的参数列表
return call_user_func_array($this->instance[$alias], $params);
}
}
// 将模型和视图绑定到服务容器中
$container = new Container();
// 将模型类(Model)实例化的方法绑定到容器中,标识为'model'
$container->bind('model', function () {return new Model();});
// 将视图类(View)实例化的方法绑定到容器中,标识为'view'
$container->bind('view', function () {return new View();});
/****************************************************************************/
// 控制器
class Controller {
// 将容器对象注入到控制器方法中
public function index(Container $container) {
//获取数据: 先通过容器获取模型(Model)类实例,再获取到数据
$data = $container->make('model')->getData();
//渲染模板:先通过容器获取视图(View)类实例,再把数据渲染出来
return $container->make('view')->fetch($data);
}
}
// MVC设计思想:C:Controller:控制器。模型和视图都必须通过控制器来调用
//客户端调用控制器
$controller = new Controller();
//以容器对象$container为实参调用
echo $controller->index($container);
?>
- 将模型和视图类(Model/View)的实例过程使用容器进行包装,是一个非常不错的解决方案
- 但是在控制器中充斥着大量的make()方法,吃相太难看,能不能再进行简化呢?
- 当然可以,现在有请"Facade门面模式"闪亮登场
七、门面模式:Facade
门面: 给容器中的Model和View的实例化方法的调用,提供一个统一的静态访问接口。即原本在Controller类中调用Container类中的Model和View类的实例化方法(make()方法),改为在Facade类中直接通过类名进行静态访问。
// 加载'模型类'
require 'Model.php';
// 加载'视图类'
require 'View.php';
/********************************************************************************/
// 创建容器类,容器中可保存很多类型,这里仅以类实例方法来演示
class Container {
// 创建容器属性,保存着类实例的创建方法
protected $instance = [];
// $alias: 类实例别名, $process: 类的实例化过程/函数/Closure闭包类型
public function bind($alias, Closure $process){
// 将类实例方法存入到容器中
$this->instance[$alias] = $process;
}
// 创建类的实例: 执行容器中的类的实例方法,$alias参数是容器中的类实例别名
public function make($alias, $params=[]){
return call_user_func_array($this->instance[$alias], []);
}
}
// 将要用到的类实例(Mode/View)绑定到容器中
$container = new Container();
// 将Model类实例的方法绑定到容器中,标识为'model'
$container->bind('model', function () { return new Model(); });
// 将View类实例的方法绑定到容器中,标识为'view'
$container->bind('view', function() { return new View(); });
/****************************************************************************/
//声明Facade类,来接管对容器中类实例方法的调用
class Facade
{
// 控制器中调用到了Model类中的getData()方法, View类中的view()方法
// 我们就为这二个访问创建一个统一访问的静态接口
// 调用方法使用的关键字static: 可以实现在静态继承上下文环境中,实现静态成员调用者的动态设置
// 后面的Product 就是 Facade类的子类,如果不使用static::就无法动态调用子类中的成员
// 如果你用不到子类,这里可以不用static后期静态绑定,可以使用self::替代
// 但是使用static , 会使代码更具健壮性,能用性
protected static $container = null;
protected static $data = [];
public static function initialize(Container $container) {
static::$container = $container; // static::是后期静态绑定的语法
}
// 设置获取数据的静态代理方法:getDate()
public static function getData(){
// static::$container 是当前引用类'Model'的实例
static::$data = static::$container->make('model')->getData();
}
// 设置渲染模板的静态代理方法:fetch()
public static function fetch(){
// static::$container 是当前引用类'View'的实例
return static::$container->make('view')->fetch(static::$data);
}
}
// 声明一个商品类
class Product extends Facade {
// 自定义的业务逻辑
}
/***************************************************************************/
// 控制器
class Controller {
//在构造方式中调用Facade中初始化方式,完成容器的实例化: 依赖注入
public function __construct(Container $container) {
Product::initialize($container);
}
// 将容器对象注入到控制器方法中
public function index(){
Product::getData(); // 获取数据
return Product::fetch(); // 渲染模板
}
}
//客户端调用
$controller = new Controller($container);
//以容器对象$container为实参调用
echo $controller->index();
八、路由原理
将一个字符串解析为数组,用explode(),第一个参数是分隔的标志,第二个参数是要处理的字符串。