设计模式

设计模式

设计模式之策略者模式(组合模式)

架构思想zkbhj 发表了文章 • 0 个评论 • 3005 次浏览 • 2019-08-29 11:20 • 来自相关话题

设计一款模拟鸭子的游戏,游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫。所用的鸭子会呱呱叫,也会游泳,所以基类负责实现。但是每一款鸭子外观不一样,所以这是抽象行为。由子类实现。

前期设计及思路 
根据情景的设计,首先我们想到这是一些列的鸭子,便会想到利用继承的手段,进行解决。其UML图型为: 





 
后续设计:需要让有的鸭子飞,有的鸭子不能飞。

后续设计 
问题: 
1、在调用中是通过抽象类进行操作的,所以新功能在超类有所体现, 
但是下载超类中实现,就所有的鸭子会飞。 
2、在超类中是飞的行为是抽象函数,这样每个子类都要实现,代码重写太复杂。 
3、用接口,只让飞的鸭子继承实现飞的行为接口,这样在超类中进行初始化接口。 
4、但是用接口后,让飞的行为进行修改,那个每个子类中飞的行为也要修改,这样改动也是很大。 
经过分析,利用组合的原则,将变化部分进行提取,封装一个新类,所以我们见fly这个行为提取出来,行为上肯定是接口,同时这个接口由行为类实现,而不是具体鸭子类实现,这样就实现策略模式。 
这里的接口就是所谓的概念,针对“利用接口编程”,关键在于多态。利用多态,程序可以对超类进行操作,根据实际情况执行到真正行为,将行为和对象进行解耦。 
在程序运行时就是针对超类编程,超类编程就是接口和抽象类。 

图为:





 
所以这样针对新行为,我们重新定义个接口,然后实现这个接口,在调用时初始化接口。 
其中主要的代码为:
//关键这是超类,必定是抽象类
public abstract class Duck
{
protected IFlyBehavior flyBehavior;

protected QuackBehavior quackBehavior;

public virtual void Quack()
{
throw new System.NotImplementedException();
}
//当然这些行为也可通过接口,但是这游泳鸭子都会,所以超类直接实现
public virtual void Swim()
{
throw new System.NotImplementedException();
}
//抽象函数,子类必须实现,意味子类这个行为不同
public abstract void DisPlay();

public virtual void performFly()
{
//通过接口执行fly行为,要寻找接口在那初始化
flyBehavior.fly();
}

}
//duck的子类,其中初始化了接口
public class BlueDuck : Duck
{
public BlueDuck()
{
//初始化接口,在用BlueDuck初始化Duck时,接口也被初始化了。
flyBehavior = new FlyWithWings();
}
public override void DisPlay()
{
throw new System.NotImplementedException();
}
}当你将两个类结合起来使用时,如同本例,这就是组合。这种做法的不同是在于行为不是继承而来,单独通过行为类和接口进行实现。 
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,使算法的变化独立于使用算法的客户。
 
原文地址:https://www.cnblogs.com/polly333/p/4705682.html 查看全部
设计一款模拟鸭子的游戏,游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫。所用的鸭子会呱呱叫,也会游泳,所以基类负责实现。但是每一款鸭子外观不一样,所以这是抽象行为。由子类实现。

前期设计及思路 
根据情景的设计,首先我们想到这是一些列的鸭子,便会想到利用继承的手段,进行解决。其UML图型为: 

QQ截图20190829111702.jpg

 
后续设计:需要让有的鸭子飞,有的鸭子不能飞。

后续设计 
问题: 
1、在调用中是通过抽象类进行操作的,所以新功能在超类有所体现, 
但是下载超类中实现,就所有的鸭子会飞。 
2、在超类中是飞的行为是抽象函数,这样每个子类都要实现,代码重写太复杂。 
3、用接口,只让飞的鸭子继承实现飞的行为接口,这样在超类中进行初始化接口。 
4、但是用接口后,让飞的行为进行修改,那个每个子类中飞的行为也要修改,这样改动也是很大。 
经过分析,利用组合的原则,将变化部分进行提取,封装一个新类,所以我们见fly这个行为提取出来,行为上肯定是接口,同时这个接口由行为类实现,而不是具体鸭子类实现,这样就实现策略模式。 
这里的接口就是所谓的概念,针对“利用接口编程”,关键在于多态。利用多态,程序可以对超类进行操作,根据实际情况执行到真正行为,将行为和对象进行解耦。 
在程序运行时就是针对超类编程,超类编程就是接口和抽象类。 

图为:

QQ截图20190829111747.jpg

 
所以这样针对新行为,我们重新定义个接口,然后实现这个接口,在调用时初始化接口。 
其中主要的代码为:
//关键这是超类,必定是抽象类
public abstract class Duck
{
protected IFlyBehavior flyBehavior;

protected QuackBehavior quackBehavior;

public virtual void Quack()
{
throw new System.NotImplementedException();
}
//当然这些行为也可通过接口,但是这游泳鸭子都会,所以超类直接实现
public virtual void Swim()
{
throw new System.NotImplementedException();
}
//抽象函数,子类必须实现,意味子类这个行为不同
public abstract void DisPlay();

public virtual void performFly()
{
//通过接口执行fly行为,要寻找接口在那初始化
flyBehavior.fly();
}

}
//duck的子类,其中初始化了接口
public class BlueDuck : Duck
{
public BlueDuck()
{
//初始化接口,在用BlueDuck初始化Duck时,接口也被初始化了。
flyBehavior = new FlyWithWings();
}
public override void DisPlay()
{
throw new System.NotImplementedException();
}
}
当你将两个类结合起来使用时,如同本例,这就是组合。这种做法的不同是在于行为不是继承而来,单独通过行为类和接口进行实现。 
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,使算法的变化独立于使用算法的客户。
 
原文地址:https://www.cnblogs.com/polly333/p/4705682.html

设计模式:面向对象设计模式(OOP)

