设计模式应用的目的是封装代码中可变的部分,提高代码的复用性,实现代码设计的高内聚,低耦合的原则。把应用程序中经常要变动的和那些不怎么变动的分离出来,把易变的拿出来封装,使得他不会影响其他代码,那么对其他代码的影响就会降到最低,而代码的可扩展性就更高。
设计模式中心思想:SOLID原则。(设计模式只是实现这几种原则在不同场景下的应用)
1. 方法和对象中尽量少直接引用其他对象和方法,而是通过参数注入的方法来传递调用。
2. 写代码时候,每写一个类,就要考虑这个类的功能是否有扩展的可能,如果扩展我该怎么做,是否违背开闭原则。
3. 通常在给类添加功能,莫过于直接修改,或者派生对应的子类扩展,或者是使用组合,显然前两种弊端很多,要尽量使用对象组合的方式。(优先选择对象组合而不是继承,这样一来,子类就不会因为继承大量没用的方法和属性而膨胀。)
以下常用的10中模式解析以及分类:
创建模式:工厂模式,单例模式。
结构模式:适配器模式,装饰着模式。
行为模式:观察者模式,策略模式,命令链模式,迭代器模式,状态模式,委托模式。
模式的代码实现结构可能很相似,区别就在于应用在不同的场景。
开闭原则里的对扩展开放,对修改关闭,修改指的是修改旧的方法或者类来实现新的功能,而不是说不动代码。
1. 工厂模式。
您可以使用工厂类创建对象,而不直接使用new。这样,如果您想要更改所创建的对象类型,只需更改该工厂即可。使用该工厂的所有代码会自动更改。
interface IDB{
function connect($config);
}
class Mysql implements IDB{
public function connect($config){
return $config;
}
}
class DB{
public function create(){
return new Mysql();
}
}
$db = DB :: create();
$db -> connect($config);
总结:如果直接new Mysql(),假如有天要换sqlServer数据库,那就要每个调用地方都修改了。
2. 单例模式
某些应用程序资源是独占的,因为有且只有一个此类型的资源。例如,通过数据库句柄到数据库的连接是独占的。您希望在应用程序中共享数据库句柄,因为在保持连接打开或关闭时,它是一种开销。
class DB{
static private $_instance = null;
static public function getInstance(){
if(empty(self :: $_instance)){
self :: $_instance = new Mysql();
}
return self :: $_instance;
}
}
3. 观察者模式
观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:一个对象通过添加一个方法(该方法允许另一个对象,即观察者注册自己)使本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。这些观察者使用该信息执行的操作与可观察的对象无关。结果是对象可以相互对话,而不必了解原因。
interface IObserver{
public function onChange( $sender, $args );
}
interface IObserable{
public function addObserver( $observer );
}
class UserListLog implements IObserver{
//观察者
public function onChange( $sender, $args ){
return "observer: $args";
}
}
class UserList implements IObserable{
private $_observers = [];
public function addObserver($observer){
$this -> _observers[] = $observer;
}
public function addCustomer( $name ){
foreach($this -> _observers as $observer){
//触发对所有注册的观察者方法的调用
$observer -> onChange( $this, $name );
}
}
}
$ul = new UserList();
总结:添加了客户,就调用所有注册的操作对象的方法,使得用户操作和观察者完全分离,如果再添加其他的观察者就不用修改内部代码,只用注册新的观察者就好。当然你也可以加删除观察者的方法。
4. 命令链模式
通过一组处理程序发送任意内容。每个处理程序都会自行判断自己能否处理请求。如果可以,该请求被处理,进程停止。您可以为系统添加或移除处理程序,而不影响其他处理程序。
interface ICommand{
function onCommand( $name, $args );
}
class MailCommand implements ICommand{
public function onCommand( $name, $args ){
if( $name != 'mail' ) return false; //执行命令的逻辑封装在每个命令类里面,如果不是调用这个命令,就执行下一个命令
echo "Mail : $args";
return true; //如果是就退出命令执行链
}
}
class UserCommand implements ICommand{
public function onCommand( $name, $args ){
if( $name != 'user' ) return false;
echo "User: $args";
return true;
}
}
class CommandChain{
private $_commands = [];
public function addCommand( $cmd ){
$this -> _commands[] = $cmd;
}
public function runComand( $name, $args ){
foreach($this -> _commands as $cmd){
if($cmd -> onCommand( $name, $args )){
return; //如果成功执行了命令,就退出命令链,如果没有就继续往下
}
}
}
}
$cc = new CommandChain();
$cc -> addCommand( new UserCommand() );
$cc -> addCommand( new MailCommand() );
$cc -> runCommand( 'user', null);
$cc -> runCommand( 'mail', null);
总结:初看,和观察者结构很像,都是添加,执行。但区别就是场景,命令链模式重点在“链“,控制执行链里的哪个命令。
5. 策略模式
在此模式中,算法是从复杂类提取的,因而可以方便地替换。
interface IStrategy{
public function filter( $record );
}
class FindStrategy implements IStrategy{
private $_name;
public function __construct( $name ){
$this -> _name = $name;
}
public function filter( $record ){
return strcmp( $this -> _name, $record ) <= 0;
}
}
class RandomStrategy implements IStrategy{
public function filter( $record ){
return rand(0, 1) >= 0.5;
}
}
class UserList{
private $_list = [];
public function __construct( $names ){
if( !empty($names) ){
foreach($names as $name){
$this -> _list[] = $name;
}
}
}
public function addUser( $name ){
array_push($this -> _list, $name);
}
public function find( $filter ){
$recs = [];
foreach($this -> _list as $name){
if( $filter -> filter( $name ) ){
$recs[] = $name;
}
}
return $recs;
}
}
$ul = new UserList( array('Jack', 'Tim', 'BoB') );
$f1 = $ul -> find( new FindAfterStrategy('J') );
print_r($f1);
$f2 = $ul -> find( new RandomStrategy() );
print_r($f2);
总结:对同一份数据进行不同策略处理。策略和数据处理完全分离,方便扩展和替换。
6. 适配器模式
在需要将一类对象转换成另一类对象时,请使用适配器模式。适配器模式适用于代码重构或者添加新功能,是一种亡羊补牢的模式。将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原来由于接口不兼容而不能一起工作的那此类可以一起工作
//新需求,适配后需要达到的目标角色
interface ITarget{
public function simpleMethod1();
public function simpleMethod2();
}
//旧的需要适配的角色
class Adaptee{
public function simpleMethod1(){
echo "Adaptee simpleMethod1";
}
}
class Adapter implements ITarget{
private $_adaptee;
public function __construct(Adaptee $Adaptee){
$this -> _adaptee = $Adaptee;
}
public function simpleMethod1(){
$this -> _adaptee -> simpleMethod1();
};
public function simpleMethod2(){
//新添加的功能
echo "Adapter simpleMethod2";
};
}
class Client{
public static function main(){
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
$adapter -> simpleMethod1();
$adapter -> simpleMethod2();
}
}
Client ::main();
总结: ITarget是新的需求接口,要实现这样的接口功能,还要沿用本来的Adaptee的方法,构造出Adapter类,在能成功实现调用旧的Adaptee类的simpleMethod1方法的基础上,实现simpleMethod2的功能。
7. 迭代器模式
迭代器模式将提供一种通过对象集合或对象数组封装迭代的方法。如果需要遍历集合中不同类型的对象,则使用这种模式尤为便利。
interface IAddressIterator{
//实现循环的退出
public function hasNext();
//实现循环指针的移动
public function next();
}
class PersonAddressIterator implements IAddressIterator{
private $_emailAddrs;
//指针的初始位置
private $position = 0;
public function __construct( $addrs ){
$this -> _emailAddrs = $addrs;
}
public function hasNext(){
if($this -> position >= sizeof($this -> _emailAddrs) || !isset($this -> _emailAddrs[$this -> position])){
return false;
}
return true;
}
public function next(){
$this -> position ++;
}
public function current(){
return $this -> _emailAddrs[$this -> position];
}
}
$addr = new PersonAddressIterator(['11@qq.com', '22@qq.com', '33@qq.com', '44@qq.com',]);
while($addr -> hasNext()){
echo $addr ->current();
$addr ->next();
}
总结:和php的spl封装的迭代器相似。
8. 装饰者模式
通过组合而非继承的方式,实现了动态扩展对象的功能的能力。有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
abstract class Beverage{
//饮料
private $_name;
abstract public function cost();
}
class Coffee extends Beverage{
public function __construct(){
$this -> _name = 'Coffee';
}
public function Cost(){
return 1.00;
}
}
class CondimentDecorator extends Beverage{
//调料类
public function __construct(){
$this -> _name = "Condiment";
}
public function Cost(){
return 0.1;
}
}
class Milk extends CondimentDecorator{
private $_beverage;
public function __construct($beverage){
$this -> _name = "Milk";
if($beverage instanceof Beverage){
$this -> _beverage = $beverage;
}
else{
exit('Failure');
}
}
public function Cost(){
return $this -> _beverage -> Cost() + 0.2;
}
}
class Sugar extends CondimentDecorator{
private $_beverage;
public function __construct($beverage){
$this -> _name = "Sugar";
if($beverage instanceof Beverage){
$this -> _beverage = $beverage;
}
else{
exit('Failure');
}
}
public function Cost(){
return $this -> _beverage -> Cost() + 0.3;
}
}
$coffee = new Coffee();
$coffee = new Milk($coffee);
$coffee = new Sugar($coffee);
$coffee -> Cost();
总结:装饰者(Milk)和被装饰者(Coffee)必须是一样的类型。目的是装饰者必须取代被装饰者。添加行为:当装饰者和组件组合时,就是在加入新的行为。
9. 委托模式
通过分配或委托至其他对象,委托设计模式能够去除核心对象中的判决和复杂的功能性。
class Bank{
protected $info;
/*
设置基本信息
*/
public function updateBankInfo($type, $money){
$this -> info[$type] = $money;
}
/*
相关操作(包括存款、取款操作)
@param int $branktype 操作类型
*/
public function brankWithdraw($branktype){
$obj = new $branktype;
return $obj -> brankMain($this->info);
}
}
interface Delegate{
//操作方法,实现该接口必须实现的方法
public function brankMain($info);
}
// 存款操作
class brankDeposit implements Delegate{
public function brankMain($info){
echo $info['deposit'];
}
}
//取款
class brankWithdraw implements Delegate{
public function brandMain($info){
echo $info['withdraw'];
}
}
$bank = new Bank();
$bank->updateBrankInfo("deposit","4000");
$bank->updateBrankInfo("withdraw","2000");
$bank->brankWithdraw("brankDeposit");
echo "<br>";
$bank->brankWithdraw("brankWithdraw");
总结:在传统方式下,我们需要判断当前操作是取款操作还是存款操作,在分别调用Bank类中的取款操作和存款操作。在委托模式下,我们将不需要客户端的判断操作,对客户端来说,需要什么操作,直接传入操作类型即可,Bank类可自动判断操作类型,返回相应操作的操作结果。当我们的操作类型非常多的时候,在客户端用if else判断无疑是很可怕的,再假如我们在很多地方都要有这块判断代码,我们需要对这些地方的判断代码都进行修改(加入后来添加的判断),而采用委托模式,我们仅仅需要在新添加的地方添加相应需要的类型即可,不需要改动其它地方的客户端代码(很大程度上提高了代码的复用性)。
对于银行来说,取款,存款等操作是可变的行为(假如我再加个开户),所以单独分离出来。而委托模式就实现了这一要求,把本来属于bank的可变操作委托给一个外部的新类,通过调用可变的新类来实现不同的操作。
10. 状态模式
其意图是允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了他的类。他根据自身的状态作出不同的反应。
interface State{
public function handle($state);
public function display();
}
class Content{
private $_state = null;
public function __construct($state){
$this -> _state = $state;
}
public function setState($state){
$this -> _state = $state;
}
public function request(){
$this -> _state -> display();
//通过状态类来调用自身的类的setSate方法
$this -> _state -> handle($this);
}
}
class StateA implements State{
public function handle($content){
$content -> setState(new StateB());
}
public function display(){
echo "state A <br>";
}
}
class StateB implements State{
public function handle($content){
$content -> setState(new StateC());
}
public function display(){
echo "state B <br>";
}
}
class StateC implements State{
public function handle($content){
$content -> setState(new StateA());
}
public function display(){
echo "state C <br>";
}
}
$obj = new Content(new StateB());
$obj -> request();
$obj -> request();
$obj -> request();
$obj -> request();
$obj -> request();
总结:根据content的不同状态,调用不同状态类下的不同操作方法(display,handle是用来切换状态的)。(同样的,把可变的状态操作类提取出来封装)