一、设计模式分类
二十三 种设计模式,大致分为三大类:
创建型五种:
单例、工厂、抽象工厂、建造者、原型模式
结构型七种:
适配器、装饰、桥接、组合、享元、代理、外观模式
行为型十一种:
命令、状态、职责链、解释器、中介者、访问者、策略、备忘录、迭代器、模板、观察者
二、设计模式六大原则
1. 单一职责原则
一个类,应该仅有一个引起它变化的原因。
2. 开放-封闭原则
软件实体(类、模块、函数等)应该可以扩展,但是不可修改。
3. 依赖倒转原则
A. 高层模块不应该依赖底层模块。两个都应该依赖抽象。
B. 抽象不应该依赖细节。细节应该依赖抽象。
4. 里氏代换原则
子类型必须能够替换掉它们的父类。
5. 迪米特法则=最少知识原则
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
6. 接口隔离
建立单一接口
a.客户端不应该依赖它不需要的接口
b.类之间依赖关系应该建立在最小的接口上
接下来是以 PHP 进行编码,开始我们的设计模式之旅
三、实现设计模式
创建型:
1、单例模式
UML
解析
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
某些应用程序资源是独占的,因为有且只有一个此类型的资源。例如,通过数据库句柄到数据库的连接是独占的。您希望在应用程序中共享数据库句柄,因为在保持连接打开或关闭时,它是一种开销,在获取单个页面的过程中更是如此。单元素模式可以满足此要求。如果应用程序每次包含且仅包含一个对象,那么这个对象就是一个单元素(Singleton)
普通写法:
<?php
class Singleton {
private static $new; //申请一个私有的静态成员变量来保存该类的唯一实例
private function __construct() {} //声明私有的构造方法,防止类外部创建对象
public static function getInstance () { //声明一个静态公共方法,供外部获取唯一实例
if (! (self::$new instanceof self)) {
self::$new = new self;
}
return self::$new;
}
private function __clone() {} //声明私有的克隆方法,防止对象被克隆
public function __sleep() { //重写__sleep方法,将返回置空,防止序列化反序列化获得新的对象
return [];
}
}
线程安全:
lock 是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
public static function getInstance () { //声明一个静态公共方法,供外部获取唯一实例
//在这里加锁
lock {
if (! (self::$new instanceof self)) {
self::$new = new self;
}
}
return self::$new;
}
双重锁定:
public static function getInstance () { //声明一个静态公共方法,供外部获取唯一实例
if (! (self::$new instanceof self)) { //线程安全
lock {
if (! (self::$new instanceof self)) {
self::$new = new self;
}
}
}
return self::$new;
}
2、简单工厂模式
UML
解析:
简单工厂:是由一个工厂对象决定创建出哪一种产品类的实例。A实例调用B实例的方法,称为A依赖于B。如果使用new关键字来创建一个B实例(硬编码耦合),然后调用B实例的方法。 一旦系统需要重构:需要使用C类来代替B类时,程序不得不改写A类代码。而用工厂模式则不需要关心B对象的实现、创建过程。
优势: 让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。从而避免了对象的调用者与对象的实现类以硬编码方式耦合,以提高系统的可维护性、可扩展性。
缺陷: 当产品修改时,工厂类也要做相应的修改,违反了开-闭原则。如上例,需要增加乘法 时,需要修改工厂类OperationFactory
简单工厂模式适用于业务简单的情况下或者具体产品很少增加的情况。
代码展示:
<?php
class Operation {
private $_numberA = 0;
private $_numberB = 0;
public function getNumberA() {
return $this->_numberA;
}
public function getNumberB() {
return $this->_numberB;
}
public function setNumberA($value) {
$this->_numberA = $value;
}
public function setNumberB($value) {
$this->_numberB = $value;
}
public function getResult() { }
}
class OperationAdd extends Operation {
public function getResult()
{
return $this->getNumberA() + $this->getNumberB();
}
}
class OperationSub extends Operation {
public function getResult()
{
return $this->getNumberA() - $this->getNumberB();
}
}
class OperationFactory
{
public function createOperate($operate)
{
$operator = new Operation();
switch ($operate){
case '+':
$operator = new OperationAdd();
break;
case '-':
$operator = new OperationSub();
break;
}
return $operator;
}
}
$factory = new OperationFactory();
$add = $factory->createOperate('+');
$sub = $factory->createOperate('-');
$add->setNumberA(1);
$add->setNumberB(2);
echo $add->getResult();
3、抽象工厂模式
UML
解析
抽象工厂模式提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。
优点:
- 易于交换产品系列,具体工厂类只在初始化的时候出现一次,改变一个应用的具体工厂很容易。
- 让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。
缺点:
a. 如果增加项目需要改动抽象接口和具体工厂类。
普通写法—代码展示
<?php
class User
{
}
interface IUser
{
public function Insert($user);
public function getUser($userId);
}
class SqlServerUser implements IUser
{
public function Insert($user)
{
echo 'SQL_SERVER插入';
}
public function getUser($userId)
{
echo 'SQL_SERVER获取';
}
}
class MySQLUser implements IUser
{
public function Insert($user)
{
echo 'MySQL插入';
}
public function getUser($userId)
{
echo 'MySQL获取';
}
}
interface IFactory
{
public function createUser();
}
class SqlServerFactory implements IFactory
{
public function createUser()
{
return new SqlServerUser();
}
}
class MySQLFactory implements IFactory
{
public function createUser()
{
return new MySQLUser();
}
}
$user = new User();
$factory = new MySQLFactory();
$IUser = $factory->createUser(); //创建User模型对象
$IUser->Insert($user); //插入数据
$IUser->getUser(1); //获取数据
反射写法—代码展示
<?php
namespace dataAccess;
use ReflectionClass;
use ReflectionException;
interface IUser
{
public function Insert();
public function getUser();
}
class SqlServerUser implements IUser {
public function Insert()
{
echo 'SQL_SERVER插入';
}
public function getUser()
{
echo 'SQL_SERVER获取';
}
}
class MySQLUser implements IUser {
public function Insert()
{
echo 'MySQL插入';
}
public function getUser()
{
echo 'MySQL获取';
}
}
class DataAccessFactory {
private $db = 'SqlServer';
public $namespace = 'dataAccess\\';
public function __construct()
{
/**
* 从配置项中获取 driver
*/
$config = include 'data_access_config.php';
$this->db = $config['driver'];
}
public function createUser()
{
$className = $this->namespace . $this->db . 'User';
try {
$class = new ReflectionClass($className);
$user = $class->newInstance();
}catch (ReflectionException $exception) {
throw new \InvalidArgumentException('暂不支持的数据库类型');
}
return $user;
}
}
$data = new DataAccessFactory();
$sql = $data->createUser();
$sql->Insert();
4、建造者模式(生成器模式)
UML
解析
建造者模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需知道建造者的类型就可以得到它们,而具体的建造过程和细节就不需知道了。适用于建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。
代码展示
<?php
class Product //产品类
{
private $parts = array();
public function add($part)
{
$this->parts [] = $part;
}
public function show()
{
echo "产品创建...\n";
foreach ($this->parts as $key => $value)
{
var_dump($value);
}
}
}
abstract class Builder //抽象建造类
{
abstract public function buildPartA();
abstract public function buildPartB();
abstract public function getResult();
}
class ConcreteBuilder extends Builder //具体建造类
{
private $product;
function __construct()
{
$this->product = new Product();
}
public function buildPartA()
{
$this->product->add('部件A');
}
public function buildPartB()
{
$this->product->add('部件B');
}
public function getResult()
{
return $this->product;
}
}
class Director
{
public function Construct($builder)
{
$builder->buildPartA();
$builder->buildPartB();
}
}
$director = new Director(); //指挥者
$builder = new ConcreteBuilder(); //建造者
$director->Construct($builder); //创建
$product = $builder->getResult(); //获取产品
$product->show(); //展示
5、原型模式
UML
解析
原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建细节。
原型浅复制—代码展示
<?php
abstract class Prototype
{
private $id;
public $v = PHP_EOL;
function __construct($id)
{
$this->id = $id;
echo 'create' . PHP_EOL;
}
public function getID()
{
return $this->id;
}
public abstract function cloneObj();
}
class ConcretePrototype extends Prototype
{
public function run()
{
echo '复制来,复制去,都是自己';
}
public function cloneObj()
{
return clone $this;
}
}
class Client
{
public function execute()
{
$prototype = new ConcretePrototype('ME');
$clone = $prototype->cloneObj();
echo 'ID是' . $clone->getID() . "\n";
$clone->run();
echo $prototype->v . '我是原型';
echo $clone->v . '我是克隆';
}
}
$client = new Client();
$client->execute();
原型深复制—代码演示
<?php
interface ICloneable
{
public function cloneObj();
public function setPersonalInfo($name, $age);
public function setWorkExperience($company);
public function display();
}
class Person implements ICloneable
{
private $name;
private $sex;
private $work;
function __construct($work)
{
$this->work = $work;
}
public function run()
{
echo '复制来,复制去,都是自己';
}
public function cloneObj()
{
$person = new Person($this->work);
$person->sex = $this->sex;
$person->name = $this->name;
return $person;
}
public function setPersonalInfo($name, $sex)
{
$this->name = $name;
$this->sex = $sex;
}
public function setWorkExperience($company)
{
$this->work->company = $company;
}
public function display()
{
echo $this->name . "\n";
echo $this->sex . "\n";
echo $this->work->workDate . '进入' . $this->work->company . "\n";
}
}
class WorkExperience
{
public $company;
public $workDate;
}
class Client
{
public function execute()
{
$prototype = new Person(new WorkExperience());
$prototype->setPersonalInfo('ztf' , '男');
$prototype->setWorkExperience('百度');
$clone = $prototype->cloneObj();
$prototype->display();
$clone->display();
}
}
$client = new Client();
$client->execute();
结构型
6、适配器模式
UML
解析
适配器模式,将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适应情况:系统的数据和行为都正确,但接口不符时,我们应,在双方都不太容易修改的时候,该考虑用适配器。
<?php
/**
* Class Player
* Target 客户所期待的接口,可以是具体的或抽象的类,也可以是接口
*/
abstract class Player
{
public $name;
function __construct($name)
{
$this->name = $name;
}
public abstract function attack();
public abstract function defense();
}
/**
* Class Forwards
*/
class Forwards extends Player //前锋
{
function __construct($name)
{
parent::__construct($name);
}
public function attack()
{
echo $this->name . '进攻' . "\n";
}
public function defense()
{
echo $this->name . '防守' . "\n";
}
}
class ForeignCenter //Adaptee需要适配的类
{
private $name;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function attack()
{
echo '外籍中锋' . $this->name . '进攻' . "\n";
}
public function defense()
{
echo '外籍中锋' . $this->name . '防守' . "\n";
}
}
/**
* Class Translator
* Adapter通过在内部包装一个 Adaptee 对象,把源接口转换成目标接口
*/
class Translator extends Player //翻译者
{
private $foreignCenter;
function __construct($name)
{
parent::__construct($name);
$this->foreignCenter = new ForeignCenter();
$this->foreignCenter->setName($name);
}
public function attack()
{
$this->foreignCenter->attack();
}
public function defense()
{
$this->foreignCenter->defense();
}
}
class Client
{
public function Main()
{
$player = new Forwards('詹姆斯');
$player->attack();
$FP = new Translator('姚明');
$FP->attack();
$FP->defense();
}
}
$client = new Client();
$client->Main();
7、装饰模式
UML
解析
装饰模式,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
适应情况:一般在类中加入新的字段、新的方法和新是逻辑,从而增加主类的复杂性,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。装饰器把每个要装饰的功能放在单独的类种,并让这个类包装它所要的装饰对象。
代码展示
<?php
abstract class Component
{
public abstract function operation();
}
class ConcreteComponent extends Component
{
public function operation()
{
echo '具体对象的操作' . "\n";
}
}
abstract class Decorator extends Component
{
protected $component;
public function setComponent($component)
{
$this->component = $component;
}
public function operation()
{
if ($this->component != null) {
$this->component->operation();
}
}
}
class ConcreteDecoratorA extends Decorator
{
public function operation()
{
//首先运行原Component的operation(),再执行本类的功能,如AddedBehavior(),相当于对原Component进行装饰
parent::operation();
$addedState = 'New State';
echo '具体装饰对象A的操作' . "\n";
}
}
class ConcreteDecoratorB extends Decorator
{
public function operation()
{
parent::operation();
$this->AddedBehavior();
echo '具体装饰对象B的操作' . "\n";
}
private function AddedBehavior()
{
echo '新行为' . "\n";
}
}
$c = new ConcreteComponent();
$Ad = new ConcreteDecoratorA();
$Bd = new ConcreteDecoratorB();
$Ad->setComponent($c);
$Bd->setComponent($Ad);
$Bd->operation();
8、桥接模式
UML
解析
桥接模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化。实现是指抽象类和它的派生类用来实现自己的对象。
使用情况:当系统有可能多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来,让它们独立变化,减少它们之间的耦合。
代码展示
<?php
abstract class Implementor
{
public abstract function operation();
}
class ConcreteImplementorA extends Implementor //模块二
{
public function operation()
{
echo '具体实现 A 的方法执行' . "\n";
}
}
class ConcreteImplementorB extends Implementor
{
public function operation()
{
echo '具体实现 B 的方法执行' . "\n";
}
}
class Abstraction //抽象关联类
{
protected $implementor;
public function setImplementor($implementor) //桥接,关联方法
{
$this->implementor = $implementor;
}
public function operation()
{
$this->implementor->operation();
}
}
class RefinedAbstraction extends Abstraction //模块一
{
public function operation()
{
$this->implementor->operation();
}
}
$ab = new RefinedAbstraction();
$ab->setImplementor(new ConcreteImplementorA());
$ab->operation();
$ab->setImplementor(new ConcreteImplementorB());
$ab->operation();
9、组合模式
UML
解析
组合模式,将对象组合成树形结构以表示’部分—整体’的层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。组合模式使得用户对单个对象和组合对象的使用具有一致性。
适应情况:需求当中是部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一的使用组合结构中的所有对象。
代码展示
<?php
abstract class Component
{
public $name;
function __construct($name)
{
$this->name = $name;
}
public abstract function add($component);
public abstract function remove($component);
public abstract function display($depth);
}
class Leaf extends Component
{
//因为叶子没有再增加分枝和树叶,add 和 remove 没有意义,
//但是为了消除叶节点和枝节点对象在抽象层的区别,它们具有一致的接口
public function add($component)
{
echo '我不是枝干' . "\n";
}
public function remove($component)
{
echo '不能从一个叶子节点移除节点' . "\n";
}
public function display($depth)
{
echo str_repeat('-', $depth) . $this->name . "\n";
}
}
class Composite extends Component
{
private $listComponent = array();
public function add($component)
{
$this->listComponent[$component->name] = $component;
}
public function remove($component)
{
unset($this->listComponent[$component->name]);
}
public function display($depth)
{
foreach ($this->listComponent as $key => $value) {
$value->display($depth + 2);
}
}
}
$root = new Composite('root');
$root->add(new Leaf('Leaf A'));
$root->add(new Leaf('Leaf B'));
$comp = new Composite('Composite X');
$comp->add(new Leaf('Leaf XA'));
$root->add($comp);
$leaf = new Leaf('Leaf D');
$root->add($leaf);
$root->remove($leaf);
$root->display(1);
10、享元模式
UML
解析
共享模式运用共享技术有效地支持大量细粒度的对象,避免大量的非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外,基本上都是相同的,有时能大幅度减少需要实例化的类的数量。
适应情况:
- 一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用
- 对象大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组合对象。
代码展示
<?php
abstract class Flyweight
{
public abstract function operation($extrinsicState);
}
class ConcreteFlyweight extends Flyweight
{
public function operation($extrinsicState)
{
echo '具体的 Flyweight';
var_dump($extrinsicState);
}
}
class UnsharedConcreteFlyweight extends Flyweight
{
public function operation($extrinsicState)
{
echo '不共享具体的 Flyweight';
var_dump($extrinsicState);
}
}
class FlyweightFactory
{
private $flyweights = array();
function __construct()
{
$this->flyweights['X'] = new ConcreteFlyweight();
$this->flyweights['Y'] = new ConcreteFlyweight();
$this->flyweights['Z'] = new ConcreteFlyweight();
}
public function getFlyweight($key)
{
return $this->flyweights[$key];
}
}
class Client
{
public function execute()
{
$extrinsicState = 22; //代码外部状态
$f = new FlyweightFactory();
$fx = $f->getFlyweight('X');
$fx->operation(--$extrinsicState);
$fy = $f->getFlyweight('Y');
$fy->operation(--$extrinsicState);
$fz = $f->getFlyweight('Z');
$fz->operation(--$extrinsicState);
$uf = new UnsharedConcreteFlyweight();
$uf->operation(--$extrinsicState);
}
}
$client = new Client();
$client->execute();
11、代理模式
UML
解析
代理模式,为其他对象提供一种代理以控制对这个对象的访问。
适用情况:
- 远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同的地址空间的事实。
- 虚拟代理,根据需要创建开销很大的对象。通过它来存放实例化需要很长实践的真实对象。
- 安全代理,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限。
- 智能指引,当调用真实对象时,代理处理另外一些事。
代码展示
<?php
abstract class Subject
{
public abstract function Request();
}
class RealSubject extends Subject
{
public function Request()
{
echo '真实的请求';
}
}
class Proxy extends Subject
{
private $realSubject;
public function Request()
{
if ($this->realSubject == null) {
$this->realSubject = new RealSubject();
}
$this->realSubject->Request();
}
}
class Client
{
public function execute()
{
$proxy = new Proxy();
$proxy->Request();
}
}
$client = new Client();
$client->execute();
12、外观模式
UML
解析
外观模式,为子系统中的一组接口提供一个一致的界面,此模式定义一个高层接口,这个接口使得这一子系统更加容易使用。
适用情况:
- 设计初,应该有意识的将不同的两个层分离
- 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观 Facade 可以提供一个简单的接口,减少它们之间的依赖。
- 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,新的需求开发必须要依赖它
代码展示
<?php
class SubSystemOne
{
public function MethodOne()
{
echo '子系统方法一' . "\n";
}
}
class SubSystemTwo
{
public function MethodTwo()
{
echo '子系统方法二' . "\n";
}
}
class SubSystemThree
{
public function MethodThree()
{
echo '子系统方法三' . "\n";
}
}
class Facade
{
private $subSystemOne;
private $subSystemTwo;
private $subSystemThree;
function __construct()
{
$this->subSystemOne = new SubSystemOne();
$this->subSystemTwo = new SubSystemTwo();
$this->subSystemThree = new SubSystemThree();
}
public function methodA()
{
echo '方法组A() ------' . "\n";
$this->subSystemOne->MethodOne();
$this->subSystemThree->MethodThree();
}
public function MethodB()
{
echo '方法组B() ------' . "\n";
$this->subSystemTwo->MethodTwo();
$this->subSystemThree->MethodThree();
}
}
class Client
{
public function execute()
{
$facade = new Facade();
$facade->MethodA();
$facade->MethodB();
}
}
$client = new Client();
$client->execute();