php 和设计模式 - 组合模式
发表于|更新于|设计模式
|总字数:238|阅读时长:1分钟|浏览量:
将对象组合成树状层次结构以表示部分-整体的层次结构,使用户对单个对象和组合对象的使用具有一致性。
两个关键词,树状,部分-整体。
举个🌰:
1 | interface Component |
比较适用于树形菜单、文件和文件夹管理等,感觉场景很有限啊……
文章作者: m-finder
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 M-finder!
相关推荐

2021-03-21
php 和设计模式 - 观察者模式
当对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。它使用的是低耦合的方式。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273class DeleteUserSubject implements \SplSubject{ protected SplObjectStorage $observers; protected $data; public function __construct() { $this->observers = new \SplObjectStorage(); } public function attach(SplObserver $observer) { $this->observers->attach($observer); } public function detach(SplObserver $observer) { $this->observers->detach($observer); } public function notify() { foreach ($this->observers as $observer) { $observer->update($this); } } public function process() { $this->data = new class { public string $name = 'wu'; public function delete() { echo '用户 ', $this->name, ' 被删除', PHP_EOL; } }; $this->data->delete(); echo '开始通知关联处理:', PHP_EOL; $this->notify(); } public function getName() { return $this->data->name; }}class UserLogDeleteObserver implements \SplObserver{ protected SplSubject $subject; public function update(SplSubject $subject) { $this->subject = clone $subject; $this->deleteUserLog(); } public function deleteUserLog() { echo '删除用户', $this->subject->getName(),' 的日志', PHP_EOL; }}$subject = new DeleteUserSubject();$subject->attach(new UserLogDeleteObserver());$subject->process(); 这个模式代码稍微多一点,但是场景很经典,也很容易理解。

2021-03-21
php 和设计模式 - 注册模式
注册模式通常将对象注册到一个全局的对象树上,需要的时候从对象树上取下需要的实例,就像卖糖葫芦的有木有。不过不一样的是糖葫芦会摘完,对象树摘下后并不会销毁该对象。 注册模式通常通过一个只有静态方法的抽象类来存放这些对象。或者通过单例模式。 1234567891011121314151617181920212223abstract class registry{ const LOGGER = 'logger'; public static array $objects = []; public static function set($key, $value) { self::$objects[$key] = $value; } public static function get($key) { return self::$objects[$key]; }}$key = Registry::LOGGER;$logger = new stdClass();Registry::set($key, $logger);$storedLogger = Registry::get($key);var_dump($storedLogger);

2021-03-21
php 和设计模式 - 命令行模式
用过 laravel 框架的应该都知道,其脚本模块非常强大,这些脚本,也就是命令行模式。 说到命令行,就不得步提一下 cli 和 cgi 的区别,在 nginx 中,php 并不是直接执行的,而是通过 cgi 调用 php 并获取执行结果。 而 cli 就是命令行接口,主要用于 shell 脚本的开发。 123456789php command.php/opt/homebrew/var/www/design-patternphp-cli command.phpX-Powered-By: PHP/7.4.16Content-type: text/html; charset=UTF-8/opt/homebrew/var/www/design-pattern 不多说了,回头再专门看一下这方面的东西。

2021-03-21
php 和设计模式 - 迭代器模式
提供一种方法顺序访问一个集合对象中的各种元素,而又不暴露该对象的内部表示。 foreach 的底层就是迭代器。很多编程语言都已经将其作为一个基础类库实现出来了,所以也就有了这个模式目前学习意义大于实际意义的说法。 在 php 中,内部已提供 Iterator 接口,可以直接使用。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879class Bookshelf implements \Countable, \Iterator{ protected array $books = []; protected int $current = 0; public function addBook(Book $book) { $this->books[] = $book; } public function current() { return $this->books[$this->current]; } public function next() { return $this->current++; } public function key(): int { return $this->current; } public function valid(): bool { return isset($this->books[$this->current]); } public function rewind() { $this->current = 0; } public function count(): int { return count($this->books); }}class Book{ protected string $author; protected string $title; public function __construct(string $author, string $title) { $this->author = $author; $this->title = $title; } public function getAuthor(): string { return $this->author; } public function getTitle(): string { return $this->title; } public function getAuthorAndTitle(): string { return $this->getAuthor() . '-' . $this->getTitle(); }}$bookA = new Book('wu', 'php');$bookB = new Book('wu', 'redis');$bookshelf = new Bookshelf();$bookshelf->addBook($bookA);$bookshelf->addBook($bookB);foreach ($bookshelf as $book) { echo $book->getAuthorAndTitle(), PHP_EOL;} 使用起来还是比较简单的,至于如何实现就不写了。

