[关闭]
@octopus 2020-10-19T00:39:52.000000Z 字数 19939 阅读 382

php7 + 设计模式 + http

php 面试


php

php 新特性

  1. # $a <=> $b, 当 $a 小于,等于,大于 $b 时分别返回 -1,0,1
  2. echo 1 <=> 1; // 0
  1. declare(strict=1); // 开启严格模式
  2. function (int $a){
  3. ...
  4. }
  1. $page = isset($_GET["page"]) ? $_GET["page"] : 1; // 旧
  2. $page = $_GET["page"] ?? 1; // php7
  1. define("Animal", ['dog','cat']);
  1. use Space\{ClassA, ClassB, ClassC as C}
  1. # 老版本是不能捕获下面的错误,php7 可以捕获错误
  2. try{
  3. undefindfunc();
  4. }cache(Error $e){
  5. var_dump($e);
  6. }
  7. # php7中全局异常回调函数也可以捕获错误
  8. set_exception_handler(function($e){
  9. var_dump($e);
  10. });

改变函数作用域

  1. class Test(){
  2. private $num = 1;
  3. }
  4. $f = function(){
  5. return $this->num + 1;
  6. };
  7. echo $f->call(new Test); // 2
  1. # 返回值为第一个参数除于第二个参数的值并取整
  2. intdiv(10,3); // 3
  1. # 老版本用 list
  2. $arr = [1,2,3];
  3. list($a, $b, $c) = $arr
  4. # php7
  5. $arr = [1,2,3];
  6. [$a, $b, $c] = $arr

php 原理

变量

  1. struct _zval_struct{
  2. zend_value value;
  3. union u1;
  4. union u2;
  5. }
  6. # 详细看三部分
  7. struct _zval_struct {
  8. union {
  9. zend_long lval; // 整型
  10. double dval; // 浮点型
  11. zend_refcounted *counted;
  12. zend_string *str; // 字符串
  13. zend_array *arr; // 数组
  14. zend_object *obj; // 对象
  15. zend_resource *res; // 资源
  16. zend_reference *ref; // 引用
  17. zend_ast_ref *ast;
  18. zval *zv;
  19. void *ptr;
  20. zend_class_entry *ce;
  21. zend_function *func;
  22. struct {
  23. uint32_t w1;
  24. uint32_t w2;
  25. } ww;
  26. } value;
  27. union {
  28. struct {
  29. ZEND_ENDIAN_LOHI_4(
  30. zend_uchar type, /* active type */
  31. zend_uchar type_flags,
  32. zend_uchar const_flags,
  33. zend_uchar reserved) /* call info for EX(This) */
  34. } v;
  35. uint32_t type_info;
  36. } u1;
  37. union {
  38. uint32_t var_flags;
  39. uint32_t next; /* hash collision chain */
  40. uint32_t cache_slot; /* literal cache slot */
  41. uint32_t lineno; /* line number (for ast nodes) */
  42. uint32_t num_args; /* arguments number for EX(This) */
  43. uint32_t fe_pos; /* foreach position */
  44. uint32_t fe_iter_idx; /* foreach iterator index */
  45. } u2;
  46. };

其中value部分, 是一个size_t大小(一个指针大小), 可以保存一个指针, 或者一个long, 或者一个double。
而type info部分则保存了这个zval的类型

  1. # u1 type_info
  2. /* regular data types */
  3. #define IS_UNDEF 0
  4. #define IS_NULL 1
  5. #define IS_FALSE 2
  6. #define IS_TRUE 3
  7. #define IS_LONG 4
  8. #define IS_DOUBLE 5
  9. #define IS_STRING 6
  10. #define IS_ARRAY 7
  11. #define IS_OBJECT 8
  12. #define IS_RESOURCE 9
  13. #define IS_REFERENCE 10
  14. /* constant expressions */
  15. #define IS_CONSTANT 11
  16. #define IS_CONSTANT_AST 12
  17. /* fake types */
  18. #define _IS_BOOL 13
  19. #define IS_CALLABLE 14
  20. /* internal types */
  21. #define IS_INDIRECT 15
  22. #define IS_PTR 17

通过 gdb 打断点可以看到底层的定义:

  1. 其中, u1 = { v= {type = 6} },查看 u1 type_info 的定义
  2. #define IS_STRING 6
  3. 6 string 类型,于是寻找 value str 是一个 zend_string 类型的指针地址,存储了字符串信息

以 zend_string 为例

  1. /*
  2. gc 用作引用计数
  3. h 哈希值,存储字符串对应的哈希值,方便在操作的时候,直接读取
  4. len 存储字符串长度
  5. val 柔性数组,存储字符串值的地址
  6. */
  7. struct _zend_string{
  8. zend_refcounted_h gc;
  9. zend_ulong h;
  10. size_t len;
  11. char val[1];
  12. }