架构思想zkbhj 发表了文章 • 0 个评论 • 2771 次浏览 • 2018-03-01 10:26 • 来自相关话题

首先我们来看下官方的定义。在维基百科上说:

  面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象

 
要理解什么是面向对象,我们就先要知道什么是面向过程。

面向过程:就是将要实现一个功能所需要的步骤一步一步的写出来出来,要做到面面俱到、有条不絮。

 
面向对象设计的三个基本要素

面向对象的三个基本特征是:封装、继承、多态。


1·封装性

封装性是一种信息隐蔽技术,他体现于类的说明,是对象重要的特性。封装使得数据和操作数据的方法封装为一个整体,想成独立性很强的模块,使得用户只能看到对象的外部特性(对象可以接受拿些信息,可以进行何种处理),而对象的内部特性(内部私有属性和实现处理能力的算法)用户是看不到的。简而言之就是说,封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,无需知道具体是怎么实现的。借助封装有助于提高类和系统的安全性。

2·继承性

继承是一种由已有类创建子类的机制,利用继承,可以先创建一个共有属性的一般类,根据这个类再创建具有特殊属性的子类,被继承的类成为父类,当然子类也可以成为父类来继续向下扩展。

3·多态性

同一个信息被不同的对象接收到时可能产生不同的行为,这就是多态性。有继承(接口)有重写,父类引用指向子类对象,就会产生多态。多态可以改善程序的组织架构,提高程序的可扩展性。

面向对象设计的五个基本设计原则

单一职责原则(SRP)、开放封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)


1·单一职责原则(Single-Responsibility Principle)

其核心思想为:一个类只做一件事情,只有一个引起他的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则上的隐身,将职责定义为引起变化的原因,以提高内举行来减少引起变化的原因。职责过多可能引起他变化的原因也就越多,这将导致职责依赖,相互之间产生影响,从而大大损伤内聚性和耦合度。单一职责就是指,只有一种单一的功能,不要为类实现过多的功能点,有些功能可以定义为接口来实现,以保证只有一个引起他变化的原因。
专注是一个人优良的品质。同样的,单一也是一个类的优良设计,杂交不清的职责将使得代码看起来特别别扭,牵一发动全身,有失没敢和必然导致丑陋的系统错误风险。

2·开放封闭原则(Open-Closeed Principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭。开放封闭原则主要体现在两个方面
1、对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开放封闭原则的核心思想就是对抽象编程,而不是具体编程,因为抽象相对稳定。让类依赖于固定的抽象类或者接口,所以修改就是封闭的。而通多面向对象的继承和多态机制,又可以继承抽象类或者实现接口,通过重写其方法来改变固有的行为,实现方法新的拓展,所以就是开放的。
需求总是变化的,没有不变的软件,所以就需要用OCP来封闭变化,满足需求,同时还能保持软件内部的封装体系的稳定,不被需求的变化影响。

3·里氏替换原则(Liskov-Substituion Principle)

核心思想:子类必须能够替换其父类。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将父类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换父类,但是父类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

4·依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是不变的。依赖于抽象,就是对接口编程,不要对实现编程。

5·接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。 查看全部
首先我们来看下官方的定义。在维基百科上说:


  面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象


 
要理解什么是面向对象,我们就先要知道什么是面向过程。


面向过程:就是将要实现一个功能所需要的步骤一步一步的写出来出来,要做到面面俱到、有条不絮。


 
面向对象设计的三个基本要素


面向对象的三个基本特征是:封装、继承、多态。



1·封装性

封装性是一种信息隐蔽技术,他体现于类的说明,是对象重要的特性。封装使得数据和操作数据的方法封装为一个整体,想成独立性很强的模块,使得用户只能看到对象的外部特性(对象可以接受拿些信息,可以进行何种处理),而对象的内部特性(内部私有属性和实现处理能力的算法)用户是看不到的。简而言之就是说,封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,无需知道具体是怎么实现的。借助封装有助于提高类和系统的安全性。

2·继承性

继承是一种由已有类创建子类的机制,利用继承,可以先创建一个共有属性的一般类,根据这个类再创建具有特殊属性的子类,被继承的类成为父类,当然子类也可以成为父类来继续向下扩展。

3·多态性

同一个信息被不同的对象接收到时可能产生不同的行为,这就是多态性。有继承(接口)有重写,父类引用指向子类对象,就会产生多态。多态可以改善程序的组织架构,提高程序的可扩展性。

面向对象设计的五个基本设计原则


单一职责原则(SRP)、开放封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)



1·单一职责原则(Single-Responsibility Principle)

其核心思想为:一个类只做一件事情,只有一个引起他的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则上的隐身,将职责定义为引起变化的原因,以提高内举行来减少引起变化的原因。职责过多可能引起他变化的原因也就越多,这将导致职责依赖,相互之间产生影响,从而大大损伤内聚性和耦合度。单一职责就是指,只有一种单一的功能,不要为类实现过多的功能点,有些功能可以定义为接口来实现,以保证只有一个引起他变化的原因。
专注是一个人优良的品质。同样的,单一也是一个类的优良设计,杂交不清的职责将使得代码看起来特别别扭,牵一发动全身,有失没敢和必然导致丑陋的系统错误风险。

