php 和设计模式 - 原型模式
原型模式通常用于大对象的创建。因为每次 new 一个对象会造成很大开销,原型模式仅需内存拷贝即可。 比较简单,直接上🌰: 12345678910111213141516171819202122232425262728293031interface Book{ public function setTitle(string $title); public function getTitle(): string;}class eBook implements Book{ protected string $title; public function setTitle(string $title) { $this->title = $title; } public function getTitle(): string { return $this->title; }}$book1 = new eBook();$book1->setTitle('1 号电子书');echo $book1->getTitle(), PHP_EOL;$book2 = new eBook();$book2->setTitle('2 号电子书');echo $book2->getTitle(), PHP_EOL; 常规情况下,我们会用 new 创建两个对象,然后分别设置各自的书名。现在用原型模式改造一下: 1234567$prototype = new eBook();foreach(range(1, 10) as $index){ $book = clone $prototype; $book->setTitle($index. ' 号电子书'); echo $book->getTitle(), PHP_EOL;} 类保持不变,只在创建时该用 clone 即可。
php 和设计模式 - 单例模式
对于一些全局使用的类,我们希望在应用中只实例化一个,避免因重复创建和销毁造成资源浪费,例如数据库连接、redis 连接等操作,这个时候就需要用到单例模式。 1234567891011121314151617181920212223242526272829303132333435363738394041class DB{ /** * @var DB $db */ private static $db; public static function getInstance(): DB { if (!(static::$db instanceof self)) { static::$db = new static(); } return self::$db; } // 防止从外部实例化 private function __construct() { } // 防止实例被克隆 private function __clone() { } // 防止实例被反序列化 private function __wakeup() { } public function connect() { echo 'connected', PHP_EOL; }}$db = DB::getInstance();$db->connect(); 单例模式是比较简单的一种模式,但是一定程度上违反了单一职责原则,所以也被认为是一种反模式,即经常出现,但存在一定问题的模式。
php 和设计模式 - 工厂模式
工厂模式工厂模式是一种类,它具有为你创建对象的某些方法,你可以通过工厂创建对象,而不是直接 new,这样当你需要替换创建的对象类型时,只需要修改工厂即可。 根据抽象程度不同,工厂模式又分为简单工厂、工厂方法和抽象工厂三种。 简单工厂简单工厂与静态工厂的唯一区别是有没有使用静态方法生成实例,因此这里不再将其分为两种模式。 123456789101112131415161718192021222324252627282930313233343536373839404142interface Car{ public function makeCar();}class BMWCar implements Car{ public function makeCar() { echo '来一辆别摸我', PHP_EOL; }}class VolvoCar implements Car{ public function makeCar() { echo '来一辆沃尔沃', PHP_EOL; }}class Factory{ public static function createBMW(): BMWCar { return new BMWCar(); } public static function createVolvo(): VolvoCar { return new VolvoCar(); }}$bmw = Factory::createBMW();$bmw->makeCar();$volvo = Factory::createVolvo();$volvo->makeCar(); 实现比较简单,但是当我们要新增一种车🚗时,就必须修改工厂,这在一定程度上违反了开闭原则。所以严格意义上简单工厂不属于 23 种设计模式。 工厂方法工厂方法是针对每一种产品提供一个工厂类,通过不同的工厂实例来创建不同的产品实例。解决了简单工厂新增具体实现需要修改工厂的问题,支持增加任意同一种抽象产品,不修改原有工厂。 那么,把刚才的简单工厂改造一下吧: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253interface Car{ public function makeCar();}class BMWCar implements Car{ public function makeCar() { echo '来一辆别摸我', PHP_EOL; }}class VolvoCar implements Car{ public function makeCar() { echo '来一辆沃尔沃', PHP_EOL; }}interface Factory{ public static function getInstance(): Car;}class BMwFactory implements Factory{ /** * @return BMWCar */ public static function getInstance(): Car { return new BMWCar(); }}class VolvoFactory implements Factory{ public static function getInstance(): Car { return new VolvoCar(); }}$bmw = BMwFactory::getInstance();$bmw->makeCar();$volvo = VolvoFactory::getInstance();$volvo->makeCar(); 不同的工厂生产不同的产品,新增产品类型时,只需要新建一个工厂即可,不再需要改动原有工厂,符合了开闭原则。 抽象工厂抽象工厂与工厂方法的区别是,抽象工厂用于生产一系列产品,工厂方法只生产一个产品,会产生大量的工厂类。 宝马生产跑车、轿车还有 mini,宝马是一个产品族,跑车、轿车等是产品的等级。那么,也就是说一个工厂,可以生产相同品牌的不同产品。 那就不写🌰了吧。懒。 总结简单工厂增加产品不方便。工厂方法增加产品很方便,但是会产生大量的工厂类。抽象工厂可以生产多个产品,但是新增产品也需要修改工厂。
php 和设计模式 - 设计模式分类
一般来说,设计模式分三个大类,分别是创建型模式、结构型模式、行为型模式。但是随着技术的不断发展,也有一些新型的模式出现。 创建型模式提供创建对象的机制,增加已有代码的灵活性和可复用性。 传统的创建型共有 5 种模式,分别是工厂模式、抽象工厂模式、生成器、原型和单例模式。 结构型模式介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。 传统的结构型共有 7 种模式,分别是适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 新型的有依赖注入模式、注册模式和流接口模式。 行为型模式负责对象间的高效沟通和职责委派。 这个最多,共 11 种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式。 其他模式委托模式、服务定位器模式、资源库模式、实体属性值模式。 虽然现在还不知道这些模式都是用来做什么的,但是没关系,一个一个的研究吧。干巴得!(╯‵□′)╯︵┻━┻
php 和设计模式 - 设计原则
设计模式是一套被反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。使用设计模式是为了 提高代码复用性 和 灵活性,让代码更容易被他人理解、保证代码 可靠性。 为了实现代码的 可复用性 和 灵活性。设计模式 提出了一些关键的 面向对象设计原则。 单一职责其核心思想为:一个类,最好只做一件事,应该仅有一个引起它变化的原因。 可以理解为,一个类,应该是一组 相关性很高 的方法及数据的封装。 当一个类承担的职责过多时,就相当于把这些职责耦合在了一起,当其中一个职责发生变动,可能会对其他职责造成影响。 类的职责包括两个方面,数据职责和行为职责,数据职责通过类的属性实现,行为职责通过其方法实现。 单一职责是实现高内聚、低耦合的指导方针。它是最简单但又最难实现的原则,需要开发人员发现类的的不同职责并将其分离。 举个🌰:登陆模块显示登录页面,校验登录参数,连接数据库,查找用户,返回结果。 功能太过耦合,拆分成多个模块。 开闭原则开闭原则是面向对象中最重要的原则。 一个软件应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应该使这个模块可以在不被修改的前提下进行扩展。 一个类一旦开发完成,后续新增的功能就不应该通过修改这个类来完成,而是应该通过继承增加新的类。为什么要对修改关闭呢?因为一旦修改某个类,就可能破坏了系统原有功能,就需要重新测试。 抽象化 是开闭原则的关键。什么是抽象化,就是把一个或多个类中的公共的、有共性的东西抽取出来。抽象的最大好处在于它是抽象的、稳定的,不容易发生改变的。实现开闭原则的核心思想就是 面向接口编程,而不是具体实现。 开闭原则可以用一个更加具体的原则来描述:可变性封装。也就是找到系统中的可变因素并把它封装起来。 上一篇中提到的灯是个绝佳的🌰。 里氏替换所有引用基类(父类)的地方必须能透明的使用其子类的对象。 这句话怎么理解呢?通俗来讲就是在软件中如果能够使用基类对象,那么一定也可以使用其子类对象。把基类都替换为它的子类对象,程序不会产生任何错误和异常。反过来则不成立。 里氏替换应该是开闭原则的一个扩展,由于使用基类对象的地方都可以使用其子类对象,因此在程序中尽量以基类类型来对对象进行定义,而在运行时再用其子类对象替换基类对象。 其中有一点很关键,里氏替换原则强调子类尽量使用基类中的方法,而不是重写,除非子类有其特殊性。 举个🌰,依然是上一篇提到的灯,但是加了一点改动: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253class Light{ function show() { echo '灯光随机', PHP_EOL; }}class BlueLight extends Light{ function show() { echo '蓝色', PHP_EOL; }}class RedLight extends Light{ private bool $power = false; public function hasPower(): bool { return $this->power; } /** * @throws Exception */ function show() { if(!$this->hasPower()){ throw new Exception('没电了,亮不起来'); } 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); 根据里氏替换原则,子类必须能够替代父类。也就是说,虽然子类重写了父类的方法,但是在能够使用父类的场景里面,也一定要能够使用子类。很显然,BlueLight 类符合原则,RedLight 类虽然也实现了父类方法,但是抛出了父类没有的异常,所以违反了里氏替换原则,在 User 类中,我们无论是传入 Light 基类对象,还是 BlueLight 类对象,都是没有任何错误和异常的。 由以上实例,我们可以总结出,里氏替换原则本质是对继承的约束。 依赖倒置依赖于抽象层,不依赖于具体。即高层次的模块不应该依赖低层次模块。抽象不应该依赖于细节,细节应该依赖于抽象。 要面向接口编程,而不是面向实现编程。 一般情况下,我们认为调用者是高层模块,被调用者是底层模块。 实现开闭原则的关键是抽象化,如果说实现开闭原则是面向对象编程的目标,那么依赖倒置就是面向对象编程的主要手段。常用的手段为在代码中使用抽象类,将具体实现放入元数据。 再强调一遍,抽象是相对稳定的,不容易发生改变。 再举个🌰: 12345678910111213141516171819202122232425class Book{ public function getContent() { echo 'long long ago, 遥远的东方有一个特别英俊帅气的小伙', PHP_EOL; }}class Mother{ public function narrate(Book $book) { $book->getContent(); }}class Paper{ public function getContent() { echo '上周五,中国首次实现经济反超美国称为世界第一经济体', PHP_EOL; }}$mother = new Mother();$mother->narrate(new Book()); 在上边的例子中,麻麻看书讲故事,如果有一天,书看烦了,想看个报纸,但是麻麻做不到,因为要读报纸首先要把麻麻改掉。这就有点荒谬。显示不是一个好的设计,原因就是麻麻和书之间的耦合程度太高了,必须降低他们之间的耦合度。 我们来做下调整: 1234567891011121314151617181920212223242526272829303132interface Reader{ public function getContent();}class Book implements Reader{ public function getContent() { echo 'long long ago, 遥远的东方有一个特别英俊帅气的小伙', PHP_EOL; }}class Paper implements Reader{ public function getContent() { echo '上周五,中国首次实现经济反超美国称为世界第一经济体', PHP_EOL; }}class Mother{ public function narrate(Reader $reader) { $reader->getContent(); }}$mother = new Mother();$mother->narrate(new Book());$mother->narrate(new Paper()); 首先,我们抽取出一个接口类 Reader,然后 Book 和 Paper 分别去实现它。然后将 Mother 调整为依赖于接口。现在,无论是想看书还是想看报纸,又或者想看连环画,我们只要去实现 Reader 即可,再也不用改动 Mother 了。 上边里氏替换中灯的例子其实已经符合依赖倒置原则,但是看到这个例子更生动,更容易理解,所以就赘述了一下。 传递依赖关系的方式有三种,上边的例子中使用的接口传递,还有构造函数传递和 setter 传递。 接口隔离客户端不应该依赖它不需要的接口。 使用多个专门的接口,而不是一个大的单一的接口。 看上去似乎和单一职责很像,但是不是,单一职责针对的是类的职责,接口隔离针对的则是接口。 举个🌰: 123456789101112131415161718192021222324252627282930313233interface WorkerInterface{ public function work(); public function sleep();}class HumanWorker implements WorkerInterface{ public function work() { echo 'I like working', PHP_EOL; } public function sleep() { echo 'I like sleeping', PHP_EOL; }}class RobotWorker implements WorkerInterface{ public function work() { echo 'I like working', PHP_EOL; } public function sleep() { echo 'robot never sleep', PHP_EOL; }} 上边的例子中,有个很明显的缺点,机器人不需要睡觉,但是它却必须实现睡觉的方法,这显然违反了接口隔离。 那么,我们再调整一下: 1234567891011121314151617181920212223242526272829303132interface WorkInterface{ public function work();}interface SleepInterface{ public function sleep();}class HumanWorker implements WorkInterface, SleepInterface{ public function work() { echo 'I like working', PHP_EOL; } public function sleep() { echo 'I like sleeping', PHP_EOL; }}class RobotWorker implements WorkInterface{ public function work() { echo 'I like working', PHP_EOL; }} 调整后,接口一分为二,实际使用时各取所需,不再被迫实现自己不需要的接口。 合成复用面向对象有两种方式实现代码复用,一是继承,二还是继承,哦不,而是组合/聚合,也可以叫做合成。合成复用原则要求在软件复用时,首先考虑使用组合、聚合等关联方式实现,其次才考虑使用继承。也就是在一个新对象里通过关联方式使用已有对象的方法和功能。 那么为什么推荐使用组合呢,首先,继承后,父类的方法暴露给了子类,这等于破坏了类的封装性,所以继承复用也被称为白箱复用。其次,父类的方法发生变动会影响到子类,属于耦合度较高的一种表现,不利于代码的维护。最后,继承自基类的方法是静态的,限制了复用的灵活性。 上个图吧,例子挺清晰的。图片出处 emmm,前边反复提的灯的例子就不太符合合成复用原则了。 有兴趣的可以自己改造一下。 迪米特法则迪米特法则又叫最少知道原则,它强调每一个类应当对其它类有尽可能少的了解,不和陌生人说话。也就是尽可能少的产生依赖。 A 和 B 产生交互,B 和 C 产生交互,A 只和 B 交互,不跟 C 玩。 这个法则就比较搞,分了狭义和广义,怎么来的没搞清楚,后边再补充。 狭义强调如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一类的某一个方法的话,可以通过第三者转发这个调用。 广义则是对封装的强调,也就是对信息隐藏的控制。方法封装在类的内部,调用者只需要调用并获取预期结果,不需要关注具体实现。 举个反面🌰: 1234567891011121314class MySchool{ public function manager() { $myClass = new MyClass(); foreach ($myClass->getClasses() as $class) { foreach ($class->getStudents() as $student) { echo $student->getName(), PHP_EOL; } } }} 在上边的例子中,School 类和 Class 类发生交互,但是也和 Student 类发生了交互,违反了最少知道原则。 再来个优化版: 123456789101112class MySchool{ public function manager() { $myClass = new MyClass(); foreach ($myClass->getClasses() as $class) { $class->manager(); } }} 通过 Class 类中 manager 方法去获取学生信息,School与 Student 类的交互就被清除了,School 也不知道 Class 是怎么获取 Student 信息的,它只管调用。 emmm,还可以继续递进,Class manager 只管理自己的信息, 然后调用 Student manager。 1234567891011121314151617181920212223242526272829303132333435363738394041424344class MySchool{ public function manager() { $myClass = new MyClass(); $myClass->manager(); }}class MyClass{ private function getClasses(): array { return [ ... ]; } public function manager() { foreach ($this->getClasses() as $class) { $student = new MyStudent(); $student->manager(); } }}class MyStudent{ private function getStudents(): array { return [ ... ]; } public function manager() { foreach ($this->getStudents() as $student) { echo $student->name, PHP_EOL; } }}
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; }}); 匿名类不支持闭包,也就无法访问定义在匿名类外的属性,但是可以通过构造函数将参数传入。
php 和设计模式
场面话工作几年,复杂的业务场景,重复的 CURD 一直在消耗着我作为程序员的激情与精力,在设计模式这方面的积累从来都不够完善,出去面试时还经常会面临面试官的灵魂拷问,总觉得自己是不是就快被淘汰了。 所以,是时候下功夫整理下这方面的知识了。 开始之前,要考虑一个问题,我们为什么要学习设计模式呢? 首先从概念来讲,设计模式作为一种描述问题及其解决方案的方法,是无数的 IT 前辈在工作中总结出的 特定场景 下的 最佳解决方案,那么当我们遇到同样的场景时,就可以通过使用模式,来实现符合自己程序的解决方案,以此降低代码的耦合度,提高代码的质量,同时也方便我们后期对程序进行调整或拓展。 第二,现在大部分 PHP 程序都是依托于框架进行开发,一般情况下,我们对于框架的使用,只是局限于在一个强大的程序基础设施上添加一些小装饰。那么学习并掌握设计模式以后,我们就能够理解框架是如何解决问题,以及框架解决问题的策略,随着开发的深入,我们也能够以设计为导向,开发出自己的可复用的代码库,这对我们来说,也是一种极大的积累和提升。 第三,对于团队来说,人来人往是常态,对于从一开始就已经接手项目的成员来说,理解程序的逻辑会很轻松,但是对于新加入的成员来说,采用标准化设计模式的程序才是更容易的理解和掌握的存在,这可以使新成员更快的参与到项目的开发工作中,发挥出他作为项目成员的作用。 第四,设计模式定义了专业词汇,通过这些词汇,开发人员之间的沟通变得更加容易,可以节省很多沟通成本。 设计模式与面向对象密切相关,因此我应该不会简单的复制一堆模式来加以理解,而是从面向对象入手,逐渐向设计模式演深。 源码 目录对象 设计原则 设计模式分类 工厂模式 单例模式 生成器模式 原型模式 门面模式 适配器模式 装饰器模式 桥接模式 代理模式 组合模式 享元模式 依赖注入模式 注册模式 流接口模式 策略模式 模板方法模式 观察者模式 迭代器模式 责任链模式 命令行模式 备忘录模式 状态模式 访问者模式 中介者模式
mac m1 下搭建 php 开发环境
一番挣扎之后,还是下手了 m1,真香。 homebrew 安装1/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 网慢的话,host 加映射。 1199.232.68.133 raw.githubusercontent.com php 安装看到好多人说,要把自带的 php 先卸载掉, 但是我折腾一圈发现,卸着太麻烦,不卸也没啥问题。 12brew install php@7.4brew link php@7.4 然后根据输出的信息添加环境变量: 1234echo 'export PATH="/opt/homebrew/opt/php@7.4/bin:$PATH"' >> ~/.zshrcecho 'export PATH="/opt/homebrew/opt/php@7.4/sbin:$PATH"' >> ~/.zshrcexport LDFLAGS="-L/opt/homebrew/opt/php@7.4/lib"export CPPFLAGS="-I/opt/homebrew/opt/php@7.4/include" 如果错过了这些信息,执行以下命令可以再次查看: 1brew info php@7.4 php-exc 安装redis安装扩展需要用到 pecl, 先查看有没有正确安装: 1pecl help version 正常情况下,输出信息应该和 php 版本一致,即 php7.4。 1pecl install redis mongodb12ln -s /opt/homebrew/Cellar/pcre2/10.36/include/pcre2.h /opt/homebrew/Cellar/php@7.4/7.4.16/include/php/ext/pcre/pcre2.hpech install mongodb xdebugxdebug3 配置有改动,具体如下: 1234567xdebug.mode = debugxdebug.client_host = 127.0.0.1xdebug.client_port = 9003xdebug.start_with_request=yesxdebug.log="opt/homebrew/var/www/logs/xdebug.log"xdebug.idekey = PHPSTORMxdebug.discover_client_host=false nginx 安装1brew install nginx 安装完成后,需要解析 php。默认文件不动,添加一个新的配置文件: vi /opt/homebrew/etc/nginx/services/laravel: 123456789101112131415161718192021222324server { listen 80; server_name laravel.test; root html; access_log html/logs/nginx.log; error_log htmle/logs/nginx-error.log; index index.php; location / { # Laravel rewrite rule try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; # fastcgi_pass unix:/dev/shm/php-cgi.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }} 然后重启或刷新 nginx: 1brew services reload nginx || brew services restart nginx 重启以后,记得改下 hosts,把域名加到映射。 1vi /etc/hosts redis 安装12brew install redisredis-server -v composer 安装12345php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"php composer-setup.phpphp -r "unlink('composer-setup.php');"sudo mkdir /usr/local/bin/sudo mv /opt/homebrew/var/www/composer.phar /usr/local/bin/composer
2021 年计划清单
魔幻的 2020 已经成为过去…… 好好活着 giorgio-spa 改版 用 Qt 做个小工具箱 泰拉瑞亚毕业 梳理设计模式 redis 进一步学习 rabbitmq 进一步学习 再剪几个视频玩玩 [ bilibili ] 继续去年的软考
2020 年计划清单
上一年的计划还没有结束,新的一年它就已经来了。 laravel-vue-spa 后台,开发中 composer 包开发 学习一下手写 swoole 分布式框架 尝试做一个基于 laravel-swoole 的 websocket 项目 研究 laravel framework 继续学习 ps,做个不合格的美工 学习 pr 剪辑 软考软件设计师 软考架构师 研究下 win10 linux 子系统在开发中的使用 域名备案