字符串的赋值与写时分离

zend_string中有refcount用来做计算。当字符串被引用的时候,则用zend_reference 来使在字符串内容不变的情况下,只保存一份zend_string。

当 $a 值改变的时候,引用 -1 ,由引用类型变为新的 zend_string

  1. $a = "string"; // $a 是 type 为 6的 zend_string 类型
  2. $b = &$a; // $a,$b 都变成了 type=10 的引用类型,见上图,refcount=2
  3. $b = "hello"; // 引用中的 zval.value.str 变了,$a $b 都变
  4. unset($b); // 1. $b的type变为1(null)2. 引用类型的 refcount=1
  5. echo $a; // hello

数组

  1. struct _zend_array {
  2. zend_refcounted_h gc;
  3. union {
  4. struct {
  5. ZEND_ENDIAN_LOHI_4(
  6. zend_uchar flags,
  7. zend_uchar _unused,
  8. zend_uchar nIteratorsCount,
  9. zend_uchar _unused2)
  10. } v;
  11. uint32_t flags;
  12. } u;
  13. uint32_t nTableMask;
  14. Bucket *arData;
  15. uint32_t nNumUsed;
  16. uint32_t nNumOfElements;
  17. uint32_t nTableSize;
  18. uint32_t nInternalPointer;
  19. zend_long nNextFreeElement;
  20. dtor_func_t pDestructor;
  21. };
    - gc: 垃圾回收相关,记录了引用计数等信息.

    - arData: 散列表中保存着存储元素(Bucket)的数组,其内存是连续的,arData指向数组的起始位置

    - nTableSize: 数组占用的长度,2的n次方(最小为8).

    - nTableMask :这个值在散列函数根据key的hash code映射元素的存储位置时用到,它的值实际就是nTableSize的负数,用位运算表示的话则为nTableMask=~nTableSize+1.

    - nNumUsed,NNumOfElements:这两个成员的含义看起来非常相似,但是他们代表的含义是不同的,nNumUsed是指数组当前是用的Bucket数,但是这些Bucket并不都是数组的有效元素,比如当我们unset一个数组元素时并不会马上将其从数组中移除,而只是将这个元素的类型标为IS_UNDEF;nNumOfElements则是数组中有效元素的数量,所以nNumOfElements<=nNumUsed

    - nNextFreeElement:当增加一个没有键值的元素时,如$arr[]=‘abc’,nNextFreeElement就是该元素的键值,从0开始,每次这样赋值时就自增1

相比PHP5时代的Hashtable,zend_array的内存占用从PHP5点72个字节,降低到了56个字节,想想一个PHP生命进程中成千上万的数组,内存降低明显。

一个简单的散列表可以用取模的方式,如散列表的大小是8,则初始化分配8个元素大小的空间,将 key 的 hash code 与 8 取模,作为数组的下标。但是这样生成的散列表是无序的,php 的数组是有序的,如何实现的呢?

映射表也是一个数组,大小与散列表相同,将 key 的 hash code 与 8 取模,将键值有序的存入 bucket 中,bucket 所在下标存入 中间表[取模后的值]

数组的结构中并没有发现这个中间表的定义,因为它和 bucket 一起分配了一块连续的内存,通过 arData 向前即可访问到

内存

内存池定义了三种粒度的内存块:chunk,page,slot,每个 chunk 大小为 2M,page 大小为 4K,一个 chunk 被分割为 512 个 page ,一个 page 被切割为多个 slot,申请内存时按照不同的申请大小决定具体的分配策略。

php 运行生命周期

php 整个生命周期分为以下几个阶段:模块初始化,请求初始化,执行脚本,请求关闭,模块关闭。根据不同的 SAPI,各阶段会有差异。如 cli 模式,每次请求都会完成经历这些阶段,而 fastCgi 模式,只会在启动时执行一次模块初始化,在请求时会经历请求初始化,执行脚本,请求关闭。

php_module_startup 阶段的操作:

php_request_startup 阶段的操作:

fpm

cli 模式的每次请求会完整的进行5各阶段,fpm模式只在初始化时完成php_module_startup 阶段,再每次请求只执行 请求初始化,执行脚本,请求关闭,三个阶段,最后再执行模块关闭。

swoole

Swoole 使用 C/C++ 语言编写,提供了 PHP 语言的异步多线程服务器、异步 TCP/UDP 网络客户端、异步 MySQL、异步 Redis、数据库连接池、AsyncTask、消息队列、毫秒定时器、异步文件读写、异步DNS查询。

协程基本原理

  1. go(function(){
  2. A...
  3. B...
  4. C...
  5. });
  6. 每一个协程块 go,都会创建一个独立的栈空间,当A依赖外部结果,就会暂存整个栈,跳出,执行其他栈