2·开放封闭原则(Open-Closeed Principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭。开放封闭原则主要体现在两个方面
1、对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开放封闭原则的核心思想就是对抽象编程,而不是具体编程,因为抽象相对稳定。让类依赖于固定的抽象类或者接口,所以修改就是封闭的。而通多面向对象的继承和多态机制,又可以继承抽象类或者实现接口,通过重写其方法来改变固有的行为,实现方法新的拓展,所以就是开放的。
需求总是变化的,没有不变的软件,所以就需要用OCP来封闭变化,满足需求,同时还能保持软件内部的封装体系的稳定,不被需求的变化影响。

3·里氏替换原则(Liskov-Substituion Principle)

核心思想:子类必须能够替换其父类。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将父类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换父类,但是父类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

4·依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是不变的。依赖于抽象,就是对接口编程,不要对实现编程。

5·接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。

PHP设计模式之:适配器模式

架构思想zkbhj 发表了文章 • 0 个评论 • 2232 次浏览 • 2018-01-29 11:15 • 来自相关话题

在这个有没有对象都要高呼“面向对象”的年代,掌握面向对象会给我们带来意想不到的方便。学编程的小伙伴从开始能写几行代码实现简单功能到后来懂得将一些重复的操作组合起来形成一个“函数”,再到后来将“函数”和属性组合起来形成一个“类”。一步步走来,我们在考虑着机器运行代码效率的提高的同时也在考虑减轻程序员的工作量。 那么我们今天讲到的适配器模型更着重考虑的是什么呢?

是程序员工作量。

 




什么时候会用到适配器模式?

其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码。 

难道真的一定要如此吗?按照套路来说,我会回答“不是的”。我们有适配器模式啊~~  

当接口发生改变时,适配器模式就派上了用场。

举个栗子

如果通过上面的简单描述,你都能懂,那在下只能佩服你的领悟能力超群了。一般人一定还是不知所云。为了方便理解,我引用一位博友的例子。原文地址。

一开始的和谐


黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

    为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。(其实我经常当这样的傻子)abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。搁谁都不淡定了。abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了“开-闭”原则,一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那么总得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。  适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。 

到此,解决方法已经呼之欲出了,下面贴上代码。<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}


//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}



class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testDriver();
$test->run();
 最后的结果就是,Toy类及其子类在不改变自身的情况下,通过适配器实现了不同的接口。

最后总结

将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作.

适配器模式核心思想:把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

      以上 查看全部
在这个有没有对象都要高呼“面向对象”的年代,掌握面向对象会给我们带来意想不到的方便。学编程的小伙伴从开始能写几行代码实现简单功能到后来懂得将一些重复的操作组合起来形成一个“函数”,再到后来将“函数”和属性组合起来形成一个“类”。一步步走来,我们在考虑着机器运行代码效率的提高的同时也在考虑减轻程序员的工作量。 那么我们今天讲到的适配器模型更着重考虑的是什么呢?


是程序员工作量。


 
timg_(4).jpg


什么时候会用到适配器模式?

其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码。 

难道真的一定要如此吗?按照套路来说,我会回答“不是的”。我们有适配器模式啊~~  

当接口发生改变时,适配器模式就派上了用场。

举个栗子

如果通过上面的简单描述,你都能懂,那在下只能佩服你的领悟能力超群了。一般人一定还是不知所云。为了方便理解,我引用一位博友的例子。原文地址

一开始的和谐


黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

    为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。(其实我经常当这样的傻子)
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}
更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。搁谁都不淡定了。
abstract class Toy  
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}
在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了“开-闭”原则一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那么总得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。  适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。 

到此,解决方法已经呼之欲出了,下面贴上代码。
<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}


//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}



class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testDriver();
$test->run();

 最后的结果就是,Toy类及其子类在不改变自身的情况下,通过适配器实现了不同的接口。

最后总结

将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作.

适配器模式核心思想:把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

      以上

设计模式:工厂模式

专业名词zkbhj 发表了文章 • 0 个评论 • 2139 次浏览 • 2017-11-09 20:15 • 来自相关话题

工厂模式分为简单工厂、工厂方法模式和抽象工厂模式。
 
简单工厂模式(Simple Factory)
 
顾名思义,这个模式本身很简单,而且使用在业务较简单的情况下。
它由三种角色组成:
工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不通,产生具体的工厂产品。如例子中的Driver类。抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。如例中的Car接口。具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。
 
来用类图来清晰的表示下的它们之间的关系:











优点:客户端不需要修改代码。
缺点: 当需要增加新的运算类的时候,不仅需新加运算类,还要修改工厂类,违反了开闭原则。

工厂方法模式​
 
抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
 UML类图如下:





 
这个和简单工厂有区别,简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂

好处:增加一个运算类(例如N次方类),只需要增加运算类和相对应的工厂,两个类,不需要修改工厂类。

缺点:增加运算类,会修改客户端代码,工厂方法只是把简单工厂的内部逻辑判断移到了客户端进行。
 
抽象工厂模式:
 UML类图如下:





 
    从图上可以看出这和工厂方法模式很相似,但是呢,有几个区别:
抽象工厂模式,一个具体工厂可以制造几个产品,例如微软工厂(相当于SqlserverFactory)可以制造微软鼠标(属于鼠标类,鼠标类下面有惠普鼠标,微软鼠标等,相当于上图中的IDepartment下面的SqlserverDepartment和AccessDepartment),也可以制造微软键盘(属于键盘类,键盘类下面有惠普键盘,微软键盘等,相当于上图中的IUser下面的SqlserverUser和AccessUser)。
引用http://blog.csdn.net/wangwenhu ... 55125中的话:

工厂方法模式:一个抽象产品类,可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
              每个具体工厂类只能创建一个具体产品类的实例。 
抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
             每个具体工厂类可以创建多个具体产品类的实例。 
区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

抽像工厂优缺:
优点:易于交换产品系列,例如Access和SQLServer数据库之间切换。
缺点:例如增加一个机箱产品,不仅需要添加三个类“机箱类,微软机箱,惠普机箱”,还要修改惠普工厂,微软工厂支持制造机箱。而添加一个联想工厂的时候,只需要添加三个类,使用联想工厂还是要修改客户端代码的。

在《大话设计模式》中,提出用简单工厂模式改进抽象工厂模式的方法。






  查看全部
工厂模式分为简单工厂、工厂方法模式和抽象工厂模式。
 
简单工厂模式(Simple Factory)
 
顾名思义,这个模式本身很简单,而且使用在业务较简单的情况下。
它由三种角色组成:
  • 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不通,产生具体的工厂产品。如例子中的Driver类。
  • 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。如例中的Car接口。
  • 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。

 
