@octopus
2020-10-19T08:39:52.000000Z
字数 19939
阅读 1224
php
面试
# $a <=> $b, 当 $a 小于,等于,大于 $b 时分别返回 -1,0,1
echo 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 6
6 是 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=1
echo $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(); # NULL
echo 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") 得到 bcd
stristr($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<b
strcasecmp($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]); // NULL
var_dump($array[1]); // int(2)
四私一公
<?php
class 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;
}
}
<?php
class 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();