魔术方法

超全局变量

$_POST, $_GET, $_SERVER, $_FILES, $_SESSION, $_COOKIE, $GLOBALS

$_SERVER

  1. $_SERVER['SERVER_ADDR'] # 服务器的 IP 地址
  2. $_SERVER['SERVER_NAME'] # 服务器名称
  3. $_SERVER['REQUEST_TIME'] # 请求时间
  4. $_SERVER['HTTP_REFERER'] # 连接到当前页面的前一页面的 URL 地址,可能为空
  5. $_SERVER['HTTP_USER_AGENT'] # User_Agent 头部的内容
  6. $_SERVER['REQUEST_URI'] #访问此页面所需的 URI
  7. $_SERVER['PATH_INFO']
  8. $_SERVER['QUERY_STRING']
  9. $_SERVER['REMOTE_ADDR'] # 用户的 IP 地址
  1. http://www.test.com/index.php/foo/bar.html?c=index&m=search
  2. 我们可以得到 $_SERVER['PATH_INFO'] ‘/foo/bar.html’,而此时 $_SERVER['QUERY_STRING'] = 'c=index&m=search';

预定义常量

设当前文件路径为: /Users/zy/Study/数据结构/test.php

  1. __FILE__ # /Users/zy/Study/数据结构/test.php
  2. __LINE__ # 此行代码的行数
  3. __DIR__ # /Users/zy/Study/数据结构

运算符优先级

递增 ++ 递减-- > 算术运算符 + - x ➗ > 比较 > < > == != > 引用 & > 位运算符 > 逻辑与 && 逻辑或 || > 三目 > 赋值 > and or

递增/递减 不影响 bool 值, true++ 还是 true,fals++ 还是 false
NULL 递减不变,递增变1
echo true 会进行类型转换,echo true; // 1

static 静态变量

  1. $count = 5;
  2. function test(){
  3. static $count;
  4. return $count++;
  5. }
  6. echo $count; # 5
  7. ++$count;
  8. echo test(); # NULL
  9. echo test(); # 1

include 与 require

include 加载一个不存在的文件会报警告,脚本继续运行,require 会报致命错误,并终止运行。

正则表达式

  1. preg_match(); // //在字符串中查找匹配项,返回一个数组。
  2. preg_replace(); // 替换匹配到的内容

匹配以139开头的手机号

  1. $p = '/^139\d{8}$/';
  2. preg_match($p, $str, $match);
  3. print_r($match);
  4. Array
  5. (
  6. [0] => 13900000000
  7. )

文件操作

遍历文件

  1. <?php
  2. $file="../../php";
  3. function list_file($date){
  4. $temp=scandir($date);
  5. //遍历文件夹
  6. foreach($temp as $v){
  7. $a=$date.'/'.$v;
  8. if(is_dir($a)){
  9. if($v=='.' || $v=='..'){ //系统隐藏的文件,防止无限循环
  10. continue;
  11. }
  12. echo "-- $a --\n"; // 输出文件夹
  13. list_file($a);
  14. }else{
  15. echo $a."\n";
  16. }
  17. }
  18. }
  19. list_file($file);

数组函数

  1. # 数组操作
  2. array_values($arr);
  3. array_keys($arr);
  4. in_array("a",$arr);
  5. array_key_exists("a",$arr);
  6. array_flip($arr); // 值与键名互换(重复值后者覆盖前者)
  7. array_reverse($arr,TRUE); // 元素顺序置反,第二个参数为TRUE则保留键名
  8. array_search("a",$arr); // 在数组中检索a,如果存在返回键名
  9. array_merge($a, $b); // 合并两数组
  10. array_sum($arr); // 对所有元素求和
  11. array_rand($arr,2); // 从数组中随机取出一个或多个元素
  12. array_combine($a,$b) // 创建一个数组,用 $a 作为键,用 $b 作为值
  13. array_count_values($arr) // 统计数组中所有值出现的次数
  14. array_unique($arr); // 移除重复的值,保留键名
  15. array_chunk($arr,3,TRUE); // 将一个数组分割成3个,TRUE为保留原数组的键名
  16. array_pad($arr,5,'x'); // 用 x 填充数组到5个元素
  17. count($arr) // 数组的元素个数
  18. # 数据结构
  19. array_push($arr,"a","b"); // 末尾入队
  20. array_pop($arr); // 末尾出队
  21. array_unshift($arr,"a"); // 头部入队
  22. array_shift($arr); // 头部出队
  23. # 排序
  24. sort($arr); // 小->大,对值排序,删除键
  25. rsort($arr); // 大->小,对值排序,删除键
  26. usort($arr,"function"); // 自定义排序,对值排序,删除键(0相等,正数 1>2,负数1<2)
  27. asort($arr); // 小->大,对值排序,保留键
  28. arsort($arr); // 大->小,对值排序,保留键
  29. uasort($arr,"function"); // 自定义排序,对值排序,保留键
  30. ksort($arr); // 小->大,对键排序
  31. krsort($arr); // 大->小,对键排序
  32. array_multisort($a,$b) // 先排序 $a,其他数组随 $a 排序
  33. # 回调函数
  34. array_walk($arr,'myfunction'); // myfunction($value,$key){...}
  35. array_map("myfunction",$arr1,$arr2); // myfunction($v1,$v2){...} 自定义返回值,成为新数组的元素
  36. array_filter($arr,"function"); // 过滤每个元素,return true 保留元素,并保留键
  37. # 集合
  38. array_diff($a,$b) // 差集结果数组(不比较键名)
  39. array_diff_assoc($a,$b) // 差集结果数组(键名也比较)
  40. array_intersect($a,$b); // 交集结果数组(不比较键名)
  41. array_intersect_assoc($a,$b); // 交集结果数组(键名也比较)
  42. # 其他
  43. range(0,12); // 创建一个包含指定范围单元的数组
  44. shuffle($arr); // 将数组的顺序打乱