来用类图来清晰的表示下的它们之间的关系:
r_简单工厂.jpg



25958655_1399530326s3Z6.jpg


优点:客户端不需要修改代码。
缺点: 当需要增加新的运算类的时候,不仅需新加运算类,还要修改工厂类,违反了开闭原则。

工厂方法模式​
 
  1. 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

 UML类图如下:

25958655_1399530481VM7v.jpg

 
这个和简单工厂有区别,简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂

好处:增加一个运算类(例如N次方类),只需要增加运算类和相对应的工厂,两个类,不需要修改工厂类。

缺点:增加运算类,会修改客户端代码,工厂方法只是把简单工厂的内部逻辑判断移到了客户端进行。
 
抽象工厂模式:
 UML类图如下:

25958655_1399530523DmFr.jpg

 
    从图上可以看出这和工厂方法模式很相似,但是呢,有几个区别:
抽象工厂模式,一个具体工厂可以制造几个产品,例如微软工厂(相当于SqlserverFactory)可以制造微软鼠标(属于鼠标类,鼠标类下面有惠普鼠标,微软鼠标等,相当于上图中的IDepartment下面的SqlserverDepartment和AccessDepartment),也可以制造微软键盘(属于键盘类,键盘类下面有惠普键盘,微软键盘等,相当于上图中的IUser下面的SqlserverUser和AccessUser)。
引用http://blog.csdn.net/wangwenhu ... 55125中的话:

工厂方法模式:一个抽象产品类,可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
              每个具体工厂类只能创建一个具体产品类的实例。 
抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
             每个具体工厂类可以创建多个具体产品类的实例。 
区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

抽像工厂优缺:
优点:易于交换产品系列,例如Access和SQLServer数据库之间切换。
缺点:例如增加一个机箱产品,不仅需要添加三个类“机箱类,微软机箱,惠普机箱”,还要修改惠普工厂,微软工厂支持制造机箱。而添加一个联想工厂的时候,只需要添加三个类,使用联想工厂还是要修改客户端代码的。

在《大话设计模式》中,提出用简单工厂模式改进抽象工厂模式的方法。


25958655_13995306095GY6.jpg

 

设计模式:策略模式Strategy(对象行为型)

架构思想zkbhj 发表了文章 • 0 个评论 • 1793 次浏览 • 2016-12-08 10:53 • 来自相关话题

1.概述

    在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

2.问题

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?


3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.结构





 
6.模式的组成
 
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。

7.效果

Strategy模式有下面的一些优点:

1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

1)出行旅游:
 
uml:





 
代码实现:
<?php
/**
* 策略模式
* 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
*
*/


/**
* 出行旅游
*
*
*/
interface TravelStrategy{
public function travelAlgorithm();
}


/**
* 具体策略类(ConcreteStrategy)1:乘坐飞机
*/
class AirPlanelStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by AirPlain", "<BR>\r\n";
}
}


/**
* 具体策略类(ConcreteStrategy)2:乘坐火车
*/
class TrainStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Train", "<BR>\r\n";
}
}

/**
* 具体策略类(ConcreteStrategy)3:骑自行车
*/
class BicycleStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Bicycle", "<BR>\r\n";
}
}



/**
*
* 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
* 算法解决类,以提供客户选择使用何种解决方案:
*/
class PersonContext{
private $_strategy = null;

public function __construct(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function setTravelStrategy(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function travel(){
return $this->_strategy ->travelAlgorithm();
}
}

// 乘坐火车旅行
$person = new PersonContext(new TrainStrategy());
$person->travel();

// 改骑自行车
$person->setTravelStrategy(new BicycleStrategy());
$person->travel();

?> 2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。
 
9.与其他相关模式

1)状态模式

策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在

对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。

•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

2)简单工厂的区别:点击打开链接

工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化

用我们上面提到旅行的例子:
我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。

上面的例子里面client代码:
$person = new PersonContext(new TrainStrategy());
$person->travel();
我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。

10.总结与分析

1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

原文地址:http://blog.csdn.net/hguisu/ar ... 8249/ 查看全部
1.概述

    在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

2.问题

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?


3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.结构

1336732187_4598.jpg

 
6.模式的组成
 
  • 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
  • 抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
  • 具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。


7.效果

Strategy模式有下面的一些优点:

1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

1)出行旅游:
 
uml:

1336733743_7225.jpg

 
代码实现:
<?php  
/**
* 策略模式
* 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
*
*/


/**
* 出行旅游
*
*
*/
interface TravelStrategy{
public function travelAlgorithm();
}


/**
* 具体策略类(ConcreteStrategy)1:乘坐飞机
*/
class AirPlanelStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by AirPlain", "<BR>\r\n";
}
}


/**
* 具体策略类(ConcreteStrategy)2:乘坐火车
*/
class TrainStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Train", "<BR>\r\n";
}
}

/**
* 具体策略类(ConcreteStrategy)3:骑自行车
*/
class BicycleStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Bicycle", "<BR>\r\n";
}
}



/**
*
* 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
* 算法解决类,以提供客户选择使用何种解决方案:
*/
class PersonContext{
private $_strategy = null;

public function __construct(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function setTravelStrategy(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function travel(){
return $this->_strategy ->travelAlgorithm();
}
}

// 乘坐火车旅行
$person = new PersonContext(new TrainStrategy());
$person->travel();

// 改骑自行车
$person->setTravelStrategy(new BicycleStrategy());
$person->travel();

?>
2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。
 
9.与其他相关模式

1)状态模式

策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在

对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。

•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

2)简单工厂的区别:点击打开链接

工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化

用我们上面提到旅行的例子:
我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。

上面的例子里面client代码:
$person = new PersonContext(new TrainStrategy());
$person->travel();
我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。

10.总结与分析

1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

原文地址:http://blog.csdn.net/hguisu/ar ... 8249/

设计模式之策略者模式(组合模式)