2021-03-21
php 和设计模式 - 对象
我们经常会用类描述对象,也经常会用对象描述类,但是这有碍于我们对于面向对象的理解,因为类决定了对象。 简而言之,类,是用来生成一个或多个对象的代码模板。 对象是根据类中定义的模板所构建的数据,我们通常会说对象是它的类的实例,对象的类型是由类定义的。 你可以用 class 关键字和任意类名来声明一个类,类名可以是任意数字和字母的组合,但不能以数字开头,类体必须定义在一对大括号内: 123456class Person{}$person1 = new Person();$person2 = new Person(); 通过关键字 new 去创建 Person 类的对象,在上面的代码中,创建了两个实例,它们是由同一个类创建的、具有相同类型的不同对象。 如果将类看作是一个生产用的铸模,那么对象就是用铸模生产出来的具体产品。 类属性我们可以在类中定义称为 属性 的特殊变量。属性也称为 成员变量,可以用来保存各个对象中不同的数据。 除了在声明它们时必须指定可见性关键字,成员变量与普通变量看起来非常相似。 可见性关键字为 private,protected 和 public,它们确定类成员变量能够被访问的作用域。 1234567class Person{ public string $name = 'wu';}$person = new Person();echo $person->name; 类方法类方法是类中的特殊函数,它允许对象执行任务。 方法声明与函数声明类似,但是它必须在类体内。 我们可以给它加上限定词,包括可见性关键字。 1234567891011class Person{ public string $name = 'wu'; public function getName() { return $this->name; }}$person = new Person();echo $person->getName(); 继承继承是指从基类中派生出一个或多个类的机制。 如果一个类继承自另外一个类,那么就说它是另外一个类的子类。这种关系通常用父子关系来形容。 子类派生自父类并继承来父类的特性,这些特性包括属性也包括方法。 通常,子类会在父类所提供的功能基础上增加一些新功能,因此,也可以说子类扩展了父类。 继承通常用来 解决代码重复,在一个类中提供共通功能,又能在其他类在处理一些方法调用时有所不同。 封装封装是指隐藏对象内的属性和具体实现,仅对外提供公共访问方式。 封装通过 可见性关键字 把一个对象的属性私有化,同时提供一些可以被外界访问属性的方法,如果不想被外界访问,我们大可不提供方法给外界。但如果一个类没有提供给外界访问的方法,那么这个类也就没有意义了。 这样做的好处是: 良好的封装可以减少代码耦合 类内部的结构可以自由修改 可以对成员进行更精确的控制 隐藏信息,实现细节 具体场景如 model 获取数据。 封装可以 提高灵活性。使我们更容易地修改类的内部实现,而无需修改使用了该类的客户代码,从而实现对成员变量进行更精确的控制。 多态可以理解为多种表现形式,即一个对外接口,多种内部实现。 在面向对象的理论中,多态性的一般定义为:同一个操作(函数)作用于不同的类的实例,将产生不同的执行结果。即不同类的对象接收到相同的消息时,将会得到不同的结果。 举个🌰: 12345678910111213141516171819202122232425262728293031class Light{ public function show($type) { switch($type){ case 0: echo '红色', PHP_EOL; break; case 1: echo '蓝色', PHP_EOL; break; case 2: echo '绿色', PHP_EOL; break; } }}class User{ public function openLight($type = 0) { $light = new Light(); $light->show($type); }}$user = new User();$user->openLight(); 这是一个存在弊端的实现,如果灯光颜色非常多,后期添加就会非常麻烦。 多态实现: 12345678910111213141516171819202122232425262728293031323334353637383940class Light{ function show() { echo '灯光随机', PHP_EOL; }}class BlueLight extends Light{ function show() { echo '蓝色', PHP_EOL; }}class RedLight extends Light{ function show() { echo '红色', PHP_EOL; }}class User{ function openLight(Light $light) { $light->show(); }}$user = new User();$light = new Light();$blueLight = new BlueLight();$redLight = new RedLight();$user->openLight($light);$user->openLight($blueLight);$user->openLight($redLight); 静态方法前面说过,类是生成对象的代码模板,对象是类的实例。我们可以调用对象的属性和方法。在前边的例子中,也都是通过对象调用属性和方法。 事实上,我们也可以访问类的属性和方法,这种方法和属性都是静态的,需要用关键字 static 声明: 1234567891011class Person{ public static string $name = 'wu'; public static function getName() { return self::$name; }}echo Person::getName(); 静态方法拥有类作用域,它们无法访问类的普通属性。因为这些属性是对象的。 静态属性和静态方法是在类上被调用的,而不是在对象上,因此它们也被称为 类属性 和 类方法。我们也无法在类中通过伪变量 $this 调用,而是需要通过对应的 self。 静态属性和静态方法可以使我们无需将一个对象传入另一个对象就可以访问而不需要实例。这可以使我们省去实例化对象的麻烦,从而使代码更加整洁。 常量属性有些属性是不应当被改变的,这个时候就应该用关键字 const 去声明常量属性。 与普通属性不同,常量不以 $ 开头,并且根据约定,通常用大写字母命名。 12345class Person{ const LEG_NUMBER = 2;}echo Person::LEG_NUMBER; 常量只能是基本类型的值,无法用来保存对象,并且与静态属性一样,我们需要通过类来访问常量。 抽象类抽象类无法被实例化,它的作用是为所有子类(继承自它的类)定义接口。 抽象类用关键字 abstract 声明。 1234567891011abstract class Person{ public string $name; public function getName() { return $this->name; } public abstract function setName();} 可以像在普通类中一样在抽象类中创建方法和属性,但是当实例化这个类时,就会有报错出现。因为抽象类不能被实例化。 一般情况下,抽象类至少有一个抽象方法,使用同样的关键字声明,但不能有方法体。 任何继承自抽象类的非虚子类都必须实现所有的抽象方法,否则它自己就必须被定义为抽象类。 接口抽象类允许提供一些实现,但是接口则是纯粹的模板,只提供定义功能,不能有实现。 使用关键字 interface 声明接口,其中可以有常量成员和方法的声明,但是不能有方法体。 123456789101112131415interface Person{ public function getName(): string;}class Man implements Person{ public string $name; public function getName(): string { // TODO: Implement getName() method. }} PHP 中的类只能有一个继承,但是可以同时实现多个接口。 TraitPHP 不支持多继承,一个类只能有一个父类,但是可以实现多个接口。 接口提供没有任何实现的类型,如果我们希望在继承层次中共享实现,就需要借助于 trait。 trait 是类似于类的结构,它本身不能被实例化,但是可以混合到类中,在 trait 中定义的任何方法都可以被使用它的任何类所使用。 延时静态绑定:static 关键字static 和 self 类似,区别在于前者引用的是被调用的类,而不是包含类。 1234567891011121314abstract class DomainObject{ public static function create() { return new static(); }}class User extends DomainObject(){ }$user = User::create(); 调用 User::create() 会创建一个 User 实例,而不是尝试创建 DomainObject 实例。 异常捕获1234567try{ $name = 'wu';}catch(\Exception $e){ throw $e;}finally{ echo 'finally';} 无论 catch 子句是重新抛出异常还是返回一个值,finally 子句都会执行,但如果在 try 或 catch 中调用了 die() 或 exit(),那么程序就会终止,finally 子句也就不会执行。 final 类final 类可以防止类再被继承。final 方法也无法重写。 123456final class Person{ public final function getName() { return 'wu'; }} 内部错误捕获可以在 try catch 子句中通过指定 Error 这个父类或它的子类来捕获相匹配的内部错误。 123456789try { eval('illegal code');} catch (\ParseError $e) { echo 'parse error', PHP_EOL;} catch (\Error $e) { echo 'error', PHP_EOL;} finally { echo 'finally', PHP_EOL;} 同样的, finally 在这里也可以用。 拦截器PHP 内置的拦截器方法可以拦截发送给为定义方法和属性的消息。 PHP 支持三个内置的拦截器方法。与 __construct() 相似,这些方法也会在适当的条件下自动调用。 方法 说明 __get($property) 访问未定义属性时会被调用 __set($property, $value) 对未定义属性赋值时会被调用 __isset($property) 对未定义属性调用 isset()时调用 __unset($property) 对未定义属性调用 unset()时调用 __call($method, $args) 调用未定义非静态方法时调用 __callStatic($method, $args) 调用未定义静态方法时调用 1234567891011121314151617class Person{ public function __get($property) { $method = "get{$property}"; if(method_exists($this, $method)){ return $this->$method(); } } public function getName(): string { return 'wu'; }}$person = new Person();echo $person->name; 析构方法析构方法会在类被垃圾回收前,也就是从内存中抹去前调用。可以用这个方法执行一些必要的清理工作。 析构方法和前边的拦截器都属于魔术方法,使用时应该慎重。 回调、匿名函数和闭包回调有什么作用呢?它允许程序在运行期间将与组件核心任务没有直接关系的功能插入组件。通过让组件拥有回调能力,可以赋予其他程序员在我们不知道的上下文上获得扩展程序的能力。 12345678910111213141516171819202122232425class Person{ protected $callbacks = []; protected $name = 'wu'; public function __construct() { $log = function ($person){ echo $person->name, PHP_EOL; }; $this->callbacks[] = $log; } public function getName(): string { foreach ($this->callbacks as $callback) { if(is_callable($callback)){ call_user_func($callback, $this); } } return $this->name; }} 上面的代码中,将匿名函数(闭包函数)赋值给 $log,然后将它作为参数传递给函数和方法,然后在指定的位置进行回调。 匿名函数可以引用那些声明在其父作用域中的变量,通过 use() 操作。 匿名类当需要从很小的类中创建和继承实例,并且这个类很简单而且特定于局部上下文时,匿名类非常有用。 12345678910111213141516171819202122interface PersonWriter{ public function write(Person $person);}class Person{ public $name = 'wu'; public function getName(PersonWriter $writer) { echo $writer->write($this), PHP_EOL; }}$person = new Person();$person->getName(new class implements PersonWriter{ public function write(Person $person) { echo $person->name, PHP_EOL; }}); 匿名类不支持闭包,也就无法访问定义在匿名类外的属性,但是可以通过构造函数将参数传入。

2021-03-21
php 和设计模式
场面话工作几年,复杂的业务场景,重复的 CURD 一直在消耗着我作为程序员的激情与精力,在设计模式这方面的积累从来都不够完善,出去面试时还经常会面临面试官的灵魂拷问,总觉得自己是不是就快被淘汰了。 所以,是时候下功夫整理下这方面的知识了。 开始之前,要考虑一个问题,我们为什么要学习设计模式呢? 首先从概念来讲,设计模式作为一种描述问题及其解决方案的方法,是无数的 IT 前辈在工作中总结出的 特定场景 下的 最佳解决方案,那么当我们遇到同样的场景时,就可以通过使用模式,来实现符合自己程序的解决方案,以此降低代码的耦合度,提高代码的质量,同时也方便我们后期对程序进行调整或拓展。 第二,现在大部分 PHP 程序都是依托于框架进行开发,一般情况下,我们对于框架的使用,只是局限于在一个强大的程序基础设施上添加一些小装饰。那么学习并掌握设计模式以后,我们就能够理解框架是如何解决问题,以及框架解决问题的策略,随着开发的深入,我们也能够以设计为导向,开发出自己的可复用的代码库,这对我们来说,也是一种极大的积累和提升。 第三,对于团队来说,人来人往是常态,对于从一开始就已经接手项目的成员来说,理解程序的逻辑会很轻松,但是对于新加入的成员来说,采用标准化设计模式的程序才是更容易的理解和掌握的存在,这可以使新成员更快的参与到项目的开发工作中,发挥出他作为项目成员的作用。 第四,设计模式定义了专业词汇,通过这些词汇,开发人员之间的沟通变得更加容易,可以节省很多沟通成本。 设计模式与面向对象密切相关,因此我应该不会简单的复制一堆模式来加以理解,而是从面向对象入手,逐渐向设计模式演深。 源码 目录对象 设计原则 设计模式分类 工厂模式 单例模式 生成器模式 原型模式 门面模式 适配器模式 装饰器模式 桥接模式 代理模式 组合模式 享元模式 依赖注入模式 注册模式 流接口模式 策略模式 模板方法模式 观察者模式 迭代器模式 责任链模式 命令行模式 备忘录模式 状态模式 访问者模式 中介者模式