字符串函数

  1. # 长度
  2. strlen($str); // 字符串长度
  3. # 查找
  4. strpos($str, $char[, $offset]); // $char 在字符串中第一次出现的位置。$offset可选,偏移量
  5. stripos($str, $char[, $offset]); // 同上,忽略大小写
  6. strrpos($str, $char[, $offset]); // $char 在字符串中最后一次出现的位置
  7. strripos($str, $char[, $offset]) // 同上,忽略大小写
  8. strstr|strchr($str, $search_str); // 返回搜索字符串之后的字符串,包含自身 strchr("abcd", "bc") 得到 bcd
  9. stristr($str, $search_str); // 同上,忽略大小写
  10. strrchr($str, $search_str); // 同上,从后向前找
  11. str_replace($search, $replace, $str); // 在 $str 中找到 $search,用 $replace 来替换
  12. str_ireplace($search, $replace, $str) // 同上,忽略大小写
  13. # 截取
  14. substr($str, $start[, $length]); // 截取字符串,从第 $start 位置开始,截取 $length 个字节
  15. mb_substr($str, $start[, $length]); // 同上,截取字符(适合处理 utf8)
  16. substr_replace($str, $replace, $start[, $length]); // 替换字符串的子串
  17. # 字符串大小写
  18. strtolower($str); // 小写
  19. strtoupper($str); // 大写
  20. ucwords($str); // 所有单词的首字母大写
  21. ucfirst($str); // 第一个首字母大写
  22. lcfirst($str); // 第一个首字母小写
  23. # 过滤
  24. trim($str[, $charlist]); // 默认过滤两端的空格,也可以过滤指定字符串
  25. ltrim($str[, $charlist]); // 同上,过滤最左
  26. rtrim($str[, $charlist]); // 同上,过滤最右
  27. strip_tags($str[, $allowTag]); // 过滤字符串中的 html 标签
  28. addslashes($str); // 使用反斜线引用字符串中的特殊字符
  29. htmlspecialchars($string[, $flag=ENT_COMPAT]); //将字符串中的特殊字符转换成HTML实体
  30. nl2br($str); // 将字符串中的\n用<br/>替换
  31. # 拆分/合并
  32. explode($delimiter, $str); // 按 $delimiter 分割成数组
  33. implode|join($delimiter, $array); // 数组转字符串,用 $delimiter 分割
  34. # 比较
  35. strcmp($a, $b); // 比较两个字符串的大小,1 a>b,0 a=b,-1 a<b
  36. strcasecmp($a, $b); // 同上,忽略大小
  37. strnatcasecmp($a, $b); // 使用自然顺序算法比较字符串
  38. # 其他
  39. strrev($str); // 反转字符串
  40. str_shuffle($str); // 随机打乱字符串

计算机网络

OSI分层(7层)

应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

Tcp 连接

短链接的优缺点:
缺点:多余传输,慢启动问题,握手阶段丢包稳定性较差

优点:简单,理论上连接数会少,无状态对负载均衡会更友好

HTTP

状态码 英文 含义 相关头信息
200 ok 成功
204 No Content 请求处理成功了,但没有资源返回
206 Partial Content 客户端进行了范围请求,服务端成功返回 Content-Range
301 Moved Permanently 永久性重定向,应按照 Location 头信息重新保存 Location
302 Found 临时性重定向
303 See Other 提示应使用GET方式获取资源
304 Not Modified 资源已找到,但未符合条件 If-Match, If-Modified, If-None-Match, If-Range, If-Unmodified-Since
400 Bad Request 请求报文中有语法错误
401 Unauthorized 认证失败
403 Forbidden 服务器拒绝返回请求资源
404 Not Found 服务器无法找到请求的资源
500 Internal Server Error 服务器错误
503 Service Unavailable 服务器处于超负载或停机维护