架构思想zkbhj 发表了文章 • 0 个评论 • 3005 次浏览 • 2019-08-29 11:20 • 来自相关话题

设计一款模拟鸭子的游戏,游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫。所用的鸭子会呱呱叫,也会游泳,所以基类负责实现。但是每一款鸭子外观不一样,所以这是抽象行为。由子类实现。

前期设计及思路 
根据情景的设计,首先我们想到这是一些列的鸭子,便会想到利用继承的手段,进行解决。其UML图型为: 





 
后续设计:需要让有的鸭子飞,有的鸭子不能飞。

后续设计 
问题: 
1、在调用中是通过抽象类进行操作的,所以新功能在超类有所体现, 
但是下载超类中实现,就所有的鸭子会飞。 
2、在超类中是飞的行为是抽象函数,这样每个子类都要实现,代码重写太复杂。 
3、用接口,只让飞的鸭子继承实现飞的行为接口,这样在超类中进行初始化接口。 
4、但是用接口后,让飞的行为进行修改,那个每个子类中飞的行为也要修改,这样改动也是很大。 
经过分析,利用组合的原则,将变化部分进行提取,封装一个新类,所以我们见fly这个行为提取出来,行为上肯定是接口,同时这个接口由行为类实现,而不是具体鸭子类实现,这样就实现策略模式。 
这里的接口就是所谓的概念,针对“利用接口编程”,关键在于多态。利用多态,程序可以对超类进行操作,根据实际情况执行到真正行为,将行为和对象进行解耦。 
在程序运行时就是针对超类编程,超类编程就是接口和抽象类。 

图为:





 
所以这样针对新行为,我们重新定义个接口,然后实现这个接口,在调用时初始化接口。 
其中主要的代码为:
//关键这是超类,必定是抽象类
public abstract class Duck
{
protected IFlyBehavior flyBehavior;

protected QuackBehavior quackBehavior;

public virtual void Quack()
{
throw new System.NotImplementedException();
}
//当然这些行为也可通过接口,但是这游泳鸭子都会,所以超类直接实现
public virtual void Swim()
{
throw new System.NotImplementedException();
}
//抽象函数,子类必须实现,意味子类这个行为不同
public abstract void DisPlay();

public virtual void performFly()
{
//通过接口执行fly行为,要寻找接口在那初始化
flyBehavior.fly();
}

}
//duck的子类,其中初始化了接口
public class BlueDuck : Duck
{
public BlueDuck()
{
//初始化接口,在用BlueDuck初始化Duck时,接口也被初始化了。
flyBehavior = new FlyWithWings();
}
public override void DisPlay()
{
throw new System.NotImplementedException();
}
}当你将两个类结合起来使用时,如同本例,这就是组合。这种做法的不同是在于行为不是继承而来,单独通过行为类和接口进行实现。 
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,使算法的变化独立于使用算法的客户。
 
原文地址:https://www.cnblogs.com/polly333/p/4705682.html 查看全部
设计一款模拟鸭子的游戏,游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫。所用的鸭子会呱呱叫,也会游泳,所以基类负责实现。但是每一款鸭子外观不一样,所以这是抽象行为。由子类实现。

前期设计及思路 
根据情景的设计,首先我们想到这是一些列的鸭子,便会想到利用继承的手段,进行解决。其UML图型为: 

QQ截图20190829111702.jpg

 
后续设计:需要让有的鸭子飞,有的鸭子不能飞。

后续设计 
问题: 
1、在调用中是通过抽象类进行操作的,所以新功能在超类有所体现, 
但是下载超类中实现,就所有的鸭子会飞。 
2、在超类中是飞的行为是抽象函数,这样每个子类都要实现,代码重写太复杂。 
3、用接口,只让飞的鸭子继承实现飞的行为接口,这样在超类中进行初始化接口。 
4、但是用接口后,让飞的行为进行修改,那个每个子类中飞的行为也要修改,这样改动也是很大。 
经过分析,利用组合的原则,将变化部分进行提取,封装一个新类,所以我们见fly这个行为提取出来,行为上肯定是接口,同时这个接口由行为类实现,而不是具体鸭子类实现,这样就实现策略模式。 
这里的接口就是所谓的概念,针对“利用接口编程”,关键在于多态。利用多态,程序可以对超类进行操作,根据实际情况执行到真正行为,将行为和对象进行解耦。 
在程序运行时就是针对超类编程,超类编程就是接口和抽象类。 

图为:

QQ截图20190829111747.jpg

 
所以这样针对新行为,我们重新定义个接口,然后实现这个接口,在调用时初始化接口。 
其中主要的代码为:
//关键这是超类,必定是抽象类
public abstract class Duck
{
protected IFlyBehavior flyBehavior;

protected QuackBehavior quackBehavior;

public virtual void Quack()
{
throw new System.NotImplementedException();
}
//当然这些行为也可通过接口,但是这游泳鸭子都会,所以超类直接实现
public virtual void Swim()
{
throw new System.NotImplementedException();
}
//抽象函数,子类必须实现,意味子类这个行为不同
public abstract void DisPlay();

public virtual void performFly()
{
//通过接口执行fly行为,要寻找接口在那初始化
flyBehavior.fly();
}

}
//duck的子类,其中初始化了接口
public class BlueDuck : Duck
{
public BlueDuck()
{
//初始化接口,在用BlueDuck初始化Duck时,接口也被初始化了。
flyBehavior = new FlyWithWings();
}
public override void DisPlay()
{
throw new System.NotImplementedException();
}
}
当你将两个类结合起来使用时,如同本例,这就是组合。这种做法的不同是在于行为不是继承而来,单独通过行为类和接口进行实现。 
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,使算法的变化独立于使用算法的客户。
 
原文地址:https://www.cnblogs.com/polly333/p/4705682.html

设计模式:面向对象设计模式(OOP)

架构思想zkbhj 发表了文章 • 0 个评论 • 2771 次浏览 • 2018-03-01 10:26 • 来自相关话题

