PHP 设计模式与MVC

一、单例模式

  • 一个类, 仅允许实例化一次,即仅允许创建一个实例,因此可以把构造函数私有化,不允许在类外实例化。
  • 应用场景: 数据库的连接对象, 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();
 ?>
存在的问题:
  1. Model类和View类的实例化,都在Controller类中完成,导致Controller类严重依赖Model类和View类;
  2. Model类和View类的构造方式的变化,将会直接改变实例化的过程,导致Controller类不能独立于这二个类;
  3. 即,Controller类,严重依赖Model类和View类,这种现象,就是我们常说的的: 代码之间的耦合度太高
  4. 比较好的解决方案是,将Model和View类的实例化过程放在Controller类之外完成,而将他们的对象以参数方式传入到
解决方案:
1. 依赖注入:对象可以像其它普通类型参数一样,进行传递
2. 依赖注入本意:将当前类依赖的其它类实例,以方法参数的形式,注入到当前类中,简称:"依赖注入"

五、依赖注入

依赖注入1.php
依赖注入:将其他类实例注入到普通方法中
  1. 将外部对象以参数形式注入到控制器的方法
  2. 调用控制器时,先将Model和View实例化,再调用控制器的方法
  3. 将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. 其实依赖注入时,对象既可以注入到控制器方法中,也可以注入到控制器构造方法中
    注入点:入口
    1、控制器的普通方法
    2、控制器的
  3. 直接注入到构造方法中,可以极大的简化代码,特别是在多个方法中都要用到这些外部对象时
依赖注入2.php
依赖注入:将其他类的实例注入到构造方法中
  1. Controller中设置一个对象容器属性用来保存外部注入的对象
  2. Controller类中创建构造方法,并将外部对象,做为构造方法的参数注入到类中
  3. 修改调用方法index(),删除注入参数,修改调用语句
  4. 调用控制器时,先将Model和View实例化,再调用控制器的方法
  5. 客户端调用时,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(),第一个参数是分隔的标志,第二个参数是要处理的字符串。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值