通用首部字段

字段名 说明
Cache-Control 控制缓存的行为
Connection 逐跳首部,连接的管理
Date 创建报文的日期时间
- Cache-Control: 
    no-cache     // 客户端将不会接收缓存过的响应,直接从源服务器获取
    no-store     // 不进行缓存
    max-age=604800  // (单位:秒) 一周内返回缓存数据,HTTP/1.1版本在遇到Expires时,会优先max-age,而忽略掉Expires,HTTP/1.0则相反

- Connection
    请求时:不再转发给代理的首部字段名
    响应时:连接方式,如 keep-Alive 持久连接(HTTP/1.1默认持久连接,HTTP/1.0需要指定)

请求首部字段

字段名 说明
Accept 客户端可处理的媒体类型
Accept-Charset 优先的字符集
Accept-Encoding 优先的内容编码
Accept-Language 优先的自然语言
Authorization Web认证信息
Host 请求资源所在的服务器
If-Match 比较实体标记(Etag)
If-None-Match 比较实体标记(与If-Match相反)
If-Modified-Since 比较资源的更新时间
If-Unmodified-Since 比较资源的更新时间(与If-Modified-Since相反)
If-Range 资源未更新时发送实体 Byte 的请求范围
Range 实体的字节范围请求
Referer URI的原始获取方
User-Agent 客户端信息
Cookie Cookie信息
- Accept: text/html,application/xml
- Accept-Encoding: gzip
- If-Match:"12345"      // 与实体的 Etag值一致服务器才会接受请求
- If-Modified-Since: Sun,29 Aug 2020 ...    // 资源在 2020.4.29 后更新过才会接受请求
- If-None-Match:"123"   // 实体的 Etag值不一致才会接受请求


- If-Range:"1234"
  Range: bytes=5001-10000       // 如果 Etag 或时间与 If-Range 匹配,则返回 5001-10000 字节内容。否则全部返回。

响应首部字段

字段名 说明
Accept-Ranges 是否接受字节范围请求
Age 推算资源创建经过时间
Etag 资源的匹配信息
Location 令客户端重定向的URI
Server 服务器信息
Set-Cookie Cookie信息

实体首部字段

字段名 说明
Allow 资源可支持的Http方法
Content-Encoding 实体主体适用的编码方式
Content-Langusge 实体主体适用的自然语言
Content-Length 实体主体大小(字节)
Content-Type 实体主体的媒体类型
Content-Range 实体主体的位置范围
Expires 实体主体过期的日期时间
Last-Modified 资源最后修改日期

spl

  1. $queue = new SplQueue();
  2. $queue->enqueue(1);
  3. $queue->enqueue(1);
  4. $queue->dequeue();
  5. $queue->dequeue();
  1. $q = new SplStack();
  2. $q[] = 1;
  3. $q[] = 2;
  4. $q[] = 3;
  5. $q->push(4);
  6. $q->add(4,5);
  7. $q->pop();
  1. $array = new SplFixedArray(5);
  2. $array[1] = 2;
  3. $array[4] = "foo";
  4. var_dump($array[0]); // NULL
  5. var_dump($array[1]); // int(2)

面向对象

单例模式

四私一公

  1. <?php
  2. class Test{
  3. // 注意 instanceof 的用法
  4. public static $instance;
  5. public static function getInstance(){
  6. if( !self::$instance instanceof self ){
  7. self::$instance = new self();
  8. }
  9. return self::$instance;
  10. }
  11. // 将构造函数私有化
  12. private function __construct(){
  13. }
  14. // 防止用克隆复制对象
  15. private function __clone(){
  16. }
  17. // 防止用序列化复制对象
  18. private function __wakeup(){
  19. }
  20. }
  21. $a = Test::getInstance();
  22. $b = Test::getInstance();
  23. //$c = clone $a;
  24. //$d = unserialize(serialize($a));
  25. var_dump($a, $b);

工厂模式

- 简单工厂模式

以支付方式举例,支付方式分为,微信、支付宝、银联等

如果不使用工厂模式,可能是维护两个类: Wechat 、AliPay,用到哪个 new 哪个,缺点是如果更改了类名,需要全局搜索修改,新接手项目的人也不能一目了然的知道都实现了哪些支付方式。