首先我们来看下官方的定义。在维基百科上说:

  面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象

 
要理解什么是面向对象,我们就先要知道什么是面向过程。

面向过程:就是将要实现一个功能所需要的步骤一步一步的写出来出来,要做到面面俱到、有条不絮。

 
面向对象设计的三个基本要素

面向对象的三个基本特征是:封装、继承、多态。


1·封装性

封装性是一种信息隐蔽技术,他体现于类的说明,是对象重要的特性。封装使得数据和操作数据的方法封装为一个整体,想成独立性很强的模块,使得用户只能看到对象的外部特性(对象可以接受拿些信息,可以进行何种处理),而对象的内部特性(内部私有属性和实现处理能力的算法)用户是看不到的。简而言之就是说,封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,无需知道具体是怎么实现的。借助封装有助于提高类和系统的安全性。

2·继承性

继承是一种由已有类创建子类的机制,利用继承,可以先创建一个共有属性的一般类,根据这个类再创建具有特殊属性的子类,被继承的类成为父类,当然子类也可以成为父类来继续向下扩展。

3·多态性

同一个信息被不同的对象接收到时可能产生不同的行为,这就是多态性。有继承(接口)有重写,父类引用指向子类对象,就会产生多态。多态可以改善程序的组织架构,提高程序的可扩展性。

面向对象设计的五个基本设计原则

单一职责原则(SRP)、开放封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)


1·单一职责原则(Single-Responsibility Principle)

其核心思想为:一个类只做一件事情,只有一个引起他的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则上的隐身,将职责定义为引起变化的原因,以提高内举行来减少引起变化的原因。职责过多可能引起他变化的原因也就越多,这将导致职责依赖,相互之间产生影响,从而大大损伤内聚性和耦合度。单一职责就是指,只有一种单一的功能,不要为类实现过多的功能点,有些功能可以定义为接口来实现,以保证只有一个引起他变化的原因。
专注是一个人优良的品质。同样的,单一也是一个类的优良设计,杂交不清的职责将使得代码看起来特别别扭,牵一发动全身,有失没敢和必然导致丑陋的系统错误风险。

2·开放封闭原则(Open-Closeed Principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭。开放封闭原则主要体现在两个方面
1、对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开放封闭原则的核心思想就是对抽象编程,而不是具体编程,因为抽象相对稳定。让类依赖于固定的抽象类或者接口,所以修改就是封闭的。而通多面向对象的继承和多态机制,又可以继承抽象类或者实现接口,通过重写其方法来改变固有的行为,实现方法新的拓展,所以就是开放的。
需求总是变化的,没有不变的软件,所以就需要用OCP来封闭变化,满足需求,同时还能保持软件内部的封装体系的稳定,不被需求的变化影响。

3·里氏替换原则(Liskov-Substituion Principle)

核心思想:子类必须能够替换其父类。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将父类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换父类,但是父类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

4·依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是不变的。依赖于抽象,就是对接口编程,不要对实现编程。

5·接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。 查看全部
首先我们来看下官方的定义。在维基百科上说:


  面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象


 
要理解什么是面向对象,我们就先要知道什么是面向过程。


面向过程:就是将要实现一个功能所需要的步骤一步一步的写出来出来,要做到面面俱到、有条不絮。


 
面向对象设计的三个基本要素


面向对象的三个基本特征是:封装、继承、多态。



1·封装性

封装性是一种信息隐蔽技术,他体现于类的说明,是对象重要的特性。封装使得数据和操作数据的方法封装为一个整体,想成独立性很强的模块,使得用户只能看到对象的外部特性(对象可以接受拿些信息,可以进行何种处理),而对象的内部特性(内部私有属性和实现处理能力的算法)用户是看不到的。简而言之就是说,封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,无需知道具体是怎么实现的。借助封装有助于提高类和系统的安全性。

2·继承性

继承是一种由已有类创建子类的机制,利用继承,可以先创建一个共有属性的一般类,根据这个类再创建具有特殊属性的子类,被继承的类成为父类,当然子类也可以成为父类来继续向下扩展。

3·多态性

同一个信息被不同的对象接收到时可能产生不同的行为,这就是多态性。有继承(接口)有重写,父类引用指向子类对象,就会产生多态。多态可以改善程序的组织架构,提高程序的可扩展性。

面向对象设计的五个基本设计原则


单一职责原则(SRP)、开放封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)



1·单一职责原则(Single-Responsibility Principle)

其核心思想为:一个类只做一件事情,只有一个引起他的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则上的隐身,将职责定义为引起变化的原因,以提高内举行来减少引起变化的原因。职责过多可能引起他变化的原因也就越多,这将导致职责依赖,相互之间产生影响,从而大大损伤内聚性和耦合度。单一职责就是指,只有一种单一的功能,不要为类实现过多的功能点,有些功能可以定义为接口来实现,以保证只有一个引起他变化的原因。
专注是一个人优良的品质。同样的,单一也是一个类的优良设计,杂交不清的职责将使得代码看起来特别别扭,牵一发动全身,有失没敢和必然导致丑陋的系统错误风险。

