php 和设计模式 - 桥接模式
发表于|更新于|设计模式
|总字数:284|阅读时长:1分钟|浏览量:
桥接模式也是一个典型的单一职责模式。
在组件设计过程中,如果职责划分不够清晰,当父类发生变更,子类也需要跟着变动,要么违背开闭原则,要么导致子类数量膨胀。桥接模式,就是为了解决这个问题。
桥接模式的做法是,使抽象和实现完全分离,使其能够独立变化。或者也可以直白一点,通过组合/聚合的方式避免继承滥用。
举个🌰:
1 | abstract class Shape |
抽象部分使用继承,实现部分使用组合。
后续如果我们需要换成另外一个颜色,只需要稍作改动即可实现:
1 | class Red implements Color{ |
文章作者: m-finder
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 M-finder!
相关推荐

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 和设计模式 - 模板方法模式
这个模式是对继承的最好诠释。当子类有重复动作时,将其重复动作放入父类统一处理,这就是模板方法最简单通俗的解释。 123456789101112131415161718192021abstract class BaseController{ public function baseMethod() { echo 'base method', PHP_EOL; } public abstract function operate();}class UserController extends BaseController { public function operate() { echo 'user operate', PHP_EOL; }}$user = new UserController();$user->baseMethod();$user->operate(); 这个模式太简单了,就不多说了。

2021-03-21
php 和设计模式 - 依赖注入模式
依赖注入是控制反转的一种实现方式。要实现控制反转,需要将创建被调用者实例的工作交由 IOC 容器完成,然后在调用者中注入被调用者,通常使用构造器或方法注入实现。这样我们舅实现了调用者和被调用者的解偶,这个过程就是依赖注入。 那么控制反转是什么呢?其实也就是 A 依赖于 B,常规做法是在 A 中直接实例化 B,那么控制反转就是将 B 在外部实例化,然后传入 A 去使用。看完以后,其实对依赖注入也就有了理解。 12345678910111213141516171819202122232425262728class Computer{ protected HardDisk $hardDisk; public function __construct(HardDisk $disk) { $this->hardDisk = $disk; } public function run() { $this->hardDisk->run(); echo '一台没有感情的电脑开始运行', PHP_EOL; }}class HardDisk{ public function run() { echo '一块没有感情的硬盘开始运行', PHP_EOL; }}$disk = new HardDisk();$computer = new Computer($disk);$computer->run(); 以上代码就是一个简单的依赖注入,你以为这就结束了?并没有,咱们在学一下 IOC 容器: 1234567891011121314151617181920212223242526272829class Container{ public array $bindings = []; public function bind($key, Closure $value) { $this->bindings[$key] = $value; } public function make($key) { $new = $this->bindings[$key]; return $new(); }}$container = new Container();$container->bind('disk', function (){ return new HardDisk();});$container->bind('computer', function () use($container){ return new Computer($container->make('disk'));});$computer = $container->make('computer');$computer->run(); ok,以上就是依赖注入的全部代码了。

2021-03-21
php 和设计模式 - 访问者模式
这个模式就比较复杂,首先从概念来说,就是将对象的操作外包给其他对象,也就是访问者,从而实现在不改变个元素的前提下定义作用于这些元素的新操作。 当一个基类可以被访问,并具有名为 acceptVisitor 的公共方法,改方法接受参数 Visitor,然后根据传递 Visitor 对象调用公共方法 visit。 1234567891011121314151617181920212223242526272829303132333435363738394041interface Visitor{ public function visit($visitor);}class Element{ protected Visitor $visitor; public string $name; public function __construct(string $name) { $this->name = $name; } public function getName(): string { return $this->name; } public function acceptVisitor(Visitor $visitor) { $visitor->visit($this); }}class NameVisitor implements Visitor{ public function visit($visitor) { echo $visitor->getName(), PHP_EOL; }}$element = new Element('wu');echo $element->getName(), PHP_EOL;$element->acceptVisitor(new NameVisitor()); 我们可以看到,通过调用 acceptVisitor 方法接收一个访问者,具体对象可以把访问者的getName 能力也扩展为自己能力。当然如果你需要多个扩展能力,你可以有多个访问者。而 acceptVisitor 方法调用访问者的visit 方法时,传入 $this 是为了能使用 Element 的属性和方法,使其感觉扩展完就是 Element 的真正一部分。

2021-03-21
php 和设计模式 - 代理模式
说到代理这个词,首先想到的是梯子,它帮助我们解决了网络问题,但是怎么处理的,我们不关心,因为这对大多数人来说属于相对生疏的专业领域。那么代理模式也是一样的道理:为其他对象提供一种代理以控制对这个对象的访问,并允许在将请求提交给对象前后进行一些处理。 按照惯例,来个🌰: 123456789101112131415161718192021222324252627282930313233343536interface RequestInterface{ public function getRequest();}class Request implements RequestInterface{ public function getRequest() { echo 'get request', PHP_EOL; }}class Proxy implements RequestInterface{ protected Request $request; public function __construct() { $this->request = new Request(); } public function getRequest() { echo 'add log in proxy', PHP_EOL; $this->request->getRequest(); }}$proxy = new Proxy();$proxy->getRequest(); 代理模式和适配器模式的区别: 适配器模式是为了改变和适配代理类的接口 代理模式不改变所代理类接口。 代理模式和装饰模式的区别: 装饰模式是为了增强功能 代理模式是为了加以控制

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(); 这个模式代码稍微多一点,但是场景很经典,也很容易理解。