使用工厂模式:

  1. // 1. 所有的支付类,都共同实现支付接口,如都有 pay() 方法
  2. // 2. 使用 switch 来从支付工厂选择方式
  3. # 接口类
  4. interface PaySimpleFactory{
  5. public function pay();
  6. }
  7. # 工厂类
  8. class SimpleFactory{
  9. public function get($operate){ // 静态工厂类,用 public static function get()
  10. switch ($operate){
  11. case "WeChatPay":
  12. $res = new WeChatPay();
  13. break;
  14. case "AliPay":
  15. $res = new AliPay();
  16. break;
  17. default:
  18. throw new xxxException("暂不支持的支付方式");
  19. }
  20. return $res;
  21. }
  22. }
  23. # 简单工厂
  24. $factory = new PayFactory();
  25. $zhifubao = $factory->get("Alipay");
  26. $zhifubao->pay();
  27. #静态工厂
  28. $zhifubao = PayFactory::get("Alipay");
  29. $zhifubao->pay();

优缺点:

    帮助封装
    解耦
    但违反了开放封闭原则(对扩展开放,对修改关闭),因为每增加一个方式,就要修改 switch

工厂方法模式

工厂方法模式解决了违反了开放封闭原则的不足,但是每增加一个方式,就多一个工厂函数,比较鸡肋。

抽象工厂模式

抽象工厂就是把简单工厂的 switch 抽象成了接口->类

  1. # 工厂接口
  2. interface Factory{
  3. public function WeChatPay();
  4. public function AliPay();
  5. }
  6. # 工厂类
  7. class PayFactory{
  8. public function WeChatPay(){
  9. return new WeChatPay();
  10. }
  11. public function AliPay(){
  12. return new AliPay();
  13. }
  14. }

缺点:不仅没有解决简单工厂的开闭原则问题,还更复杂了。
完美解决方式:利用反射来优化抽象工厂

  1. # 工厂接口
  2. interface Factory{
  3. public function getPay();
  4. }
  5. # 工厂类
  6. class PayFactory{
  7. public function getPay($type){
  8. $className = __NAMESPACE__."\\".$type;
  9. try{
  10. $class = new ReflectionClass($className);
  11. $instance = class->newInstanceArgs();
  12. }cache(...){
  13. ...
  14. }
  15. return $instance;
  16. }
  17. }

注册树/注册器 模式

  1. <?php
  2. class Test{
  3. public static $Objects;
  4. public static function set($alias, $obj){
  5. self::$Objects[$alias] = $obj;
  6. }
  7. public static function get($alias){
  8. return self::$Objects[$alias] ?? false;
  9. }
  10. public static function _unset($alias){
  11. unset(self::$Objects[$alias]);
  12. }
  13. }
  14. $t = new Test;
  15. $t::set("a", "123");
  16. $res = $t::get("a");
  17. $res2 = $t::get("a");
  18. var_dump($res, $res2);

注册树模式和单例模式很相似,在生成了/设置了对象后,获取的都是同一个对象。但区别就是在未设置对象时,单例模式是创建对象并返回,而注册树返回 false。

单例模式解决的是创建对象唯一实例的问题,工厂模式解决的是如何不通过 new 创建实例,注册树就是方便调用已有对象的

建造者模式

无需关心是如何实现的,只需要获取结果。

  1. # 建造汽车接口
  2. interface Builder
  3. {
  4. // 建造汽车各部分的实现
  5. public function buildPartA();
  6. public function buildPartB();
  7. public function buildPartC();
  8. public function buildOthers();
  9. /**
  10. * 返回最后组装成品结果 (返回最后装配好的汽车),
  11. * 成品的组装过程不在这里进行,而是转移到下面的Director类中进行
  12. * 从而实现了解耦过程和部件。
  13. * return Product
  14. */
  15. public function getCar();
  16. }
  17. # 实现类
  18. class Car implements Builder
  19. {
  20. /**
  21. * @var array
  22. */
  23. protected $_parts = [];
  24. public function buildPartA()
  25. {
  26. $this->_parts[] = "发动机、";
  27. return $this;
  28. }
  29. public function buildPartB()
  30. {
  31. $this->_parts[] = "底盘、";
  32. return $this;
  33. }
  34. public function buildPartC()
  35. {
  36. $this->_parts[] = "变速箱、";
  37. return $this;
  38. }
  39. public function buildOthers()
  40. {
  41. $this->_parts[] ="其他零件";
  42. return $this;
  43. }
  44. public function getCar()
  45. {
  46. $str = "这辆车由:";
  47. foreach ($this->_parts as $item) {
  48. $str .= $item;
  49. }
  50. $str .= "组成\n";
  51. echo $str;
  52. }
  53. }
  54. # 拼接类
  55. class Director
  56. {
  57. private $builder;
  58. public function __construct(Builder $builder)
  59. {
  60. $this->builder = $builder;
  61. }
  62. public function build()
  63. {
  64. $this->builder->buildPartA();
  65. $this->builder->buildPartB();
  66. $this->builder->buildPartC();
  67. $this->builder->buildOthers();
  68. return $this->builder->getCar();
  69. }
  70. }
  71. $car = new Car();
  72. $director = new Director($car);