2·开放封闭原则(Open-Closeed Principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭。开放封闭原则主要体现在两个方面
1、对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开放封闭原则的核心思想就是对抽象编程,而不是具体编程,因为抽象相对稳定。让类依赖于固定的抽象类或者接口,所以修改就是封闭的。而通多面向对象的继承和多态机制,又可以继承抽象类或者实现接口,通过重写其方法来改变固有的行为,实现方法新的拓展,所以就是开放的。
需求总是变化的,没有不变的软件,所以就需要用OCP来封闭变化,满足需求,同时还能保持软件内部的封装体系的稳定,不被需求的变化影响。

3·里氏替换原则(Liskov-Substituion Principle)

核心思想:子类必须能够替换其父类。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将父类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换父类,但是父类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

4·依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是不变的。依赖于抽象,就是对接口编程,不要对实现编程。

5·接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。

PHP设计模式之:适配器模式

架构思想zkbhj 发表了文章 • 0 个评论 • 2232 次浏览 • 2018-01-29 11:15 • 来自相关话题

在这个有没有对象都要高呼“面向对象”的年代,掌握面向对象会给我们带来意想不到的方便。学编程的小伙伴从开始能写几行代码实现简单功能到后来懂得将一些重复的操作组合起来形成一个“函数”,再到后来将“函数”和属性组合起来形成一个“类”。一步步走来,我们在考虑着机器运行代码效率的提高的同时也在考虑减轻程序员的工作量。 那么我们今天讲到的适配器模型更着重考虑的是什么呢?

是程序员工作量。

 




什么时候会用到适配器模式?

其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码。 

难道真的一定要如此吗?按照套路来说,我会回答“不是的”。我们有适配器模式啊~~  

当接口发生改变时,适配器模式就派上了用场。

举个栗子

如果通过上面的简单描述,你都能懂,那在下只能佩服你的领悟能力超群了。一般人一定还是不知所云。为了方便理解,我引用一位博友的例子。原文地址。

一开始的和谐


黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

    为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。(其实我经常当这样的傻子)abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。搁谁都不淡定了。abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了“开-闭”原则,一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那么总得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。  适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。 

到此,解决方法已经呼之欲出了,下面贴上代码。<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}


//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}



class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testDriver();
$test->run();
 最后的结果就是,Toy类及其子类在不改变自身的情况下,通过适配器实现了不同的接口。

最后总结

将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作.

适配器模式核心思想:把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

      以上 查看全部
在这个有没有对象都要高呼“面向对象”的年代,掌握面向对象会给我们带来意想不到的方便。学编程的小伙伴从开始能写几行代码实现简单功能到后来懂得将一些重复的操作组合起来形成一个“函数”,再到后来将“函数”和属性组合起来形成一个“类”。一步步走来,我们在考虑着机器运行代码效率的提高的同时也在考虑减轻程序员的工作量。 那么我们今天讲到的适配器模型更着重考虑的是什么呢?


是程序员工作量。


 
timg_(4).jpg


什么时候会用到适配器模式?

其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码。 

难道真的一定要如此吗?按照套路来说,我会回答“不是的”。我们有适配器模式啊~~  

当接口发生改变时,适配器模式就派上了用场。

举个栗子

如果通过上面的简单描述,你都能懂,那在下只能佩服你的领悟能力超群了。一般人一定还是不知所云。为了方便理解,我引用一位博友的例子。原文地址

一开始的和谐


黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

    为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。(其实我经常当这样的傻子)
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}
更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。搁谁都不淡定了。
abstract class Toy  
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}
在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了“开-闭”原则一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那么总得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。  适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。 

到此,解决方法已经呼之欲出了,下面贴上代码。
<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}


//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}



class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testDriver();
$test->run();

 最后的结果就是,Toy类及其子类在不改变自身的情况下,通过适配器实现了不同的接口。

最后总结

将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作.

适配器模式核心思想:把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

      以上

设计模式:工厂模式

专业名词zkbhj 发表了文章 • 0 个评论 • 2139 次浏览 • 2017-11-09 20:15 • 来自相关话题

工厂模式分为简单工厂、工厂方法模式和抽象工厂模式。
 
简单工厂模式(Simple Factory)
 
顾名思义,这个模式本身很简单,而且使用在业务较简单的情况下。
它由三种角色组成:
工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不通,产生具体的工厂产品。如例子中的Driver类。抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。如例中的Car接口。具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。
 
来用类图来清晰的表示下的它们之间的关系:











优点:客户端不需要修改代码。
缺点: 当需要增加新的运算类的时候,不仅需新加运算类,还要修改工厂类,违反了开闭原则。

工厂方法模式​
 
抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
 UML类图如下:





 
这个和简单工厂有区别,简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂

好处:增加一个运算类(例如N次方类),只需要增加运算类和相对应的工厂,两个类,不需要修改工厂类。

缺点:增加运算类,会修改客户端代码,工厂方法只是把简单工厂的内部逻辑判断移到了客户端进行。
 
抽象工厂模式:
 UML类图如下:





 
    从图上可以看出这和工厂方法模式很相似,但是呢,有几个区别:
抽象工厂模式,一个具体工厂可以制造几个产品,例如微软工厂(相当于SqlserverFactory)可以制造微软鼠标(属于鼠标类,鼠标类下面有惠普鼠标,微软鼠标等,相当于上图中的IDepartment下面的SqlserverDepartment和AccessDepartment),也可以制造微软键盘(属于键盘类,键盘类下面有惠普键盘,微软键盘等,相当于上图中的IUser下面的SqlserverUser和AccessUser)。
引用http://blog.csdn.net/wangwenhu ... 55125中的话:

工厂方法模式:一个抽象产品类,可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
              每个具体工厂类只能创建一个具体产品类的实例。 
抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
             每个具体工厂类可以创建多个具体产品类的实例。 
区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

抽像工厂优缺:
优点:易于交换产品系列,例如Access和SQLServer数据库之间切换。
缺点:例如增加一个机箱产品,不仅需要添加三个类“机箱类,微软机箱,惠普机箱”,还要修改惠普工厂,微软工厂支持制造机箱。而添加一个联想工厂的时候,只需要添加三个类,使用联想工厂还是要修改客户端代码的。

在《大话设计模式》中,提出用简单工厂模式改进抽象工厂模式的方法。






  查看全部
工厂模式分为简单工厂、工厂方法模式和抽象工厂模式。
 
简单工厂模式(Simple Factory)
 
顾名思义,这个模式本身很简单,而且使用在业务较简单的情况下。
它由三种角色组成:
  • 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不通,产生具体的工厂产品。如例子中的Driver类。
  • 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。如例中的Car接口。
  • 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。

 
来用类图来清晰的表示下的它们之间的关系:
r_简单工厂.jpg



25958655_1399530326s3Z6.jpg


