@octopus
2020-10-19T00:39:52.000000Z
字数 19939
阅读 1482
php 面试
# $a <=> $b, 当 $a 小于,等于,大于 $b 时分别返回 -1,0,1echo 1 <=> 1; // 0
declare(strict=1); // 开启严格模式function (int $a){...}
$page = isset($_GET["page"]) ? $_GET["page"] : 1; // 旧$page = $_GET["page"] ?? 1; // php7
define("Animal", ['dog','cat']);
use Space\{ClassA, ClassB, ClassC as C}
# 老版本是不能捕获下面的错误,php7 可以捕获错误try{undefindfunc();}cache(Error $e){var_dump($e);}# php7中全局异常回调函数也可以捕获错误set_exception_handler(function($e){var_dump($e);});
改变函数作用域
class Test(){private $num = 1;}$f = function(){return $this->num + 1;};echo $f->call(new Test); // 2
# 返回值为第一个参数除于第二个参数的值并取整intdiv(10,3); // 3
# 老版本用 list$arr = [1,2,3];list($a, $b, $c) = $arr# php7$arr = [1,2,3];[$a, $b, $c] = $arr

struct _zval_struct{zend_value value;union u1;union u2;}# 详细看三部分struct _zval_struct {union {zend_long lval; // 整型double dval; // 浮点型zend_refcounted *counted;zend_string *str; // 字符串zend_array *arr; // 数组zend_object *obj; // 对象zend_resource *res; // 资源zend_reference *ref; // 引用zend_ast_ref *ast;zval *zv;void *ptr;zend_class_entry *ce;zend_function *func;struct {uint32_t w1;uint32_t w2;} ww;} value;union {struct {ZEND_ENDIAN_LOHI_4(zend_uchar type, /* active type */zend_uchar type_flags,zend_uchar const_flags,zend_uchar reserved) /* call info for EX(This) */} v;uint32_t type_info;} u1;union {uint32_t var_flags;uint32_t next; /* hash collision chain */uint32_t cache_slot; /* literal cache slot */uint32_t lineno; /* line number (for ast nodes) */uint32_t num_args; /* arguments number for EX(This) */uint32_t fe_pos; /* foreach position */uint32_t fe_iter_idx; /* foreach iterator index */} u2;};
其中value部分, 是一个size_t大小(一个指针大小), 可以保存一个指针, 或者一个long, 或者一个double。
而type info部分则保存了这个zval的类型
# u1 type_info/* regular data types */#define IS_UNDEF 0#define IS_NULL 1#define IS_FALSE 2#define IS_TRUE 3#define IS_LONG 4#define IS_DOUBLE 5#define IS_STRING 6#define IS_ARRAY 7#define IS_OBJECT 8#define IS_RESOURCE 9#define IS_REFERENCE 10/* constant expressions */#define IS_CONSTANT 11#define IS_CONSTANT_AST 12/* fake types */#define _IS_BOOL 13#define IS_CALLABLE 14/* internal types */#define IS_INDIRECT 15#define IS_PTR 17
通过 gdb 打断点可以看到底层的定义:

其中, u1 = { v= {type = 6} },查看 u1 type_info 的定义#define IS_STRING 66 是 string 类型,于是寻找 value 的 str 是一个 zend_string 类型的指针地址,存储了字符串信息
以 zend_string 为例
/*gc 用作引用计数h 哈希值,存储字符串对应的哈希值,方便在操作的时候,直接读取len 存储字符串长度val 柔性数组,存储字符串值的地址*/struct _zend_string{zend_refcounted_h gc;zend_ulong h;size_t len;char val[1];}