应用场景:
- 可能存在几种类型的对象
- 构造函数具有很多参数

原型模式

用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。

  1. class Test{
  2. public $type;
  3. public function __construct()
  4. {
  5. echo "构造函数进行中\n";
  6. $this->type = 1;
  7. }
  8. public function get(){
  9. echo "type:".$this->type;
  10. }
  11. }
  12. $t = new Test();
  13. echo "----\n";
  14. $t2 = clone $t;
  15. $t2->get();
  16. # 结果
  17. 构造函数进行中
  18. ----
  19. type:1

适配器模式

适配器又叫包装器,比如三孔插座与二孔插座的转换。原类已经不支持新功能,但又不能修改时,可以使用适配器,包一层再修改。

  1. # 不兼容的类
  2. class A
  3. {
  4. protected $driver = "mysql";
  5. public function set()
  6. {
  7. echo "use {$this->driver}.\n";
  8. }
  9. }
  10. # 接口
  11. interface DatabaseTarget
  12. {
  13. public function set();
  14. public function get();
  15. }
  16. # 适配器类
  17. class B extends A implements NewInterface
  18. {
  19. public function __construct()
  20. {
  21. $this->driver = "pdo";
  22. }
  23. public function get()
  24. {
  25. echo "used {$this->driver}.\n";
  26. }
  27. }

与类适配器不同的是,不需要继承原类,对其进行扩展,而是将原类的实例作为构造函数的参数注入。

  1. class B implements NewInterface
  2. {
  3. protected $adaptee;
  4. public function __construct(A $adaptee)
  5. {
  6. $this->adaptee = $adaptee;
  7. $adaptee->driver = "mysqli";
  8. }
  9. public function set(){
  10. }
  11. public function get(){
  12. }
  13. }

桥接模式