优点:客户端不需要修改代码。
缺点: 当需要增加新的运算类的时候,不仅需新加运算类,还要修改工厂类,违反了开闭原则。

工厂方法模式​
 
  1. 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

 UML类图如下:

25958655_1399530481VM7v.jpg

 
这个和简单工厂有区别,简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂

好处:增加一个运算类(例如N次方类),只需要增加运算类和相对应的工厂,两个类,不需要修改工厂类。

缺点:增加运算类,会修改客户端代码,工厂方法只是把简单工厂的内部逻辑判断移到了客户端进行。
 
抽象工厂模式:
 UML类图如下:

25958655_1399530523DmFr.jpg

 
    从图上可以看出这和工厂方法模式很相似,但是呢,有几个区别:
抽象工厂模式,一个具体工厂可以制造几个产品,例如微软工厂(相当于SqlserverFactory)可以制造微软鼠标(属于鼠标类,鼠标类下面有惠普鼠标,微软鼠标等,相当于上图中的IDepartment下面的SqlserverDepartment和AccessDepartment),也可以制造微软键盘(属于键盘类,键盘类下面有惠普键盘,微软键盘等,相当于上图中的IUser下面的SqlserverUser和AccessUser)。
引用http://blog.csdn.net/wangwenhu ... 55125中的话:

工厂方法模式:一个抽象产品类,可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
              每个具体工厂类只能创建一个具体产品类的实例。 
抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 
              一个抽象工厂类,可以派生出多个具体工厂类。 
             每个具体工厂类可以创建多个具体产品类的实例。 
区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

抽像工厂优缺:
优点:易于交换产品系列,例如Access和SQLServer数据库之间切换。
缺点:例如增加一个机箱产品,不仅需要添加三个类“机箱类,微软机箱,惠普机箱”,还要修改惠普工厂,微软工厂支持制造机箱。而添加一个联想工厂的时候,只需要添加三个类,使用联想工厂还是要修改客户端代码的。

在《大话设计模式》中,提出用简单工厂模式改进抽象工厂模式的方法。


25958655_13995306095GY6.jpg

 

设计模式:策略模式Strategy(对象行为型)

架构思想zkbhj 发表了文章 • 0 个评论 • 1793 次浏览 • 2016-12-08 10:53 • 来自相关话题

1.概述

    在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

2.问题

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?


3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.结构





 
6.模式的组成
 
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。

7.效果

Strategy模式有下面的一些优点:

1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

1)出行旅游:
 
uml:





 
代码实现:
<?php
/**
* 策略模式
* 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
*
*/


/**
* 出行旅游
*
*
*/
interface TravelStrategy{
public function travelAlgorithm();
}


/**
* 具体策略类(ConcreteStrategy)1:乘坐飞机
*/
class AirPlanelStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by AirPlain", "<BR>\r\n";
}
}


/**
* 具体策略类(ConcreteStrategy)2:乘坐火车
*/
class TrainStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Train", "<BR>\r\n";
}
}

/**
* 具体策略类(ConcreteStrategy)3:骑自行车
*/
class BicycleStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Bicycle", "<BR>\r\n";
}
}



/**
*
* 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
* 算法解决类,以提供客户选择使用何种解决方案:
*/
class PersonContext{
private $_strategy = null;

public function __construct(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function setTravelStrategy(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function travel(){
return $this->_strategy ->travelAlgorithm();
}
}

// 乘坐火车旅行
$person = new PersonContext(new TrainStrategy());
$person->travel();

// 改骑自行车
$person->setTravelStrategy(new BicycleStrategy());
$person->travel();

?> 2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。
 
9.与其他相关模式

1)状态模式

策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在

对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。

•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

2)简单工厂的区别:点击打开链接

工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化

用我们上面提到旅行的例子:
我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。

上面的例子里面client代码:
$person = new PersonContext(new TrainStrategy());
$person->travel();
我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。

10.总结与分析

1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

原文地址:http://blog.csdn.net/hguisu/ar ... 8249/ 查看全部
1.概述

    在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

2.问题

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?


3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.结构

1336732187_4598.jpg

 
6.模式的组成
 
  • 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
  • 抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
  • 具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。


7.效果

Strategy模式有下面的一些优点:

1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

1)出行旅游:
 
uml:

1336733743_7225.jpg

 
代码实现:
<?php  
/**
* 策略模式
* 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
*
*/


/**
* 出行旅游
*
*
*/
interface TravelStrategy{
public function travelAlgorithm();
}


/**
* 具体策略类(ConcreteStrategy)1:乘坐飞机
*/
class AirPlanelStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by AirPlain", "<BR>\r\n";
}
}


/**
* 具体策略类(ConcreteStrategy)2:乘坐火车
*/
class TrainStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Train", "<BR>\r\n";
}
}

/**
* 具体策略类(ConcreteStrategy)3:骑自行车
*/
class BicycleStrategy implements TravelStrategy {
public function travelAlgorithm(){
echo "travel by Bicycle", "<BR>\r\n";
}
}



/**
*
* 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
* 算法解决类,以提供客户选择使用何种解决方案:
*/
class PersonContext{
private $_strategy = null;

public function __construct(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function setTravelStrategy(TravelStrategy $travel){
$this->_strategy = $travel;
}
/**
* 旅行
*/
public function travel(){
return $this->_strategy ->travelAlgorithm();
}
}

// 乘坐火车旅行
$person = new PersonContext(new TrainStrategy());
$person->travel();

// 改骑自行车
$person->setTravelStrategy(new BicycleStrategy());
$person->travel();

?>
2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。
 
9.与其他相关模式

1)状态模式

策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在

对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。

•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

2)简单工厂的区别:点击打开链接

工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化

用我们上面提到旅行的例子:
我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。

上面的例子里面client代码:
$person = new PersonContext(new TrainStrategy());
$person->travel();
我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。

10.总结与分析

1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

原文地址:http://blog.csdn.net/hguisu/ar ... 8249/