zend_string中有refcount用来做计算。当字符串被引用的时候,则用zend_reference 来使在字符串内容不变的情况下,只保存一份zend_string。
当 $a 值改变的时候,引用 -1 ,由引用类型变为新的 zend_string
$a = "string"; // $a 是 type 为 6的 zend_string 类型$b = &$a; // $a,$b 都变成了 type=10 的引用类型,见上图,refcount=2$b = "hello"; // 引用中的 zval.value.str 变了,$a $b 都变unset($b); // 1. $b的type变为1(null)2. 引用类型的 refcount=1echo $a; // hello
struct _zend_array {zend_refcounted_h gc;union {struct {ZEND_ENDIAN_LOHI_4(zend_uchar flags,zend_uchar _unused,zend_uchar nIteratorsCount,zend_uchar _unused2)} v;uint32_t flags;} u;uint32_t nTableMask;Bucket *arData;uint32_t nNumUsed;uint32_t nNumOfElements;uint32_t nTableSize;uint32_t nInternalPointer;zend_long nNextFreeElement;dtor_func_t pDestructor;};
- 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 整个生命周期分为以下几个阶段:模块初始化,请求初始化,执行脚本,请求关闭,模块关闭。根据不同的 SAPI,各阶段会有差异。如 cli 模式,每次请求都会完成经历这些阶段,而 fastCgi 模式,只会在启动时执行一次模块初始化,在请求时会经历请求初始化,执行脚本,请求关闭。