需求:现在要生产(输出)不同颜色的玩具,比如红色小马,黄色小马,红色星星,蓝色星星... 如果不使用桥接模式,可能有多少种玩具就要写多少个类,而使用桥接模式,就可以抽象出 颜色,形状,两种接口,再将实现了两种接口的类互相组合。

  1. # 颜色抽象类
  2. abstract class Color
  3. {
  4. abstract public function output();
  5. }
  6. # 形状抽象类
  7. abstract class Shape
  8. {
  9. protected $color;
  10. public function __construct(Color $color)
  11. {
  12. $this->color = $color;
  13. }
  14. abstract public function run();
  15. # 设定颜色的圆形
  16. class Circle extends Shape
  17. {
  18. public function run()
  19. {
  20. echo "{$this->color->output()} 的圆形星星\n";
  21. }
  22. }
  23. # 设定颜色
  24. class RedColor extends Color
  25. {
  26. public function output()
  27. {
  28. return "红色";
  29. }
  30. }
  31. # 合成红色圆形星星!
  32. $red = new RedColor();
  33. $redCircle = new Circle($red);
  34. $redCircle->run();

装饰器模式

茶店,顾客可以选一杯茶,然后加入奶/咖啡/...
火锅店,顾客可以要一个汤底,然后加入菜/肉/...

以点一杯奶茶为例:茶和奶对店来说有名称有价格就可以了,是一类,但核心是茶,是一切的基础。最终目的是价格相加来结算。

  1. # 饮料接口
  2. interface Drinks
  3. {
  4. public function name();
  5. public function price();
  6. }
  7. # 为配料定义抽象类
  8. abstract class Decorator implements Drinks
  9. {
  10. protected $drinks;
  11. public function __construct(Drinks $drinks)
  12. {
  13. $this->drinks = $drinks;
  14. }
  15. }
  16. # 定义茶(主角,被装饰者)
  17. class Tea implements Drinks
  18. {
  19. public function name()
  20. {
  21. return '茶';
  22. }
  23. public function price()
  24. {
  25. return 10;
  26. }
  27. }
  28. # 定义奶(配角,装饰者)
  29. class Milk extends Decorator
  30. {
  31. public function name()
  32. {
  33. return '加奶的'.$this->drinks->name();
  34. }
  35. public function price()
  36. {
  37. return $this->drinks->price() + 8;
  38. }
  39. }
  40. # 生成奶茶
  41. $tea = new Tea();
  42. $milkTea = new Milk($tea);
  43. echo $milkTea->name();
  44. echo $milkTea->price()."元\n";

组合模式

将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。组合模式又称为部分整体模式,该模式将对象组合成树形结构以表示"部分-整体"的层次结构。

组合模式主要应用于树形关系,比如公司职位,文件夹与目录的层级关系等。下面以目录为例

透明模式,将所有方法,不管不同级别节点用不用,都用接口约束。因为目录与文件的不同在于,目录可以扩展,文件不可以,是最小节点,所以把“添加下层” 用接口约束,会使文件类无法使用此方法。

  1. # 接口
  2. abstract class Component
  3. {
  4. protected $name;
  5. public function __construct($name)
  6. {
  7. $this->name = $name;
  8. }
  9. abstract public function add(Component $component);
  10. abstract public function getName();
  11. }
  12. # 目录类
  13. class Dir extends Component
  14. {
  15. protected $children = [];
  16. public function add(Component $component)
  17. {
  18. $this->children[] = $component;
  19. }
  20. public function getName()
  21. {
  22. $nameStr = $this->name ."\n";
  23. foreach ($this->children as $k => $v) {
  24. $nameStr .= "--" . $v->getName();
  25. }
  26. return $nameStr;
  27. }
  28. }
  29. # 文件类
  30. class File extends Component
  31. {
  32. public function add(Component $component)
  33. {
  34. throw new \Exception("文件不能添加子节点");
  35. }
  36. public function getName()
  37. {
  38. return "--" . $this->name . "\n";
  39. }
  40. }
  41. # 输出整体结构
  42. $composite = new Dir("Composite");
  43. $transparent = new Dir("Transparent");
  44. $composite->add($transparent);
  45. $componentFile = new File("Component.php");
  46. $dirFile = new File("Dir.php");
  47. $transparent->add($componentFile);
  48. $transparent->add($dirFile);
  49. echo $composite->getName();

安全模式只在接口约束共有方法。各种类的独有方法自己按需实现

  1. abstract class Component
  2. {
  3. protected $name;
  4. public function __construct($name)
  5. {
  6. $this->name = $name;
  7. }
  8. abstract public function getName();
  9. }

安全模式的叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则

外观模式

1、设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式;
2、开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口;
3、维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。

  1. # 当业户逻辑复杂,客户端需要这样使用时...
  2. $a = new A();
  3. $a->a();
  4. $a->b();
  5. $b = new B();
  6. $b->a($a);
  7. ...
  8. # 就可以使用‘家丑不外扬’,封装一层,表面看起来光鲜亮丽
  9. class Test{
  10. public function test(){
  11. $a = new A();
  12. $a->a();
  13. $a->b();
  14. $b = new B();
  15. $b->a($a);
  16. }
  17. }
  18. $t = new Test();
  19. $t->test();

代理模式

一个类获取另一个类的功能,或进行一些扩展。如在原类上包一层代理类,来进行缓存优化、日志记录等功能。

  1. class Proxy
  2. {
  3. protected $realSubject;
  4. public function __construct()
  5. {
  6. $this->realSubject = new RealSubject(); # 被代理类
  7. }
  8. public function __call($name, $arguments) # 精髓
  9. {
  10. return $this->realSubject->{$name}(...$arguments);
  11. }
  12. public function send()
  13. {
  14. echo __CLASS__ . " send\n";
  15. }
  16. }

模板方法模式

定义好了各方法的调用顺序,且不可修改,子类实现各块功能

  1. # 抽象接口
  2. abstract class AbstractClass
  3. {
  4. final public function templateMethod() # final 子类不可重写
  5. {
  6. $this->open();
  7. $this->startPlay();
  8. $this->endPlay();
  9. }
  10. protected function open()
  11. {
  12. echo "open game\n";
  13. }
  14. abstract protected function startPlay(); # 子类去实现功能
  15. protected function endPlay()
  16. {
  17. echo "end game\n";
  18. }
  19. }

状态模式

在状态模式中,类的行为是基于它的状态改变的。比如qq状态,在活跃状态的时候,图标是绿色的,在离线状态的时候,图标是灰色的,就可以用状态模式。

  1. # 中间人角色(状态改变了告诉它,获取状态也找它)
  2. class Context {
  3. private $state;
  4. public function setState(State $state){
  5. this.state = $state;
  6. }
  7. public function getState(){
  8. return state;
  9. }
  10. }
  11. # 状态接口,定义状态,和它的行为
  12. abstract class State {
  13. public function setContext($context) {
  14. $context->setState(this);
  15. }
  16. public function showColor();
  17. }
  18. # 定义状态
  19. class activeState implements State {
  20. public function showColor(){
  21. return "green";
  22. }
  23. }
  24. class outState implements State {
  25. public function showColor(){
  26. return "grew";
  27. }
  28. }
  29. # 切换状态
  30. $act = new activeState();
  31. $context = new Context();
  32. $act->setContext($context);
  33. echo $context->getState()->showColor();
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注