php_module_startup 阶段的操作:
php_request_startup 阶段的操作:
激活zend引擎 zend_activate()
激活sapi
cli 模式的每次请求会完整的进行5各阶段,fpm模式只在初始化时完成php_module_startup 阶段,再每次请求只执行 请求初始化,执行脚本,请求关闭,三个阶段,最后再执行模块关闭。
Swoole 使用 C/C++ 语言编写,提供了 PHP 语言的异步多线程服务器、异步 TCP/UDP 网络客户端、异步 MySQL、异步 Redis、数据库连接池、AsyncTask、消息队列、毫秒定时器、异步文件读写、异步DNS查询。
go(function(){A...B...C...});每一个协程块 go,都会创建一个独立的栈空间,当A依赖外部结果,就会暂存整个栈,跳出,执行其他栈
$_POST, $_GET, $_SERVER, $_FILES, $_SESSION, $_COOKIE, $GLOBALS
$_SERVER['SERVER_ADDR'] # 服务器的 IP 地址$_SERVER['SERVER_NAME'] # 服务器名称$_SERVER['REQUEST_TIME'] # 请求时间$_SERVER['HTTP_REFERER'] # 连接到当前页面的前一页面的 URL 地址,可能为空$_SERVER['HTTP_USER_AGENT'] # User_Agent 头部的内容$_SERVER['REQUEST_URI'] #访问此页面所需的 URI$_SERVER['PATH_INFO']$_SERVER['QUERY_STRING']$_SERVER['REMOTE_ADDR'] # 用户的 IP 地址
http://www.test.com/index.php/foo/bar.html?c=index&m=search我们可以得到 $_SERVER['PATH_INFO'] = ‘/foo/bar.html’,而此时 $_SERVER['QUERY_STRING'] = 'c=index&m=search';
设当前文件路径为: /Users/zy/Study/数据结构/test.php
__FILE__ # /Users/zy/Study/数据结构/test.php__LINE__ # 此行代码的行数__DIR__ # /Users/zy/Study/数据结构
递增 ++ 递减-- > 算术运算符 + - x ➗ > 比较 > < > == != > 引用 & > 位运算符 > 逻辑与 && 逻辑或 || > 三目 > 赋值 > and or
递增/递减 不影响 bool 值, true++ 还是 true,fals++ 还是 false
NULL 递减不变,递增变1
echo true 会进行类型转换,echo true; // 1
$count = 5;function test(){static $count;return $count++;}echo $count; # 5++$count;echo test(); # NULLecho test(); # 1
include 加载一个不存在的文件会报警告,脚本继续运行,require 会报致命错误,并终止运行。
preg_match(); // //在字符串中查找匹配项,返回一个数组。preg_replace(); // 替换匹配到的内容
匹配以139开头的手机号
$p = '/^139\d{8}$/';preg_match($p, $str, $match);print_r($match);Array([0] => 13900000000)
fopen ( $file, $mode )
打开一个文件,并指定打开模式
r 只读,文件指针指向开头
r+ 读写,文件指针指向开头
w 写入,清空文件,文件指针指向开头
w+ 读写,文件指针指向开头,文件不存在则创建
a 写入,文件指针指向末尾(追加写),文件不存在则创建
a+ 读写,文件指针指向末尾,文件不存在则创建
x 写入,文件指针指向开头,如果文件已存在则 warning,不存在则创建
x+ 读写
fread ( $handle, $size )
读取文件,接收文件句柄,读 size 大小的内容
<?php$file="../../php";function list_file($date){$temp=scandir($date);//遍历文件夹foreach($temp as $v){$a=$date.'/'.$v;if(is_dir($a)){if($v=='.' || $v=='..'){ //系统隐藏的文件,防止无限循环continue;}echo "-- $a --\n"; // 输出文件夹list_file($a);}else{echo $a."\n";}}}list_file($file);
# 数组操作array_values($arr);array_keys($arr);in_array("a",$arr);array_key_exists("a",$arr);array_flip($arr); // 值与键名互换(重复值后者覆盖前者)array_reverse($arr,TRUE); // 元素顺序置反,第二个参数为TRUE则保留键名array_search("a",$arr); // 在数组中检索a,如果存在返回键名array_merge($a, $b); // 合并两数组array_sum($arr); // 对所有元素求和array_rand($arr,2); // 从数组中随机取出一个或多个元素array_combine($a,$b) // 创建一个数组,用 $a 作为键,用 $b 作为值array_count_values($arr) // 统计数组中所有值出现的次数array_unique($arr); // 移除重复的值,保留键名array_chunk($arr,3,TRUE); // 将一个数组分割成3个,TRUE为保留原数组的键名array_pad($arr,5,'x'); // 用 x 填充数组到5个元素count($arr) // 数组的元素个数# 数据结构array_push($arr,"a","b"); // 末尾入队array_pop($arr); // 末尾出队array_unshift($arr,"a"); // 头部入队array_shift($arr); // 头部出队# 排序sort($arr); // 小->大,对值排序,删除键rsort($arr); // 大->小,对值排序,删除键usort($arr,"function"); // 自定义排序,对值排序,删除键(0相等,正数 1>2,负数1<2)asort($arr); // 小->大,对值排序,保留键arsort($arr); // 大->小,对值排序,保留键uasort($arr,"function"); // 自定义排序,对值排序,保留键ksort($arr); // 小->大,对键排序krsort($arr); // 大->小,对键排序array_multisort($a,$b) // 先排序 $a,其他数组随 $a 排序# 回调函数array_walk($arr,'myfunction'); // myfunction($value,$key){...}array_map("myfunction",$arr1,$arr2); // myfunction($v1,$v2){...} 自定义返回值,成为新数组的元素array_filter($arr,"function"); // 过滤每个元素,return true 保留元素,并保留键# 集合array_diff($a,$b) // 差集结果数组(不比较键名)array_diff_assoc($a,$b) // 差集结果数组(键名也比较)array_intersect($a,$b); // 交集结果数组(不比较键名)array_intersect_assoc($a,$b); // 交集结果数组(键名也比较)# 其他range(0,12); // 创建一个包含指定范围单元的数组shuffle($arr); // 将数组的顺序打乱
# 长度strlen($str); // 字符串长度# 查找strpos($str, $char[, $offset]); // $char 在字符串中第一次出现的位置。$offset可选,偏移量stripos($str, $char[, $offset]); // 同上,忽略大小写strrpos($str, $char[, $offset]); // $char 在字符串中最后一次出现的位置strripos($str, $char[, $offset]) // 同上,忽略大小写strstr|strchr($str, $search_str); // 返回搜索字符串之后的字符串,包含自身 strchr("abcd", "bc") 得到 bcdstristr($str, $search_str); // 同上,忽略大小写strrchr($str, $search_str); // 同上,从后向前找str_replace($search, $replace, $str); // 在 $str 中找到 $search,用 $replace 来替换str_ireplace($search, $replace, $str) // 同上,忽略大小写# 截取substr($str, $start[, $length]); // 截取字符串,从第 $start 位置开始,截取 $length 个字节mb_substr($str, $start[, $length]); // 同上,截取字符(适合处理 utf8)substr_replace($str, $replace, $start[, $length]); // 替换字符串的子串# 字符串大小写strtolower($str); // 小写strtoupper($str); // 大写ucwords($str); // 所有单词的首字母大写ucfirst($str); // 第一个首字母大写lcfirst($str); // 第一个首字母小写# 过滤trim($str[, $charlist]); // 默认过滤两端的空格,也可以过滤指定字符串ltrim($str[, $charlist]); // 同上,过滤最左rtrim($str[, $charlist]); // 同上,过滤最右strip_tags($str[, $allowTag]); // 过滤字符串中的 html 标签addslashes($str); // 使用反斜线引用字符串中的特殊字符htmlspecialchars($string[, $flag=ENT_COMPAT]); //将字符串中的特殊字符转换成HTML实体nl2br($str); // 将字符串中的\n用<br/>替换# 拆分/合并explode($delimiter, $str); // 按 $delimiter 分割成数组implode|join($delimiter, $array); // 数组转字符串,用 $delimiter 分割# 比较strcmp($a, $b); // 比较两个字符串的大小,1 a>b,0 a=b,-1 a<bstrcasecmp($a, $b); // 同上,忽略大小strnatcasecmp($a, $b); // 使用自然顺序算法比较字符串# 其他strrev($str); // 反转字符串str_shuffle($str); // 随机打乱字符串
应用层,表示层,会话层,传输层,网络层,数据链路层,物理层


短链接的优缺点:
缺点:多余传输,慢启动问题,握手阶段丢包稳定性较差
优点:简单,理论上连接数会少,无状态对负载均衡会更友好

| 状态码 | 英文 | 含义 | 相关头信息 |
|---|---|---|---|
| 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_autoload_register()
SplQueue
$queue = new SplQueue();$queue->enqueue(1);$queue->enqueue(1);$queue->dequeue();$queue->dequeue();
$q = new SplStack();$q[] = 1;$q[] = 2;$q[] = 3;$q->push(4);$q->add(4,5);$q->pop();
$array = new SplFixedArray(5);$array[1] = 2;$array[4] = "foo";var_dump($array[0]); // NULLvar_dump($array[1]); // int(2)
四私一公
<?phpclass Test{// 注意 instanceof 的用法public static $instance;public static function getInstance(){if( !self::$instance instanceof self ){self::$instance = new self();}return self::$instance;}// 将构造函数私有化private function __construct(){}// 防止用克隆复制对象private function __clone(){}// 防止用序列化复制对象private function __wakeup(){}}$a = Test::getInstance();$b = Test::getInstance();//$c = clone $a;//$d = unserialize(serialize($a));var_dump($a, $b);
以支付方式举例,支付方式分为,微信、支付宝、银联等
如果不使用工厂模式,可能是维护两个类: Wechat 、AliPay,用到哪个 new 哪个,缺点是如果更改了类名,需要全局搜索修改,新接手项目的人也不能一目了然的知道都实现了哪些支付方式。
使用工厂模式:

// 1. 所有的支付类,都共同实现支付接口,如都有 pay() 方法// 2. 使用 switch 来从支付工厂选择方式# 接口类interface PaySimpleFactory{public function pay();}# 工厂类class SimpleFactory{public function get($operate){ // 静态工厂类,用 public static function get()switch ($operate){case "WeChatPay":$res = new WeChatPay();break;case "AliPay":$res = new AliPay();break;default:throw new xxxException("暂不支持的支付方式");}return $res;}}# 简单工厂$factory = new PayFactory();$zhifubao = $factory->get("Alipay");$zhifubao->pay();#静态工厂$zhifubao = PayFactory::get("Alipay");$zhifubao->pay();
优缺点:
帮助封装
解耦
但违反了开放封闭原则(对扩展开放,对修改关闭),因为每增加一个方式,就要修改 switch

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

抽象工厂就是把简单工厂的 switch 抽象成了接口->类
# 工厂接口interface Factory{public function WeChatPay();public function AliPay();}# 工厂类class PayFactory{public function WeChatPay(){return new WeChatPay();}public function AliPay(){return new AliPay();}}
缺点:不仅没有解决简单工厂的开闭原则问题,还更复杂了。
完美解决方式:利用反射来优化抽象工厂
# 工厂接口interface Factory{public function getPay();}# 工厂类class PayFactory{public function getPay($type){$className = __NAMESPACE__."\\".$type;try{$class = new ReflectionClass($className);$instance = class->newInstanceArgs();}cache(...){...}return $instance;}}
<?phpclass Test{public static $Objects;public static function set($alias, $obj){self::$Objects[$alias] = $obj;}public static function get($alias){return self::$Objects[$alias] ?? false;}public static function _unset($alias){unset(self::$Objects[$alias]);}}$t = new Test;$t::set("a", "123");$res = $t::get("a");$res2 = $t::get("a");var_dump($res, $res2);
注册树模式和单例模式很相似,在生成了/设置了对象后,获取的都是同一个对象。但区别就是在未设置对象时,单例模式是创建对象并返回,而注册树返回 false。
单例模式解决的是创建对象唯一实例的问题,工厂模式解决的是如何不通过 new 创建实例,注册树就是方便调用已有对象的
无需关心是如何实现的,只需要获取结果。
# 建造汽车接口interface Builder{// 建造汽车各部分的实现public function buildPartA();public function buildPartB();public function buildPartC();public function buildOthers();/*** 返回最后组装成品结果 (返回最后装配好的汽车),* 成品的组装过程不在这里进行,而是转移到下面的Director类中进行* 从而实现了解耦过程和部件。* return Product*/public function getCar();}# 实现类class Car implements Builder{/*** @var array*/protected $_parts = [];public function buildPartA(){$this->_parts[] = "发动机、";return $this;}public function buildPartB(){$this->_parts[] = "底盘、";return $this;}public function buildPartC(){$this->_parts[] = "变速箱、";return $this;}public function buildOthers(){$this->_parts[] ="其他零件";return $this;}public function getCar(){$str = "这辆车由:";foreach ($this->_parts as $item) {$str .= $item;}$str .= "组成\n";echo $str;}}# 拼接类class Director{private $builder;public function __construct(Builder $builder){$this->builder = $builder;}public function build(){$this->builder->buildPartA();$this->builder->buildPartB();$this->builder->buildPartC();$this->builder->buildOthers();return $this->builder->getCar();}}$car = new Car();$director = new Director($car);
应用场景:
- 可能存在几种类型的对象
- 构造函数具有很多参数
用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
class Test{public $type;public function __construct(){echo "构造函数进行中\n";$this->type = 1;}public function get(){echo "type:".$this->type;}}$t = new Test();echo "----\n";$t2 = clone $t;$t2->get();# 结果构造函数进行中----type:1
适配器又叫包装器,比如三孔插座与二孔插座的转换。原类已经不支持新功能,但又不能修改时,可以使用适配器,包一层再修改。
# 不兼容的类class A{protected $driver = "mysql";public function set(){echo "use {$this->driver}.\n";}}# 接口interface DatabaseTarget{public function set();public function get();}# 适配器类class B extends A implements NewInterface{public function __construct(){$this->driver = "pdo";}public function get(){echo "used {$this->driver}.\n";}}
与类适配器不同的是,不需要继承原类,对其进行扩展,而是将原类的实例作为构造函数的参数注入。
class B implements NewInterface{protected $adaptee;public function __construct(A $adaptee){$this->adaptee = $adaptee;$adaptee->driver = "mysqli";}public function set(){}public function get(){}}
需求:现在要生产(输出)不同颜色的玩具,比如红色小马,黄色小马,红色星星,蓝色星星... 如果不使用桥接模式,可能有多少种玩具就要写多少个类,而使用桥接模式,就可以抽象出 颜色,形状,两种接口,再将实现了两种接口的类互相组合。
# 颜色抽象类abstract class Color{abstract public function output();}# 形状抽象类abstract class Shape{protected $color;public function __construct(Color $color){$this->color = $color;}abstract public function run();# 设定颜色的圆形class Circle extends Shape{public function run(){echo "{$this->color->output()} 的圆形星星\n";}}# 设定颜色class RedColor extends Color{public function output(){return "红色";}}# 合成红色圆形星星!$red = new RedColor();$redCircle = new Circle($red);$redCircle->run();
茶店,顾客可以选一杯茶,然后加入奶/咖啡/...
火锅店,顾客可以要一个汤底,然后加入菜/肉/...
以点一杯奶茶为例:茶和奶对店来说有名称有价格就可以了,是一类,但核心是茶,是一切的基础。最终目的是价格相加来结算。
# 饮料接口interface Drinks{public function name();public function price();}# 为配料定义抽象类abstract class Decorator implements Drinks{protected $drinks;public function __construct(Drinks $drinks){$this->drinks = $drinks;}}# 定义茶(主角,被装饰者)class Tea implements Drinks{public function name(){return '茶';}public function price(){return 10;}}# 定义奶(配角,装饰者)class Milk extends Decorator{public function name(){return '加奶的'.$this->drinks->name();}public function price(){return $this->drinks->price() + 8;}}# 生成奶茶$tea = new Tea();$milkTea = new Milk($tea);echo $milkTea->name();echo $milkTea->price()."元\n";
装饰器模式与桥接模式的区别?
1.没有装饰者和被装饰者的主次区别,桥接和被桥接者是平等的,不用继承自同一个父类。(即桥接可以互换)
2.桥接模式不用使用同一个接口;装饰模式用同一个接口装饰,接口在父类中定义。
将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。组合模式又称为部分整体模式,该模式将对象组合成树形结构以表示"部分-整体"的层次结构。
组合模式主要应用于树形关系,比如公司职位,文件夹与目录的层级关系等。下面以目录为例
透明模式,将所有方法,不管不同级别节点用不用,都用接口约束。因为目录与文件的不同在于,目录可以扩展,文件不可以,是最小节点,所以把“添加下层” 用接口约束,会使文件类无法使用此方法。
# 接口abstract class Component{protected $name;public function __construct($name){$this->name = $name;}abstract public function add(Component $component);abstract public function getName();}# 目录类class Dir extends Component{protected $children = [];public function add(Component $component){$this->children[] = $component;}public function getName(){$nameStr = $this->name ."\n";foreach ($this->children as $k => $v) {$nameStr .= "--" . $v->getName();}return $nameStr;}}# 文件类class File extends Component{public function add(Component $component){throw new \Exception("文件不能添加子节点");}public function getName(){return "--" . $this->name . "\n";}}# 输出整体结构$composite = new Dir("Composite");$transparent = new Dir("Transparent");$composite->add($transparent);$componentFile = new File("Component.php");$dirFile = new File("Dir.php");$transparent->add($componentFile);$transparent->add($dirFile);echo $composite->getName();
安全模式只在接口约束共有方法。各种类的独有方法自己按需实现
abstract class Component{protected $name;public function __construct($name){$this->name = $name;}abstract public function getName();}
安全模式的叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则
1、设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式;
2、开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口;
3、维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。
# 当业户逻辑复杂,客户端需要这样使用时...$a = new A();$a->a();$a->b();$b = new B();$b->a($a);...# 就可以使用‘家丑不外扬’,封装一层,表面看起来光鲜亮丽class Test{public function test(){$a = new A();$a->a();$a->b();$b = new B();$b->a($a);}}$t = new Test();$t->test();
优点:
1、实现了子系统与客户端之间的松耦合关系;
2、客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。
缺点:
1、不符合开闭原则,如果要修改某一个子系统的功能,通常外观类也要一起修改;
2、没有办法直接阻止外部不通过外观类访问子系统的功能,因为子系统类中的功能必须是公开的
一个类获取另一个类的功能,或进行一些扩展。如在原类上包一层代理类,来进行缓存优化、日志记录等功能。
class Proxy{protected $realSubject;public function __construct(){$this->realSubject = new RealSubject(); # 被代理类}public function __call($name, $arguments) # 精髓{return $this->realSubject->{$name}(...$arguments);}public function send(){echo __CLASS__ . " send\n";}}
定义好了各方法的调用顺序,且不可修改,子类实现各块功能
# 抽象接口abstract class AbstractClass{final public function templateMethod() # final 子类不可重写{$this->open();$this->startPlay();$this->endPlay();}protected function open(){echo "open game\n";}abstract protected function startPlay(); # 子类去实现功能protected function endPlay(){echo "end game\n";}}
在状态模式中,类的行为是基于它的状态改变的。比如qq状态,在活跃状态的时候,图标是绿色的,在离线状态的时候,图标是灰色的,就可以用状态模式。
# 中间人角色(状态改变了告诉它,获取状态也找它)class Context {private $state;public function setState(State $state){this.state = $state;}public function getState(){return state;}}# 状态接口,定义状态,和它的行为abstract class State {public function setContext($context) {$context->setState(this);}public function showColor();}# 定义状态class activeState implements State {public function showColor(){return "green";}}class outState implements State {public function showColor(){return "grew";}}# 切换状态$act = new activeState();$context = new Context();$act->setContext($context);echo $context->getState()->showColor();