让设计不再成为负担-设计模式详解【2008年11月19日迭代器模式】
[size=5][color=red]写在前面的话[/color][/size][size=5][color=#ff0000] [color=olive]首先,鄙人的DZ分析贴其实一直是心里一件大事。。前几天辞职。一怒之下,把电脑上所有东西低格。。。昨天才装好系统:sweat: 。网络一直每装好。刚刚才弄好,现在在配置php环境和下总总软件手册之类的。反正坐着也是坐着。打算在家休息两个月,完成DZ论坛分析计划,设计模式学习计划。ZF学习计划。然后是出去找工作:sweat: 。。。貌似php最新的版本要出来了。很期待迟绑定的支持。因为鄙人比较倾向OO的设计实现。因为鄙人一直没空间。只能来这里发发帖写写自己的心得。。。再啰嗦一句。。终于没有阻碍的琐事打搅我了~~~认真学习,天天向上:lol:[/color][/color][/size]
[size=5][color=#ff0000][color=olive]顺便打个广告:[/color][/color][/size]
[quote]
[url=http://www.joinphp.cn/sampeng][attach]26883[/attach][/url]SamPeng's blog。将开源精神进行到底
[/quote]
这几天消失就是搞他去了。。。喜欢鄙人这些还没完成的教程的朋友,欢迎常来坐坐。加入收藏和邮件订阅是最好的选择
[size=5][color=#808000] [color=black]废话不多说了。这次分享的是设计模式。。其实在学java的时候碰过。但没时间。就放下了。现在继续拿起来学习发。看明白了还是很简单的,对程序的编写,风格规范化都是很有帮助的。[/color][/color][/size]
[size=5] 一楼一个设计模型。按照《深入浅出 设计模式》目录结构写的。因为是java的设计模式。。。例子都是自己写的。。。自己还只学4个。。。。一边学一边写把。[/size]
[size=5] [color=yellowgreen]从第二章开始,我会留1-2道思考题。希望观看的人不要只扫一眼就不看了。一起思考把。[/color][/size]
[size=5] [color=red]有任何纰漏和理解错误,请Boss告诉我。。不求加精。。。。等我完成一半再加精。[/color][/size]
[size=5][quote][/size]
[size=5]为什么要用设计模式?[/size]
[size=4] 面向对象有很多设计原则。。。恩。网友ZendFramework在回帖里要求跟大家说为什么要用。和详细讲解设计原则。好吧。我转一堆设计原则过来。。。[/size]
[size=4]
[size=3][color=red]“开—闭”原则
面向对象设计的基石是“开—闭”原则。
“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。
这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。
从另外一个角度讲,就是所谓的“对可变性封装原则”。
“对可变性封装原则”意味着两点:
1 .一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。
2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。
做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具。
里氏代换原则
里氏代换原则:
即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。
如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种:
1 创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。
2 从B到A的继承关系改写为委派关系。
咋一看觉得这个怎么还是面向对象设计的原则呢?这个明明就是Java的语法规则。对,Java是提供了对里氏代换原则在语法上的支持。但是仅仅是语法上,在和现实世界的相符合程度上根本没有提供。所有常常会有不符合里氏代换原则的情况出现。
依赖倒转原则
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。
此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。
接口隔离原则
接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
合成、聚合复用原则
合成、聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。这个原则有一个简短的描述:要尽量使用合成、聚合,尽量不要使用继承。
合成、聚合有如下好处:
新对象存取成分对象的唯一方法是通过成分对象的接口。
这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不到的。
这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。
合成、聚合可以应用到任何环境中去,而继承只能应用到一些有限环境中去。
导致错误的使用合成、聚合与继承的一个常见原因是错误的把“Has-a”关系当作“Is-a”关系。如果两个类是“Has-a”关系那么应使用合成、聚合,如果是“Is-a”关系那么可使用继承。
迪米特法则
迪米特法则说的是一个对象应该对其它对象有尽可能少的了解。即只与你直接的朋友通信,不要跟陌生人说话。如果需要和陌生人通话,而你的朋友与陌生人是朋友,那么可以将你对陌生人的调用由你的朋友转发,使得某人只知道朋友,不知道陌生人。换言之,某人会认为他所调用的是朋友的方法。
以下条件称为朋友的条件:
当前对象本身。以参量的形式传入到当前对象方法中的对象。当前对象的实例变量直接引用的对象。当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友。
当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:
在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。
在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。在类的设计上,只要有可能,一个类应当设计成不变类。在对其它对象的引用上,一个类对其它对象的引用应该降到最低。[/color][/size]
[color=black][/color]
谁能够完全看明白呢?今天思考了很久,觉得设计模式应该不是为使用设计原则而出现的。他是一系列的经验积累而来的原则综合体。
就像我在第一章的策略模式中的例子,在设计团队中,你是觉得说上一堆的设计原则更容易沟通还是就一句话:我们用策略模式来定义这些工资发放形式吧。两者比起来,后者是不是更容易理解呢?你的伙伴马上就能明白:恩,我知道了。将算法整体的封装起来。这样达到对修改开放对无变化的地方闭合的效果。过程就是面向抽象而不是实现了。
下面几点是我总结出来为什么要学设计模式:
[color=orange] 1、你更容易理解设计原则。记住,设计模式不是万能的。总有你找不到匹配的设计模式。万一这样怎么办?进入大脑的设计模式就其作用了。无论你是否在利用设计模式做设计。你脑袋里始终想的不应该是复合设计模式,而是符合设计原则。[/color]
[color=orange] 2、模式可以让你的代码更具有OO良好设计质量。[/color]
[color=orange] 3、模式是经验,不是去发明,而是发现。模式就像楼房的图纸。而不需要考虑如何实现他。是不是经常钻进如何实现某个效果而苦恼,反而和其他的程序整合出了纰漏。不要针对实现编程,要针对抽象做设计。这样更容易让自己感觉是在设计程序,而不是高价的打字员。[/color]
[color=orange] 4、代码更容易维护。大部分设计模式允许系统局部独立于系统其他部分。[/color]
[color=orange] 5、习惯封装。把变化部分拿出来封装之![/color]
[color=orange] 6、和团队有了更多的共同语言。一句话顶千言[/color]
[color=orange] 7、切记:模式不是代码,而是解决实际问题的方案[/color]
[/size][size=5][/quote] [/size]
[size=5][/size]
[size=5]我也学深入浅出设计模式书中一样,每完成一部分就弄个工具箱:[/size]
[quote]
[size=4][color=red]看看我们现在有什么[/color][/size]
[size=5][b]OO基础:[/b][/size]
[size=5]继承,多态,封装,抽象。[/size]
[size=5][b]OO原则:[/b][/size]
[size=5]将变化封装[/size]
[size=5]多用组合,少用继承[/size]
[size=5]针对接口编程,不针对实现编程[/size]
[size=5]设计是为实现交互对象之间松耦合而努力[/size]
[size=5][color=black]开-闭原则:对扩展开放,对修改封闭[/color][/size]
[size=5]依赖导致原则:依赖抽象,不要依赖具体类[/size]
[size=5][b]OO模式[/b][/size]
[size=5]策略模式:定义算法族,分别封装起来,让他们之间可以互相替换。此模式让算法的变化独立于使用算法的用户[/size]
[size=5]观察者模式:定义了对象一对多的依赖。这样一来,对象的状态发生改变,所依赖的对象都会获取通知而更新[/size]
[size=5]装饰模式:动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更具又弹性的替代方案[/size]
[size=5]工厂方法模式:提供一个接口来创建产品。但是具体的对象实例化是在它的子类中完成。也就是工厂方法将实例化推辞到子类完成[/size]
[size=5]抽象工厂模式:提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类[/size]
[size=5]单例模式:确保一个类只有一个实例,并提供全局访问点。[/size]
[size=5]命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。[/size]
[size=5]迭代模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
[/size][/quote]
你也能做到!大家一起加油吧
[quote]所有程序建立在php5的基础上,已经全部调试成功[/quote]
[size=5] 目录:[/size]
[quote]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1#pid684811][size=4][color=darkslateblue]第一章 封装你的算法:策略模式[/color][/size][/url][/size]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1#pid684815][size=4][color=navy]第二章 让你的对象知悉现状 观察者模式[/color][/size][/url][/size]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1#pid684816][size=4][color=blue]第三章 观察对象:观察者模式[/color][/size][/url][/size]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1#pid684818][size=4][color=darkslateblue]第四章 加工你的OO精华 工厂模式[/color][/size][/url][/size]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1#pid684820][size=4][color=darkslateblue]第五章 独一无二的对象 单例模式[/color][/size][/url][/size]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1&fromuid=11734#pid684823][size=4][color=darkslateblue]第六章 封装调用 命令模式[/color][/size][/url][/size]
[size=4][color=darkolivegreen]第七章 随遇而安 适配器和外观模式[/color][/size]
[size=4][color=darkolivegreen]第八章 封装算法 模板方法模式[/color][/size]
[size=4][url=http://bbs.phpchina.com/viewthread.php?tid=87905&page=1&fromuid=11734#pid684826][size=4][color=darkslateblue]第九章 管理良好的集合 迭代器和组合模式[/color][/size][/url][/size]
[size=4][color=darkolivegreen]第十章 事物的状态 状态模式[/color][/size]
[size=4][color=darkolivegreen]第十一章 控制对象的访问 代理模式[/color][/size]
[size=4][color=darkolivegreen]第十二 模式中的模式 复合模式[/color][/size]
[/quote]
[[i] 本帖最后由 某个人 于 2008-11-19 17:00 编辑 [/i]]
第一章 策略模式
注:UML软件还在下载,等会再补充所有类图为什么要用面向对象?我想最大的理由是OO可以让代码更加的具有可读性。也能让代码具有灵活性。继承一定是很多OO初学者和我一样都最喜欢用的代码复用方法吧。
你可能要说了,为什么不呢?父类里面代码已经写好了。我继承后就能照用了。啊哈,继承从技术水平上说本身是没错的。但是相当于把功能方法硬编码进了程序里面。一旦需要修改就要打开源代码修改源代码。。
你觉得在写程序的时间最多的部分是什么了?是前期编写还是后期维护?对的,就是后期维护。但项目过大的时候,后期无尽的添加修改功能能把一人逼疯。我也经常那样。于是。。我拿起了OO。用上了 OO的众多优秀特点。可还是不可避免的导致后期维护一塌糊涂。
这都是说在前面的话。可能你又要说了,这光我什么事。我有足够的能力和经历去维护代码库和框架。
其实我更想说的,在OO中,尤其是设计模型理念你,最需要的不是你又多大的能力去编写代码。而是思维。用<深入浅出 设计模式>中的话来说就是,先洗脑,再想事情。
而设计模式中最主要最主要最主要最主要的思维方式就是:不要针对实现编码,要针对抽象编码。有些人能明白,有些人又不能明白。我想,进入设计模式大门后,你一定能够深切理解这句话。
我们以一个例子来进入第一章:策略模式
我们在外打工,最基本的是什么?工资!没工资我可活不下去。ok,现在有一个客户需要你给他用面向对象的方式写一个工资发放的系统出来。
你会怎么做?先分析需要哪几个类。
首先,所有人在公司里面都属于老板下的职员。ok,抽象类诞生abstract Personnel class。为什么是抽象的类,而不是实际的类?先想想哈,仔细想想你一定能明白的。(提示,抽象类的定义)
职员又分两种:雇员和经理。又出来两个类,很好.class Mannage和class Employee。
所有的职员都几个行为:发工资,涨工资,改变基础工资.减工资
如果是你,会如何编写这个类?只是说这个类的结构。
会不会是这样?(类图待补充。。)
[quote]
abstract Personnel class{
protected 很多属性//用protected是为了安全。要养成习惯
public function getSalary()//发工资了
public function addSalary()//天啊。涨工资了
public function basicSalary()//基本工资也发生了改变
public function decreaseSalary()//fuck,既然减我工资。我不干了
}
[/quote]
只写个抽象类。其他的子类的方法就是覆盖这些,然后重写,对吗?
这样看其来没什么问题。但是,如果我一个公司又几万人,有N种分工的工种,当然,还是要从这里继承下去。可是,难道每修改一个工种的发工资的方法就得达开源程序去添加或者修改吗?如果有几个员工是一样的呢工资呢?copy?这些方法确实能够解决问题。但是,效率呢?
我们在程序中最常打交道的是什么?Change!改变。程序是为了运行时的改变而改变,是为了将来某些不定的时间内改变而改变!
请记住这些原则:将程序中改变的部分剥离出来于不改变的部分分别开。恕小弟愚钝,我理解,这就是封装了。我丫的将改变的地方全封闭起来。其他的地方调用,只需要调用他就可以了。完全不需要知道他是如何操作的。这样听起来似乎很又意思。。
继续这个例子。。这几个方法都可能会改变。那就是他们了。剥离出来。放入一个OO常用的东西里面:接口的实现类。
注意,这里所说的放入接口实现类,并不是将所有的方法放入一个接口的实现来处理。这样就没灵活性的展示了。
而是将一个方法处理的形式交给一个接口的实现类。如下处理:
[php]
/**
* 涨工资的接口
*/
interface ChangeAddSalary{
public function addSalary($_basicsalary);
}
class MannageAddSalary implements ChangeAddSalary{
protected $basicsalary;
protected $addsalary;
public function __construct(){
$this->addsalary=500;
}
public function addSalary($_basicsalary){
$this->basicsalary = $_basicsalary;
$str = "涨工资".$this->addsalary."</br>";
return $str;
}
public function getNewSalary(){
return $this->addsalary;
}
}
class EmployeeAddSalary implements ChangeAddSalary {
protected $basicsalary;
protected $addsalary;
public function __construct(){
$this->addsalary = 200;
}
public function addSalary($_basicsalary){
$this->basicsalary = $_basicsalary;
$str = "涨工资".$this->addsalary."</br>";
return $str;
}
public function getNewSalary(){
return $this->addsalary;
}
}
[/php]
这个接口和实现非常简单。不需要多解释了哈。有疑问发论坛小刀给我。
这样一个接口和实现类就只实现一个功能,涨工资。我管你谁长,我只知道我这里我定义两个涨工资的方法。你自己调用就是了。外部调用也是一样。我只需要长工资。但不需要烦恼到底涨工资的人是什么职位。为什么呢?因为他会运行时的绑定。php5的迟绑定没有支持。多态只能是抽象意义的多态。因为他是个弱语言多态。
在抽象类里面这样处理:
[php]
abstract class Personnel{
protected $basicsalary;
protected $name;
protected $addsalary;
protected $decsalary;
protected $salary;
//上面一系列属性。。不多解释。
abstract public function getSalary();//得有个外部接口获取工资
//注意重点,参数ChangeAddSalary $_changeaddsalary.ChangeAddSalary是接口类型。传入后的参数应该是这个ChangeAddSalary类型的实现类
public function addSalary(ChangeAddSalary $_changeaddsalary){
echo $_changeaddsalary->addSalary($this->basicsalary);//这里调用各种形式的addSalary方法。不需要担心他会给雇员发经理水平的工资。传入参数的时候会指定的。他所调用的是如:MannageAddSalary和EmployeeAddSalary两个类里面的addSalary.
$this->addsalary = $_changeaddsalary->getNewSalary()+$this->addsalary;
}
public function basicSalary(ChangeBasicSalary $_changebasicsalary){
echo $_changebasicsalary->basicSalary();
$this->basicsalary = $_changebasicsalary->getNewSalary();
}
public function decreaseSalary(ChangeDecreaseSalary $_changedecresesalary){
echo $_changedecresesalary->decreaseSalary($this->basicsalary);
$this->decsalary = $_changedecresesalary->getNewSalary()+$this->decsalary;
}
//用一个方法来计算把。。偷懒了一下。。直接拼音对付
public function jisuan(){
$this->salary=$this->basicsalary+$this->addsalary-$this->decsalary;
}
}
[/php]
子类需要干点什么?这些都已经继承过来了。只需要初始化一些参数就可以了。比如基本工资这些。怎么涨工资。在具体的方法里面去修改。
[php]
class Mannage extends Personnel {
public $excuse;
public function __construct(){
$this->name = $_name;
$this->basicsalary =$this->salary =2000;
}
public function getSalary(){
$this->jisuan();
return $this->salary;
}
public function getName(){
return $this->name;
}
}
class Employee extends Personnel {
public function __construct(){
$this->basicsalary = 1000;
}
public function getSalary(){
$this->jisuan();
return $this->salary;
}
public function getName(){
return $this->name;
}
}
[/php]
记住。。上面的都是继承过来的。他们已经从父类那里继承了各种工资的方法。
关键在下面如何进行动态绑定:
我创建一个操作类。因为没办法,php的特性造成的。如果需要模仿多态只能这样。。。当然。。不模仿多态也行。因为php本身弱语言特性就决定了php本身就是多态的。只是这样更符合OO的多态特性。
[php]
class User{
//静态的方法方便调用。参数也是利用上抽象类Personnel 也是可以使用多态迟绑定的。你得告诉你创建的实力,者个实例到底是个什么类型
public static function addSalary(Personnel $_personnel){
$changeaddsalary;
if($_personnel instanceof Mannage){//(对类型判断一下。保证代码安全)
$changeaddsalary = new MannageAddSalary();//(创建实例。)
}elseif ($_personnel instanceof Employee){
$changeaddsalary = new EmployeeAddSalary();
}else{
echo "没有这个用户组";
}
$_personnel->addSalary($changeaddsalary);
}
public static function basicSalary(Personnel $_personnel){
$changebasicsalary;
if($_personnel instanceof Mannage){
$changebasicsalary = new MannageBasicSalary();
}elseif ($_personnel instanceof Employee){
$changebasicsalary = new EmployeeBasicSalary();
}else{
echo "没有这个用户组";
}
$_personnel->basicSalary($changebasicsalary);
}
public static function decreaseSalary(Personnel $_personnel){
$changedecreasesalary;
if($_personnel instanceof Mannage){
$changedecreasesalary = new MannageDecreaseSalary();
}elseif ($_personnel instanceof Employee){
$changedecreasesalary = new EmployeeDecreaseSalary();
}else{
echo "没有这个用户组";
}
$_personnel->decreaseSalary($changedecreasesalary);
}
}
[/php]
最后测试调用:
$a = new Employee();
User::addSalary($a);
User::decreaseSalary($a);
User::decreaseSalary($a);
echo $a->getSalary();
翻译一下:
a是个雇员。
老板给a涨了工资
老板减少了a工资
老板又减少了a工资
好把。看看a最后还剩多少工资
如果有其他的工资改变呢?发点奖金?涨工资不是按一次涨多少。而是需要按比例呢?不需要再打开该死的原类:Personnel了。直接去改接口文件内的定义吧。这些改变就留给各位把。
是不是把以后要改变的都剥离出来了呢?记住这一节的要点:将程序中改变的部分剥离出来于不改变的部分分别开!
现在回头来看看是不是在针对接口编程而不是实现编程了呢?慢慢和我一起理解和消化吧
本节完。。。。。
有理解错误的地方忘各位大神指出。谢谢
补充:
[color=red]非常感激ZendFramwork补充的遗漏问题:[/color]
[color=red][quote] [/color]
[color=red]设计模式的本质都是为了面向对象的那几个基本原则
首先你得让大家知道为什么会有这些设计模式.不能盲目的去学习.
最好先介绍一下 开闭原则 里矢代换原则 依赖倒转原则
比如策略模式和这些原则的关系:
我们依赖于抽象策略而不是具体策略 --依赖倒转原则
每一种具体的策略都能取代抽象策略的位置 --里矢代换原则
每当出现新的策略,直接添加,而不需要任何修改 --开闭原则[/color]
[color=red][/quote][/color]
[color=#ff0000]这几个原则明天我会在下一章重点阐述。。。下一章预告时间:明天晚上6点前[/color]
[[i] 本帖最后由 某个人 于 2008-11-2 20:43 编辑 [/i]]
第二章 让你的对象知悉现状 观察者模式
观察者模式在框架里应用是很多的。尤其是java的JDK.可以说是遍布观察者。js的监听事件,java的监听类。都是观察者的变体。甚至大家熟悉的MVC.也是个复杂的观察者模式.我们先脱离程序,用现实中的例子来说明什么是观察者模式
你订阅过报吗?过程如何?应该是这样的。
你向报社发出申请,我要订阅报纸。
报纸将你加入名单。每天只要他还没倒闭。他就继续给你提供报纸。
时间长了。有点烦这些整天报道喜讯的报纸。好吧,我取消报纸的订阅。
报社在名单你取消你的名额。管你是谁,只要他又新的新闻,继续提供给他名单里的成员。
OK!观察者的模式出来了。如果你能理解这个。你就理解了观察者模式!!!很简单不是吗?
订阅者+出版者=观察者模式
回到程序世界来,什么叫观察者模式?精确的定义应该是这样:
[color=red] 定义了对象一对多的依赖,一旦对象的状态发生改变,它的所依赖者都会接到通知自动更新[/color]
[color=#ff0000] 类图如下[/color]
[color=#ff0000] [attach]26403[/attach][/color]
上次给公司解决工资发放问题了。老大很高兴,后果很忙碌。。继续给你个程序来做。公司新闻现在需要从数据库中取出。然后将这些数据分别发送到手机,网页,或者BB机(如果能的话:lol: )。商务通也是个不错的选择。现在psp很强大,也可以选择发送到psp里面。
具体怎么实现不在这里考虑拉。只考虑思维。思维很重要。
你如何操作类?
会不会这样?
[php]
/**
*假设你已经做好各种收取平台的类了。
*用一个newsChange的方法来给个平台来刷新
*/
class NewsCenter{
//一系列的变量声明
public void newsChange(){
$news = getNews();//我假设你用这个方法获取新闻数据。并赋值给$news
//省略为这下面几个对象实例的过程。我假设你已经进过new处理了
$WebBrowser->update($news);
$Phone->update($news);
$BB->update($news);
}
//然后是其他的方法
}
[/php]
回想一下我在第一章和首楼上说的那几点概念和原则。
$WebBrowser这里是实例化过的-----是针对具体实现的编程。如果我们要增加上十余种的平台呢?打开源文件10余次。。删删改改。。很崩溃吧。
后面的update($news)这里倒是比较向是面向抽象编程。
ok .问题出现了。我以后添加修改或者删除发布平台怎么办?就像现在的blog。我想开发个程序。让这个平台扩展到任意的CMS或者任意的朋友blog上去显示。怎么办?难道添加几千几万条这样的代码。。。
好了。不卖官子了。现在我们已经找到了将会改变的部分。
[quote]
$WebBrowser->update($news);
$Phone->update($news);
$BB->update($news);
[/quote]
剩下的是拿出来封装。该怎么做呢?上面不是已经说了观察者模式的概念么?
用在这里怎么样?
新闻作为一个主题。当他有新的新闻发布的时候。由于各平台都是观察者。所以会自动的更新状态。
添加修改平台都不会和主题发生影响。这就叫做松耦合。
[size=4][color=red]设计原则之一:为了交互对象松耦合而努力。
[/color][/size] 何谓松耦合?我的理解就是彼此有依赖,但是不存在主要依赖。
用观察者模式来说,
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
任何时候我们都可以增加新的观察者。应为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。实际上,在运行时我们都可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。
在新类型的观察者出现时,主题的代码不需要任何的改变([color=red]what this?开闭原则。对改变开放,对不改变的地方封闭[/color])假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
如果我们在其他地方需要使用主题或者观察者,可以非常轻易的独立地重复复用。
改变主题或观察者其中一方,并不影响另一方。因为这两者是松耦合的
下面是我的实现。各位可以任意扩展。只是起个抛砖引玉的作用。
观察者接口:
[php]
interface Observer {
//所有的观察者都必须实现这个接口从而在主题状态改变的时候状态改变
public function update($_news);
}
[/php]
具体的观察者~~可以任意添加各种这样的观察者。。但是构造器那的注册你需要照搬了=。=可以又很多形式。。。我只是说明一下观察者。就简洁点了
不要鸡蛋里挑骨头=。=
[php]
interface DisPlayEL{
//只是用来显示状态的
public function display();
}
class WebBrowser implements Observer,DisPlayEL{
private $newscenter;
public $news;
public function __construct(Subject $_subject){
$this->newscenter = $_subject;
$this->newscenter->registerObserver($this);
}
public function update($_news){
$this->news= $_news;
$this->display();
}
public function display(){
echo "网页最新新闻:".$this->news."</br>";
}
}
class Phone implements Observer {
private $newscenter;
public $news;
public function __construct(Subject $_subject){
$this->newscenter = $_subject;
$this->newscenter->registerObserver($this);//注册观察者。其实可以有更好的方式。直接写成一个方法也未尝不可。大家可以自己试试
}
public function update($_news){
$this->news= $_news;
$this->display();
}
public function display(){
echo "<br>手机最新新闻:".$this->news."</br>";
}
}
[/php]
然后是主题类。他负责制造数据出来。然后给各个观察者。你可以理解成报社。。
[php]
class NewCenter implements Subject {
private $array;//用一个数组存储。是在构造函数里进行初始创造
private $news;
public function __construct(){
$this->array=array();
}
public function registerObserver(Observer $_o){
//我用一个数组来记录这个观察者的列表。其实还可以用数据库啊。文件啊。等等方式。
$this->array[]=$_o;
}
public function removeObserver(Observer $_o){
foreach ($this->array as $key => $value){
if($value === $_o)
{
unset($this->array[$key]);
echo "注销了一个观察者。下次他别想再看到我的新闻了";
}
}
}
/**
* 更新数据给观察者
*
*/
public function notifyObserver(){
foreach ($this->array as $key => $value){
$value->update($this->news);
}
}
public function newsChange(){
$this->notifyObserver();
}
//设置几个新闻测试数据来看看。其实你可以任意的数据获取方式。希望这点举一反三的能力还是得又
public function setNews($_news){
$this->news = $_news;
$this->newsChange();
}
}
[/php]
简单的测试一下
[php]
$a=new NewsCenter()//先实例一个对象。一个神诞生了。他能给我们新闻
$b = new WebBrowser($a);//把WebBrowser对象实例化并且注册给这个主题;
$c = new Phone($a);//同上
//神的指意开始了。。大家ORZ。。。
$a->setNews("天气很好,温度40度,请大家注意中暑");
[/php]
会显示什么呢?
[quote]
网页新闻:天气很好,温度40度,请大家注意中暑
手机新闻:天气很好,温度40度,请大家注意中暑
[/quote]
好。接下来在上面测试语句后面跟一句。我注销这个用户,然后再更新一次新闻。看看会得到什么。这才是重点啊。呵呵
[php]
$a->removeObserver($b);
$a->setNews("天气不好");
[/php]
会得到什么样的效果呢?
[quote]
网页新闻:天气很好,温度40度,请大家注意中暑
手机新闻:天气很好,温度40度,请大家注意中暑
注销了一个观察者。下次他别想再看到我的新闻了
手机最新新闻:天气不好
[/quote]
网页新闻就获取不到新闻了。
第二章就讲到这里。。。。事实上还可以实现很多功能。推和拉的功能。
[color=red]课后作业:1、大家自己试试,如何做到观察者已经知道主题已经更新,但观察者不想更新数据。也就是根据观察者的喜好来判断是否更新手头上的新[/color][color=red]闻资料。[/color]
[color=red]2、此模式复合哪几点设计原则[/color]
[color=#ff0000]明天中午来公布答案。希望我能在回帖里看到同学们的思考[/color]
[[i] 本帖最后由 某个人 于 2008-11-3 17:15 编辑 [/i]]
第三章 装饰对象:装饰模式
Oh,居然没有人看我的思考并做出相应回答。那既然这样,我何必要费心写出我的答案呢?设计原则中又一点我已经重复强调了。这里也给个精确的定义:
[color=red] 开-闭原则,对扩展开放,对修改封闭。[/color]
回顾一下上面的两章。策略模式中的策略接口下实现策略类,是不是就是一种扩展呢?你需要做的只是添加更好的策略方式。但是修改你很难做到
观察者模式是具有松耦合的。观察主题本身可能会需要修改。可能吗?一般不是特殊情况下的更改,不需要打开观察主题类。只需要对观察者进行扩展就可以了。这样是不是也满足了对扩展开放,对修改封闭呢?
我们的目标是允许类在不更改现有代码的情况下进行良好扩展。这样就可以搭载新的行为。兄弟,你花几天的时间写了个几百行的类。突然需要改变,又要翻进类里面查找bug解决问题。ok。这该死的都完成了。好把,这个类的门就给死死的关上吧。不允许自己或者其他人随意打开类来进行修改。这样的好处显而易见的。这样可以使类具有更大的灵活性,可以接受新的功能来应对新的改变需求。
从深入浅出中转几个常见问题:
这似乎看其来有点矛盾,但是确实又一技术和技巧可以允许在不直接修改代码的情况下对其进行扩展。
但请注意:在选择需要扩展的代码部分时要小心。每一个地方都采用开放-关闭原则,是一种浪费,也没有必要,还会导致代码变得复杂并且难以理解。
要知道那些地方需要改变。这个设计到OO系统的经验。多看一下其他的例子可以帮助班别设计中的变化区。因为设计模式是贯穿整个工程的生命周期的。你可以在任何时候使用设计模式和设计原则重构你的代码。这没设没什么好担心的
概念性的总结完毕。。。下面进入装饰模式的环节。
老板很欣赏你,你给他完成了发工资又解决了发新闻的问题。现在请你去吃饭。到了饭局。老板开口了。你看这每个菜都很复杂,又是放盐又是放油的。多点不行,少也也不行。因为成本都是通过这些来控制的。如果是机器完成的多好啊。你就来模拟一下机器完成炒肉这个成本的计算
听其来不错。下面有个比较错误的思维方式,类图说明
[attach]26465[/attach]
看其来不错。好像没什么问题。但肉只有这几种做法吗?中国饮食闻名于世,是因为简单的一个菜系能做出无数种方式。可能光一个肉能做出成百上千菜来。作料的不同,几百个菜种这是很有可能的。并且还又一个维护的危险,就是口味不同的人材料的量就会不同。再一个一个计算出价格。一个类一个类的去改变。很崩溃的把。
回顾一下上面说的,对扩展开放,对修改关闭。这里继承下来的类太多,就造成了类爆炸。维护也不方便
好了。让我们来了解一下装饰者模式是如何运作的。他能解决这些问题
首先,我们得知道厨师拿到菜了该如何去做成想要的菜。
1、获取一个菜的对象。比如说肉(A原料费)
2、用盐去装饰肉(B材料费)
3、用油装饰他(C材料费用)
4、用辣椒装饰他(D材料费用)
5、用炒菜装饰他。(E人工费用)
完成,青椒炒肉。想改变盐的分量。就在盐里面改变。要改变油量。就在油的装饰行为里改变。因为单价是相同的。
总价格=A+B+C+D+E。
再来看看他是如何做到的。
先,我们先获取一个对象
(画图很费时间。用括号代表了)
肉(描述,A原料费)
然后用油来装饰他。把他包围起来
油(描述,B材料费用(肉(描述,A原料费)))
然后用辣椒装饰他
辣椒(描述,C材料费(油(描述,B材料费用(肉(描述,A原料费)))))
最后炒菜装饰他
炒菜(描述,E人工费用( 辣椒(描述,C材料费(油(描述,B材料费用(肉(描述,A原料费)))))))
先嗲用E的价格计算方法。E的计算方法会调用下一层C的计算方法,C的计算方法又会调用下一层B的计算方法。这样层层累加,最后得到整数。
如何做到这一点?委托和继承,继承不是继承方法,而是继承类型。指引这些类都是在同一个类型(菜)之下的。这样通过多态才能完成上面的自动调用下一层方法。
可能实现起来就明白了。这样说还是很迷糊。如果这样想就对了。你可能已经预料到是以一个类作为基准,然后其他的装饰着来装饰这些产品。
好了,这是目前所知道的一切:
[quote]
[list][*]装饰者和被装饰者对象有相同的超类型(继承得来)[*]你可以用一个或者多个装饰者包装一个对象(除了上面说的肉。你也可以包装猴子。。在你要你愿意犯法)[*]既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,都可以用装饰过的对象代替他。(见识过学校里炒菜的方式么。炒肉是一盘,炒辣椒是一盘。好了。两个合起来,就成了青椒炒肉)[*][b][color=red]装饰者可以在所委托被装饰者的行为之前或者之后,加上自己的行为,以达到特定的目的(例子里的成本计算。都是附加自身成本的价格)[/color][/b][*]对象可以在任何时候被装饰,所以可以在运行时动态,不限量地用你喜欢的装饰者来装饰对象。[/list][/quote]
看看装饰模式的精确定义吧
[quote]
动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更具又弹性的替代方案
[/quote]
类图如下:
[attach]26471[/attach]
再把这个应用到我们的计算饭菜成本价格的程序里去
[attach]26472[/attach]
终于调试完了。。。。没办法哈。php和java那种纯粹的OO语言还是有很大区别的。
继续。
在调试过程中发现有个错误。类图不能这样。继承而来的抽象Decorator类,里面只是上面继承过来的类。也就是他不仅仅是个抽象类。而且是个虚类(我自己发明的名词=。=。因为里面什么都没有)。为什么要这么设立个类。因为我要保证整个逻辑的层次性,并且如果你将来要扩展自己的调料方法呢?难道改父类?所以这个类是有必要的。
废话不说了。上代码
这是food类。写在inderface文件内。本来是和Decorator抽象类放一起的,但使用的时候老是报错。拿开就没事了。
[php]
<?php
/**
* 定义一个食物的抽象类。任何材料的计算都是从这里继承下去,从而拥有相同的类型。
*/
abstract class Food{
public $description = "食物";
//方法getDescription已经在这里实现。
public function getDescription(){
return $this->description;
}
//抽象的cost方法必须在子类实现
abstract public function cost();
}
?>
[/php]
最好是抽象的。因为更容易扩展。当然也可以是实类。自己实验吧。
然后是。。。第二阶梯的装饰公司。。。他们负责装修好那些什么都没有的纯粹材料
[php]
<?php
require("interface.php");
abstract class Decorator extends Food {
}
?>
[/php]
(不明白。为什么两个抽象类放在一起的时候发生重定义错误。拉开就没问题了)
这只是装饰公司的招牌而已。。。。他告诉外面的人这里面的都是装饰者。想找谁自己进去点名拉
请这些伟大的装饰者上场!
[php]
<?php
/****************************************************************************
* 此文件用于存放各种菜的类,其实可以一个菜一个文件。
* 但这是装饰者模式的一个弊病。会产生无数的小类。所以存放在一起。方便管理
****************************************************************************/
require 'interface1.php';
/**
* 开饭馆得先吧盐买好。神啊,建立个盐吧
*/
class salt extends Decorator {
//怎样才能装饰?得吧需要装饰的组件引用进来并存储。对吧!想想办法哈。。。建立个变量,再利用构造器。组件就进来了。欧也
protected $food;
public function __construct(Food $_food){
$this->food = $_food;
}
//这里是继承Decorator类里的getDescription方法。重写他!
public function getDescription(){
return $this->food->getDescription()."盐";//为什么能这样。因为都是同样的类型!继承而来的!
}
public function cost(){
return $this->food->cost() + "0.2";//一餐盐我按2冒钱算。。可能贵了点。
}
}
/**
* 现在的油好贵啊~~~~
*
*/
class fat extends Decorator {
protected $food;
public function __construct(Food $_food){
$this->food = $_food;
}
//这里是继承Decorator类里的getDescription方法。重写他!
public function getDescription(){
return $this->food->getDescription()."油";//为什么能这样。因为都是同样的类型!继承而来的!
}
public function cost(){
return $this->food->cost() + 0.5;//囧~好贵。。其实怎么算可以用很多方法算出这个价格来。只是做演示。没必要较真
}
}
/**
* 现在都爱吃辣椒,加点辣椒进来
*/
class capsicum extends Decorator {
protected $food;
public function __construct(Food $_food){
$this->food = $_food;
}
//这里是继承Decorator类里的setDescription方法。重写他!
public function getDescription(){
return $this->food->getDescription()."辣椒";//为什么能这样。因为都是同样的类型!继承而来的!
}
public function cost(){
return $this->food->cost() + 0.5;//差不多了。。。
}
}
?>
[/php]
好了。装饰公司已经开张了。等客户上门。你看,客户坐下面的:
[php]
<?php
/****************************************************************************
* 此文件用于存放各种菜的类,其实可以一个菜一个文件。
* 但这是装饰者模式的一个弊病。会产生无数的小类。所以存放在一起。方便管理
****************************************************************************/
/**
* 肉。。。。记得要给其设置描述,还得重写cost方法
*/
class meat extends Food {
public function __construct(){
$this->description = "肉";
}
public function cost(){
return 11.00;//现在肉多少一斤?11块吧。。忘记了
}
}
/**
* 再来一个,蔬菜!。当然,你可以狡辩白菜,小白菜,娃娃菜。ORZ~~饶了我吧。。
*/
class vegetable extends Food {
public function __construct(){
$description = "蔬菜";
}
public function cost(){
return 3.00;//3块一把。。好贵的蔬菜。囧
}
}
/**
* 换个口味!排骨。当然狗爱啃骨头。可惜我不是狗
*/
class chop extends Food {
public function __construct(){
$description = "排骨";
}
public function cost(){
$cost=10.00;//排骨大概什么价,我还真不晓得。。。我是正统的宅男
}
}
/**
* 来点牛肉吧。鄙人很喜欢这玩意
*/
class beeves extends Food {
public function __construct(){
$description = "排骨";
}
public function Cost(){
return 15.00;//随便点吧。。。
}
}
?>
[/php]
所有人都到其了~~
自己对照我上面的类图来看。不多解释=。=。。。很多说明都写在描述里了。再多解释就是啰嗦了。
[color=red][quote]
[color=red]精华:装饰者的接口(再此重申,面向对象里面的接口是广义说法,包括接口和抽象类。引用的是超类型定义)为什么要继承food类。因为他要能替换food的位置。让food下面的那些材料都一位他就是food。从而达到装饰的作用!切记,[/color][size=5][color=black]这里的继承不是重点继承行为,而是继承类型[/color][/size]
[/quote]
[/color][color=black]测试代码:[/color]
[php]
require_once('Decorator.php');
require_once('SomeFood.php');
//以上是引入必要文件
$somefood = new meat();//这里应该理解成Food $somefood=new meat().
echo $somefood->getDescription()."$".$somefood->cost()."</br>";//测试是否能实力他成功
//下面开始装饰
$somefood = new salt($somefood); //用盐开始装饰他。
$somefood = new capsicum($somefood);//用辣椒装饰他。
echo $somefood->getDescription()." $ ".$somefood->cost();//输出
[/php]
结果:
[quote]肉$11
肉盐辣椒 $ 11.7[/quote]
只是简单的例子说明一下。所以输出没做更好的处理。大家可以自己实验。
并且这只是个基础部分。我只公布了一半实例代码。另一半~~~嘿嘿,大家自己试试:我只是让盐,油,这些作料加进去。怎么个做法呢?炒还是烧。这些都可以作为装饰者来修饰整个过程~具体怎么做。。。自己写代码吧。有问题再问我。如果你能理解这个装饰者模式。那你这个一定能够写出来的。很简单。
可能你已经注意到了。装饰者类会产生大量的小类。这是事实,不能逃避。但是不能遮蔽他的强大。
java.io这个包大家一定听过。而且听起来非常的大。。类图我就不贴了。。。刚随便找了个。超过了文件尺寸。。。
这个包完全都是用装饰者模式编写的。
最上层的inputstream是抽象的组件。
然后下面的单类分别是需要被装饰的具体组件和装饰者集合。以前我看这个包的时候也是头大,然后就没看了。现在算是明白了。其实也很简单=。=
这只是个例子。
其实在php中,装饰者模式的应用我个人觉得最多的应该是字符串的处理了。太多的处理方式。大写,小写,格式化,截断,加密。。用装饰者模式操作他是不是思路,扩展,维护都会更方便呢?这是小方面,大方面就得看具体项目的应用了。呵呵~
可能小类太多,造成操作不方便。下一章节工厂模式可以解决这个问题。明天中午起来再写下个章节吧。。。。。挺长的.
包括3个部分,简单工厂模式,正统的工厂模式,工厂方法
[[i] 本帖最后由 某个人 于 2008-11-10 20:48 编辑 [/i]]
第四章 加工你的OO精华 工厂模式
上几章都大致了解了在OOP设计中,应该针对抽象编程而不是具体实现编程。但是上面几章的代码中或多或少都又"new"来创建对象的实例。那么在这些地方,就不是针对抽象编程,而成了具体实现的编程。
但使用"new" 有错吗?从本质上讲是没错的,因为这是OOP的基础。但是,从另一个角度去说,他是错误的。但错不在他。而在程序上面。
简单的说,就是我们使用了new关键词将代码的执行硬编码进了程序之中。他不能在程序运行时来决定运行哪一个。也就是说,当我们希望一个项目需要改变的时候,需要添加新的对象的时候,需要打开文件进去修改
第三节的装饰模式重点讲过一个原则:对修改关闭,对扩展打开。但是,频繁使用new这个创建实例的办法就是在破坏这个原则。因为你总是会要在不久的将来改变他的。只要你要添加新的对象。你必定要修改源文件。
那错误具体是由什么导致的?第一章我说过,OO编程的基本原理不是那些原则,而是Change!改变。一切都是因为改变捣的鬼。
看看下面这个例子:
我们需要开一个面包店。已经建立好了面包订购的类(Bread):
下面用另一个类的方法调用他。
[php]
function orderBread(){
$bread = new Bread();//如果说大家看了上面的设计模式,或者已经对设计模式有了一定的认识,这里肯定那个希望是建立一个抽象类或者接口。可是那样就没办法实例化了
//对面包进行各样的操作
$bread->prepare();
$bread->bake();
$bread->cut();
$bread->box();
}
[/php]
这里本身没有任何错误。但是,如果我要更多的面包呢?只能由我们来决定面包的种类,然后叫这个方法来制造面包了
[php]
function orderBread($_bread){
if($_bread == "BlackBread"){
$this->break = new BlackBread();
}else if ($_break == "WhiteBread"){
$this->break = new WhiteBread();
}else if($_break == "SweetBread"){
$this->break = new SweetBread();
}else ($_break == "NutsBread"){
$this->break = new NutsBread();
}
//对面包进行各样的操作
$bread->prepare();
$bread->bake();
$bread->cut();
$bread->box();
}
[/php]
手写一堆代码。。真麻烦
恩,这样就完成了。如果以后要改呢?要增加面包,或者减少面包种类呢?是不是必须得打开这个文件来修改他。删删改改的。
这样就找到问题结症了。问题不在new,而在这一堆的改变上面。他们随时会改变的。你就得随时的去改变这些文件。
那就把它拿出来。
[quote] if($_bread == "BlackBread"){
$this->break = new BlackBread();
}else if ($_break == "WhiteBread"){
$this->break = new WhiteBread();
}else if($_break == "SweetBread"){
$this->break = new SweetBread();
}else ($_break == "NutsBread"){
$this->break = new NutsBread();
}
[/quote]
这一块改变拿出来,放到另一个类里面。这样方法orderBread就不用关心是要定什么面包了。他只要知道调用它的时候需要制作一块面包。
这就是工厂!用工厂来建立对象!
先从工厂入手:
[php]
<?php
require("Bread.php");
class BreadFactory{
private $bread;
//通过这个creatBread方法创建我们的面包
public function creatBread($_bread){
if(is_string($_bread)){
//类型判断是必须的。php是弱类型语言。
if($_bread == "BlackBread"){
$this->bread = new BlackBread();
}elseif ($_bread == "WhiteBread"){
$this->bread = new WhiteBread();
}elseif($_bread == "SweetBread"){
$this->bread = new SweetBread();
}elseif($_bread == "NutsBread"){
$this->bread = new NutsBread();
}else{
echo "本工厂不提供这款面包";
}
//以上没有任何变化。将他从原来的面包点的代码中拿出来
}
return $this->bread;
}
}
?>
[/php]
然后是面包种类
[php]
<?php
/**
* 这里存放很多很多种类的面包类。共用抽象类,Bread
*/
abstract class Bread {
abstract function prepare();
abstract function bake();
abstract function cut();
abstract function box();
}
class BlackBread extends Bread {
public function prepare(){
echo "准备好黑面包</br>";
}
public function bake(){
echo "烘烤黑面包</br>";
}
public function cut(){
echo "切片黑面包</br>";
}
public function box(){
echo "黑面包已经打包好了</br>";
}
}
class WhiteBread extends Bread{
public function prepare(){
echo "准备好白面包</br>";
}
public function bake(){
echo "烘烤白面包</br>";
}
public function cut(){
echo "切片白面包</br>";
}
public function box(){
echo "白面包已经打包好了</br>";
}
}
class SweetBread extends Bread{
public function prepare(){
echo "准备好甜面包</br>";
}
public function bake(){
echo "烘烤甜面包</br>";
}
public function cut(){
echo "切片甜面包</br>";
}
public function box(){
echo "甜面包已经打包好了</br>";
}
}
class NutsBread extends Bread{
public function prepare(){
echo "准备好果仁面包</br>";
}
public function bake(){
echo "烘烤果仁面包</br>";
}
public function cut(){
echo "切片果仁面包</br>";
}
public function box(){
echo "果仁面包已经打包好了</br>";
}
}
?>
[/php]
面包和工厂准备好了。最后开店
[php]
/**
* 面包店类。开店咯
*/
require("BreadFactory.php");
class BreadStore{
private $bread;
//通过构造函数来创建面包.你开店必须得找到源头啊。所以得定义工厂是谁
public function __construct(BreadFactory $_bread){
$this->bread = $_bread;
}
public function orderBread($_type){
$orderBread = $this->bread->creatBread($_type);
$orderBread->prepare();
$orderBread->bake();
$orderBread->cut();
$orderBread->box();
}
}
[/php]
最后测试一下
[php]
$a=new BreadFactory();
$b=new BreadStore($a);
//测试一下.我先订购一款黑面包
$b->orderBread("BlackBread");
//再来个白面包如何
$b->orderBread("WhiteBread");
[/php]
结果是
[quote]
准备好黑面包
烘烤黑面包
切片黑面包
黑面包已经打包好了
准备好白面包
烘烤白面包
切片白面包
白面包已经打包好了
[/quote]
是不是很简单,思路也非常清晰~
注意,我在Bread文件里面让所有的面包都继承了个抽象类,单是在工厂里却没使用这个抽象类。实际上,所有的变量前面你应该理解成Bread的类型。
这个其实是简单工厂。并不能叫做完全的模式。但是我个人在很多的教程里面都看到将这样的过程称为工厂模式。事实上,这只是个编程习惯,但经常使用。所以还是能将其归纳进工厂模式的。
下面来看看我的简单面包点类图是如何的
[attach]26920[/attach]
再次提醒!这里所说的实现接口并不是单指:写一个类,用implements关键字来实现一个接口。而是泛指实现某个超类型(可以是一个类也可以是具体接口)的某个方法
现在我们又有了新的改变。面包店生意太好了。有一些其他省市的面包店希望加盟我们。
好像问题不是很大,用上面的简单工厂方式。那就每个工厂一个工厂类。HNBreadFactory,GDBreadFactory。
这样并没多大问题。但是我们面包店生意好就是因为我们的一些质量有保证。如果我希望多点质量控制呢?都用同样的揉面,切面,装箱。流程都是一模一样的。如何操作?下面就用工厂方法来完成这个工作。看看他是如何工作的。
先将creatBread方法从工厂类里面移出来,并放入我们原先的面包店。但他只知道建立面包。具体建立什么面包他并不知道。需要完成具体面包的是在他的子类里,也就是说将他抽象话。不仅抽象整个Bread类,也要抽象这个creatBread方法。
下面是代码
[php]
<?php
/**
* 面包店类。开店咯
*/
abstract class BreadStore{
private $bread;
public function orderBread($_type){
//调用自身的抽象方法
$orderBread = $this->creatBread($_type);
$orderBread->prepare();
$orderBread->bake();
$orderBread->cut();
$orderBread->box();
}
abstract function creatBread($_type);
}
?>
[/php]
这样做的好处是抽象的类BreadStore并不能决定建立什么样的面包。当然,我这里说的由子类建立面包,并不是他自身在运行时决定。而是在下面的子类来决定他应该如何提供面包。你从这个代码里面只能理解到。
$orderBread = $this->creatBread($_type);
订购面包。
订购什么样的面包,BreadStore并不知道这个面包是什么样的。可他是抽象的,不能实例化。所以是由他的子类来重写creatBread类来决定建立什么样的面包。
看看他的子类如何做的
[php]
<?php
require("Bread.php");
require("BreadStore.php");
//在湖南开店
class HNBreadStore extends BreadStore{
public function creatBread($_bread){
if(is_string($_bread)){
//类型判断是必须的。php是弱类型语言。
if($_bread == "BlackBread"){
return new HNBlackBread();
}elseif ($_bread == "WhiteBread"){
return new HNiteBread();
}elseif($_bread == "SweetBread"){
return new HNSweetBread();
}elseif($_bread == "NutsBread"){
return new HNNutsBread();
}
}
}
}
//在广东开店
class GDBreadStore extends BreadStore{
public function creatBread($_bread){
if(is_string($_bread)){
//类型判断是必须的。php是弱类型语言。
if($_bread == "BlackBread"){
return new GDBlackBread();
}elseif ($_bread == "WhiteBread"){
return new GDiteBread();
}elseif($_bread == "SweetBread"){
return new GDSweetBread();
}elseif($_bread == "NutsBread"){
return new GDNutsBread();
}else return null;
}
}
}
?>
[/php]
子类里通过重写方法来决定如何建立面包。每一个加盟店都有自己的面包对象家族。他来决定采用哪样的面包。
下面是关键的地方。因为光又面包店,却没有面包,当然是不行的。
这里相对于上面的简单工厂模式有了点改变,因为为了更好的扩展面包的种类。所以面包抽象类进行了改变,直接由他来定义好了各种质量保证流程
[php]
<?php
/**
* 这里存放很多很多种类的面包类。共用抽象类,Bread
*/
abstract class Bread {
protected $name;
protected $douch;
protected $sauce;
protected $toppings=array();
public function prepare(){
echo "正在准备".$this->name."</br>";
echo "揉入".$this->douch."...<br>";
echo "加入调料".$this->sauce."...<br>";
echo "加入配料...";
foreach ($this->toppings as $value){
echo " ".$value." ";
}
echo "</br>";
}
function bake(){
echo "烘烤10分钟<br>";
}
function cut(){
echo "切成圆形<br>";
}
function box(){
echo "放入盒子<br>";
}
}
class HNBlackBread extends Bread {
public function __construct(){
$this->name = "湖南黑面包";
$this->douch = "酵母面团";
$this->sauce = "辣椒酱";
$this->toppings = array("鸡蛋");
}
}
class HNWhiteBread extends Bread{
public function __construct(){
$this->name = "湖南白面包";
$this->douch = "不发酵面团";
$this->sauce = "辣椒酱";
$this->toppings = array("鸡蛋");
}
}
class HNSweetBread extends Bread{
public function __construct(){
$this->name = "湖南甜面包";
$this->douch = "酵母面团";
$this->sauce = "辣椒酱";
$this->toppings = array("鸡蛋","糖精","果酱");
}
}
class HNNutsBread extends Bread {
public function __construct(){
$this->name = "湖南果仁面包";
$this->douch = "酵母面团";
$this->sauce = "辣椒酱";
$this->toppings = array("鸡蛋","开心果");
}
}
class GDBlackBread extends Bread {
public function __construct(){
$this->name = "广东黑面包";
$this->douch = "酵母面团";
$this->sauce = "糖精";
$this->toppings = array("鸡蛋");
}
}
class GDWhiteBread extends Bread{
public function __construct(){
$this->name = "广东白面包";
$this->douch = "不发酵面团";
$this->sauce = "糖精";
$this->toppings = array("鸡蛋");
}
}
class GDSweetBread extends Bread{
public function __construct(){
$this->name = "广东甜面包";
$this->douch = "酵母面团";
$this->sauce = "糖精";
$this->toppings = array("鸡蛋","糖精","果酱");
}
}
class GDNutsBread extends Bread {
public function __construct(){
$this->name = "广东果仁面包";
$this->douch = "酵母面团";
$this->sauce = "糖精";
$this->toppings = array("鸡蛋","开心果");
}
}
?>
[/php]
这样整个工作就完成了。好吧,订购两个不同地区的面包来尝一下吧。
[php]
$b =new GDBreadStore();
$b->orderBread("BlackBread");
$b =new HNBreadStore();
$b->orderBread("BlackBread");
[/php]
如果你将两个简单商店类的creaderBread设立成静态的那就更简单,大家可以自己测试一下。
下面是显示结果:
[quote]
正在准备广东黑面包
揉入酵母面团...
加入调料糖精...
加入配料... 鸡蛋
烘烤10分钟
切成圆形
放入盒子
正在准备湖南黑面包
揉入酵母面团...
加入调料辣椒酱...
加入配料... 鸡蛋
烘烤10分钟
切成圆形
放入盒子
[/quote]
是不是很简单。再回头来看看测试代码
$b =new GDBreadStore();
$b->orderBread("BlackBread");
$b =new HNBreadStore();
$b->orderBread("BlackBread");
我建立两个店,然后订购。非常的复合我们自身在生活中的习惯。
ok。如果上面的都明白了。那你也明白了什么叫做工厂方法模式。
他和简单工厂很相似。所以很多人混为一谈!但他是通过子类来决定建立什么样的面包。而简单工厂是有其他的类来确定建立什么样的面包。
[size=2]下面看看我们的面包加盟店的类图。可能这个关系更清楚一点:[/size]
[size=2][attach]26960[/attach][/size]
[size=2]解释都写在图上。就不啰嗦了=。=[/size]
[size=2]定义我们的工厂方法模式:[/size]
[quote]
定义一个创建对象的接口,但是具体对象有它的子类来决定。也就是工厂方法将对象实例化推迟到了子类
[/quote]
具体的工厂方法的类图
[attach]26961[/attach]
工厂方法让具体的对象解脱了出来。并不再依赖具体的类。而是抽象。
它正式是复合OOP设计中非常重要的一个原则:
依赖倒置原则(经常听到,却不明白到底什么意思)
定义:
[quote]
[color=red]依赖抽象编程。而不要依赖具体编程[/color]
[/quote]
看起来这个原则和上面的
[size=5]针对接口编程,不针对实现编程[/size]
[size=2]这个原则非常的相似。但是有本质区别的[/size]
[size=2]他更注重的是依赖抽象。[/size]
[size=2]就像上面的面包店。本身面包店BreadStore他不能决定生产什么样的面包,他只能依赖他的底层子类来决定。[/size]
[size=2]面包本身(各种面包)也不能依赖自身去决定怎么样去切片怎么样去装箱。只能依赖他的抽象类Bread。[/size]
[size=2]这个原则说明了:不要让高层组件依赖于底层组件。而是不管高还是底,都应该依赖抽象对象。[/size]
[size=2]换句话说。如果这个例子里所有的面包对象都是依赖面包这个具体类而建立的,就是违反了这个原则,就导致了解耦的失败。[/size]
[size=2]依赖倒置原则到底倒置了哪里呢?[/size]
[size=2]先想想看,你要开一个面包店会怎么考虑?[/size]
[size=2]A:为了能够烘烤,切片,装到盒子里给客户,我必须先准备好不同种类的面包。黑的白的,甜的或者果仁的。甚至更多。[/size]
[size=2]这样是没错的,但是这样就依赖各种面包的具体类。也就是这是从高层开始依赖。而违反了这个原则。如果我倒过来呢?[/size]
[size=2]A:先提供一个抽象接口,他告诉我可以提供这些面包,而我不用担心会如何制作。[/size]
很好。这样就不用理会那些具体的面包类了。剩下的就是如何去开面包店了。思维~~~倒过来了。从底层开始往上面思考。并且依赖抽象。而不是具体的类了。
下面几个原则来告诉你如何做到尽量不要违反这个原则:切记!是尽量不要违反。而不是任何地方都不违反。这和OO设计原则一个道理的!不可能完全遵循OO设计原则而增加自己的工作两。设计原则和设计模式只是为了解决问题而存在,而不是产生问题。如果你连建立一个字符对象都用到此原则说明你有问题了。你应该思考一下这个对象将来是否会发生什么改变,如果不会发生改变,不遵循任何原则也没什么影响。
[quote]
变量不可以持有具体的引用!
(用new关键词就是在次优具体的引用了。尽量用工厂模式中的一种避免它)
不要让类派生自具体类
(如果派生自具体类,你就会依赖具体类。请派生自一个抽象)
不要覆盖基类中已经实现的方法
(如果覆盖基类已经实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享)
[/quote]
你不可能完全遵守这些方针。你应该尽量达到这个原则。而不是随时都遵守这个原则。自己慢慢体会吧=。=
让我们进入真正的抽象工厂模式吧。
现在我们需要进行原料控制,因为面包加盟店有些可能会偷工减料。所以我们给其指定原料工厂提供原料。
先准备一下原料工厂
[php]
interface MaterialFactory{
public function creatDouch();
public function creatSauce();
public function creatToppings();
}
/**
* 各个地区的原料工厂
*/
class HNMaterialFactory implements MaterialFactory{
public function creatDouch(){
return new FermentDouch();
}
public function creatSauce(){
return new ChiliSauce();
}
public function creatToppings(){
return new setToppings(new Egg(),new NewChiliSauce(),new Jam());
}
}
class GDMaterialFactory implements MaterialFactory{
public function creatDouch(){
return new NotFermentDouch();
}
public function creatSauce(){
return new Saccharin();
}
public function creatToppings(){
return new setToppings(new Egg(),new NewSaccharin(),new Jam());//作为示例,将这里写死也没什么影响
}
}
[/php]
上面的原料工厂里也看到了。每个原料都来至不同的原料对象。下面是对原料进行简单的定义
[php]
<?php
/**
* 存储各种各样的原材料,当然,每种原料都是依赖的接口
*/
/**
* 各种原材料的接口
*
*/
interface Douch{
public function getDouch();
}
interface Sauce{
public function getSauce();
}
interface Toppings{
public function getToppings();
}
//面粉团集群
class FermentDouch implements Douch {
protected $douch;
public function __construct(){
$this->douch = "发酵面包";
}
public function getDouch(){
return $this->douch;
}
}
class NotFermentDouch implements Douch {
protected $douch;
public function __construct(){
$this->douch = "不发酵面包";
}
public function getDouch(){
return $this->douch;
}
}
//酱汁集群
class ChiliSauce implements Sauce {
protected $sauce;
public function __construct(){
$this->sauce = "辣椒酱";
}
public function getSauce(){
return $this->sauce;
}
}
class Saccharin implements Sauce{
protected $sauce;
public function __construct(){
$this->sauce = "果酱";
}
public function getSauce(){
return $this->sauce;
}
}
//作料集群
class setToppings implements Toppings {
protected $toppings;
public function __construct(Toppings $_egg,Toppings $_sauce,Toppings $_jam){
$this->toppings=array($_egg->getToppings(),$_sauce->getToppings(),$_jam->getToppings());
}
public function getToppings(){
return implode(",",$this->toppings);
}
}
class Egg implements Toppings {
protected $name;
public function __construct(){
$this->name = "鸡蛋";
}
public function getToppings(){
return $this->name;
}
}
class NewChiliSauce implements Toppings {
protected $name;
public function __construct(){
$this->name = "苏丹红辣椒酱";//辣飞你
}
public function getToppings(){
return $this->name;
}
}
class Jam implements Toppings {
protected $name;
public function __construct(){
$this->name = "高级果酱";
}
public function getToppings(){
return $this->name;
}
}
class NewSaccharin implements Toppings {
protected $name;
public function __construct(){
$this->name = "加量糖精";
}
public function getToppings(){
return $this->name;
}
}
?>
[/php]
原料准备完毕。那如何将这个原料提供给具体的客户呢?
[php]
//在湖南开店
class HNBreadStore extends BreadStore{
public function creatBread($_bread){
$material = new HNMaterialFactory();
if(is_string($_bread)){
//类型判断是必须的。php是弱类型语言。
if($_bread == "BlackBread"){
$bread = new BlackBread($material);
$bread->setName("湖南口味的黑面包");
}elseif ($_bread == "WhiteBread"){
$bread = new WiteBread($material);
$bread->setName("湖南口味的白面包");
}elseif($_bread == "SweetBread"){
$bread = new SweetBread($material);
$bread->setName("湖南口味的甜面包");
}elseif($_bread == "NutsBread"){
$bread = new NutsBread($material);
$bread->setName("湖南口味的果仁面包");
}else return null;
}
return $bread;
}
}
//在广东开店
class GDBreadStore extends BreadStore{
public function creatBread($_bread){
$material = new GDMaterialFactory();
if(is_string($_bread)){
//类型判断是必须的。php是弱类型语言。
if($_bread == "BlackBread"){
$bread = new BlackBread($material);
$bread->setName("广东口味的黑面包");
}elseif ($_bread == "WhiteBread"){
$bread = new WiteBread($material);
$bread->setName("广东口味的白面包");
}elseif($_bread == "SweetBread"){
$bread = new SweetBread($material);
$bread->setName("广东口味的甜面包");
}elseif($_bread == "NutsBread"){
$bread = new NutsBread($material);
$bread->setName("广东口味的果仁面包");
}else return null;
}
return $bread;
}
}
[/php]
重点在
$material = new HNMaterialFactory();
$bread = new BlackBread($material);
这里重写了新建不同面包的对象方法。
$material就是原料工厂。告诉$bread从这个原料工厂里提取原料。这样保证完成正确的面包种类。
面包的抽象类并没有发生任何改变:
[php]
/**
* 面包店类。开店咯
*/
abstract class BreadStore{
private $bread;
public function orderBread($_type){
//调用自身的抽象方法
$orderBread = $this->creatBread($_type);
$orderBread->prepare();
$orderBread->toString();
$orderBread->bake();
$orderBread->cut();
$orderBread->box();
}
abstract function creatBread($_type);
}
[/php]
决定生产什么样的面包还是由各加盟店的子类来决定的。
下面是各种面包的定义
[php]
class BlackBread extends Bread {
protected $factory;
public function __construct(MaterialFactory $_factory){
$this->factory = $_factory;
}
public function prepare(){
$this->name = "正在准备".$this->name;
$this->douch = $this->factory->creatDouch();
$this->sauce = $this->factory->creatSauce();
$this->toppings = $this->factory->creatToppings();
}
}
class WhiteBread extends Bread{
public function __construct(MaterialFactory $_factory){
$this->factory = $_factory;
}
public function prepare(){
$this->name = "正在准备".$this->name;
$this->douch = $this->factory->creatDouch();
$this->sauce = $this->factory->creatSauce();
$this->toppings = $this->factory->creatToppings();
}
}
class SweetBread extends Bread{
public function __construct(MaterialFactory $_factory){
$this->factory = $_factory;
}
public function prepare(){
$this->name = "正在准备".$this->name;
$this->douch = $this->factory->creatDouch();
$this->sauce = $this->factory->creatSauce();
$this->toppings = $this->factory->creatToppings();
}
}
class NutsBread extends Bread {
public function __construct(MaterialFactory $_factory){
$this->factory = $_factory;
}
public function prepare(){
$this->name = "正在准备".$this->name;
$this->douch = $this->factory->creatDouch();
$this->sauce = $this->factory->creatSauce();
$this->toppings = $this->factory->creatToppings();
}
}
[/php]
这里和上面的工厂方法是有区别的。大家可以自己翻上去查看。
重点改变是在新建这些面包种类的实例对象的时候在构造函数中引进了原料工厂。
真正起到作用的就是这里。
public function __construct(MaterialFactory $_factory){
$this->factory = $_factory;
}
准备工作中只是在准备,并不知道他准备的是什么口味的。所以弱耦合还是存在,并没破坏的。
由什么源材料决定什么样的面包。这是很自然的道理。
现在一切都有,只欠东风了。
[php]
$b =new GDBreadStore();
$b->orderBread("BlackBread");
$b =new HNBreadStore();
$b->orderBread("BlackBread");
[/php]
输出其实和上面的一样。只是工作模式不同
[quote]
正在准备广东口味的黑面包
不发酵面包
果酱
鸡蛋,加量糖精,高级果酱
烘烤10分钟
切成圆形
放入盒子
正在准备湖南口味的黑面包
发酵面包
辣椒酱
鸡蛋,苏丹红辣椒酱,高级果酱
烘烤10分钟
切成圆形
放入盒子
[/quote]
上面的工厂方法和简单工厂理解了的这个正式的工厂模式其实就非常容易理解了。。只是方式不同而已。可能细心的朋友已经发现了。工厂方法是蕴藏在抽象工厂模式中。
通过抽象工厂所提供的接口,可以创建产品的家族。利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各种各样的工厂,制造出各种不同的产品。例如,不同的区域,不同的操作系统,不同的外观以及操作。
因为代码从实际的产品中解耦了,所以我们可以替换不同的工厂来取得不同的行为(例如,取得番茄酱料,而不是取得大蒜酱料);
好了。让我们来定义抽象工厂模式吧:
[php]
提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。
[/php]
抽象工厂允许客户使用抽象的接口来创建一组相关的产品。而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
类图。。。。。如下。。
[attach]27005[/attach]
画得比较乱。。。呃。。其实我已经理解了。只是不知道怎么用我这个软件话出来。。不能画折线。囧
至于为什么抽象工厂的方法经常以工厂方法的方式实现。。截一段深入潜出设计模式中的说明吧。
抽象工厂的任务是定义一个负责创建一组产品的接口。这个接口内的每个方法都负责创建一个具体的产品,同时,我们利用实现抽象工厂的子类来提供这些具体的做法。所以,在抽象工厂中利用工厂方法实现生产方法是相当自然的做法。
一下来区别工厂方法和抽象工厂之间:
[quote]
工厂方法是使用类,而抽象工厂是操作的对象。
工厂方法创建对象的方法是继承,抽象工厂创建工厂的方法是组合。
抽象工厂是通过抽象方法(MaterialFactory),由这个类型的子类定义产品被产品的方法。但必须先实例化。然后将他传入一些针对抽象类型所写的代码中
工厂方法是直接通过抽象方法的子类决定如何创建产品。不需要在外部再实例化产品传入具体代码。
不管是使用工厂方法还是抽象工厂,都可以将对象创建封装起来,使应用程序解耦,并降低对特定实现的依赖。
抽象工厂是在需要创建产品家族和想让制造的相关产品集合其来的时候使用。
工厂方法在把客户代码需要实例化的具体类中解耦或者如果目前不知道将来需要实例化那些具体类的时候使用。
[/quote]
相对而言进过个人经验来判断是使用哪一个工厂模式。工厂方法相当于轻量级的设计模式。简单处理就可以使用他,只需要把抽象方法继承成子类并实现这个工厂方法就可以了。
抽象工厂的范围更广泛一点。
ok。。今天工厂模式终于告一段落了。。。3个模式。你明白了吗?从简单到容易。从轻量级的简单工厂到重量级的抽象工厂。可以满足我们解耦应用程序的各项需要。根据自己项目的需求来选择吧!相信他能工作得很好。
结束。。有任何疑问请访问我的blog。。
[[i] 本帖最后由 某个人 于 2008-11-13 21:48 编辑 [/i]]
第五章 独一无二的对象 单例模式
单例模式可能是所有设计模式中最简单,但却是非常重要的一种设计模式。很多情况下都需要类只有一个实例,比如说数据库的实例对象(可以看深空的那个数据库类。就是采用了单例模式)。因为他只有一个类图。所以说他简单。
通过几个提问,可能你能更容易的理解单例模式!
你如何创建类的实例?
使用new关键字
那如何避免一个类的实例创建?
可以这样操作么?
class MyObject{
private function __construct() {
}
}
那这样将如何获取这个类的实例?
因为构造函数已经被私有化。只有类的内部才能调用这个类的实例。外部想调用是没办法的。
那如果这样操作是不是就解决了?
class MyObject{
private function __construct() {
}
public function getInstance(){
return new MyObject;
}
}
直接外部调用getInstance()方法就能够返回这个类的实例。这样做的好处是保证整个应用程序中只有一个类的实例。当然。需要进一步的限制。为了保证这个类的实例是独一无二的。所以得在类的内部进行存储,判断。下面是标准的单例模式的例子
[php]
/**
* 单例模式实例
*/
class MyObject{
//提供一个私有的静态成员。他用来存储这个类在整个应用程序中的实例
static $obj;//并且没有声明是什么保护类型。子类也可以直接使用。
//下面这个变量是测试用的
private $test;
private function __construct(){
//可以在这里写入你构造这个类实例的时候初始化操作
$this->test="我将进行单例模式的操作";
}
//通常情况下的单利模式都是采用getInstance作为单例引用的方法名
public static function getInstance(){
//通过instanceof类型判断是否这个类的实例变量已经进行了初始化。此过程是保证整个类的实例是独一无二的
if(!(self::$obj instanceof self)){
self::$obj = new self();
}
return self::$obj;
}
//对__clone方法进行私有化是为了防止外部通过复制这个类的副本而形成不同的实例引用。
private function __clone(){}
public function test(){
echo $this->test;
}
}
$a= MyObject::getInstance();
$a->test();
[/php]
self的用法,我已经在我的[url=http://www.joinphp.cn/sampeng]blog[/url]进行过探讨。self代表了整个类的引用,而不是任何具体的实例引用。有兴趣的可以去看看。
类图我就不画了。挺简单的东西。就一个类。
作为单例模式。必须具有一下几个特征:
[quote]
1、必须有一个显性的构造器,并且申明为私有的(private)
2、必须有一个静态成员(static mamber)来记录这个类的实例引用。
3、必须有一个公开的(public)方法来对这个类进行实例化调用
[/quote]
其他的注意项我已经在上面的例子中进行了著名。结果我就不贴了。自己运行尝试。
定义单例模式:
[quote]
确保一个类只有一个实例,并提供全局访问点。
[/quote]
没什么需要交代的了。就这么多。赶紧去修改自己的数据库类吧!
[[i] 本帖最后由 某个人 于 2008-11-14 13:42 编辑 [/i]]
第六章 封装调用 命令模式
呵呵。休息了两天。今天开始开始我们的新的一个模式的认识:命令模式!首先看看我们今天的例子:程序box!
我们现在给操作系统做一个程序。我想很多人用过懒人安装法吧。就是用一个应用程序列出所有的可能安装的程序。只需要点击就能安装。
现在我们需要实现的功能类似。只用一个程序盒子就来控制其他程序的开和关!但不仅仅是预定义进去的几个程序。而是在将来会有更多的程序添加进去。但整个盒子的运作必须能够正常使用。也就是说,不管添加多少应用程序,都能够实现用盒子来操作应用程序的开关等操作。
那现在我们面临的问题是什么?记住。设计模式是用来解决问题的。
各种不同程序将会有不同的方法。并且这个盒子必须能够对这些程序发出正确的指令,让其实现正确的功能。可能会有一种好的解决方案,就是将盒子的程序做得简单点,让他从其他的程序里面解耦出来。尽量保证他的简单。但这样一来似乎又不可能,因为在用户点击盒子里面的按钮的时候必须执行正确的程序。你总不希望你的程序里充满if...elseif...elseif...这样的结构。因为他是硬编码进去的。将来需要添加新的应用程序,你又要修改。并且很有可能你还要修改你的程序来复合这个盒子。
那有什么好的办法来使程序和盒子达到解耦呢?那就是今天要讲的命令模式!它能够做到。他将这个过程简单化为命令的发出者对象和命令的接受者对象。执行的是对象与对象之间的操作。这样依赖盒子就只需要知道在按了按钮后马上执行一个命令,但命令的具体内容他完全不需要知道。因为这个命令执行的具体知识是封装在命令接受者里面的。
可能这样说还是不能理解。我用一个具体的例子来说明:
到餐厅里面点菜,我们都要有这样一个过程:点菜--->服务员将订单送到厨房-->然后厨师开始做菜。
将这个过程对象化。
点菜:客户将一个包含了菜单的order写好了。传递给服务员
服务员调用这个order。然后对着厨房叫一声:准备饭菜。将整个菜单给厨师。
这样的过程就完成了一个命令模式。重点在服务员。和厨师之间的关系。服务员只需要调用准备饭菜这样的方法,厨师就开始做菜了。他们两个之间不需要任何沟通。因为服务员知道订单上面一定有这个方法,他只需要调用就可以了。厨师听到调用了准备饭菜。马上开始准备饭菜。他们两个之间就是完全解耦的
好了。看看这个例子我们怎么来写。可能你还比较迷茫。所以先从简单的开始:
[php]
/**
* 实现命令接口。确保每个命令都有execute这个方法。。
*/
interface Command{
public function execute();
}
[/php]
再然后来写命令类
[php]
/**
* 实现命令接口。文本编辑器的控制例子。假设在文本编辑器的控制程序上有一个on()和off的方法
*/
require 'soft.php';
class EditOnCommand implements Command {
private $Edit;
//实例化的时候要绑定命令到底指向哪个对象。完成全部命令发送者的封装
public function __construct(Edit $_edit){
$this->Edit = $_edit;//存储好这个对象的引用,以后可以多次调用。
}
public function execute(){
$this->Edit->on();
}
}
class EditOffCommand implements Command {
private $Edit;
//实例化的时候要绑定命令到底指向哪个对象。完成全部命令发送者的封装
public function __construct(Edit $_edit){
$this->Edit = $_edit;
}
public function execute(){
$this->Edit->off();
}
}
[/php]
这样他就成了命令的发布者。所有需要发布命令的控制器,只需要调用上面这个类的execute方法。当然。需要将他绑定到你设计的控制器上去。我这里简单点。就两个按钮,一个按钮是开,一个是关。只控制这个文本编辑器
[php]
/**
* 文本编辑器开关控制
*/
class EditControl{
private $oncommand;
private $offcommand;
//将命令发送者绑定到命令接收器上面来。
public function addCommand(Command $_oncommand,Command $_offcommand){
$this->oncommand = $_oncommand;
}
public function onButtonPress(){
$this->oncommand->execute();
}
public function offButtonPress(){
$this->offcommand->execute();
}
}
[/php]
测试时候到了
[php]
/**
* 简单测试
*/
//先实例化一个控制器
$control = new EditControl();
$edit = new Edit();//这里创建一个编辑器的对象,也就是命令的接受者。
//实例化一个命令的对象,将命令接受者绑定给他。将其绑定到控制器上
$oncommand = new EditOnCommand($edit);
$offcommand = new EditOffCommand($edit);
$control->addCommand($oncommand,$offcommand);
//模拟按钮被按下来的情况
$control->onButtonPress();
$control->offButtonPress();
[/php]
哦。还忘记了我们的文本编辑器类
[php]
<?php
/**
* 软件集合类
*/
class Edit{
public function on(){
echo "文本编辑器被打开</br>";
}
public function off(){
echo "文本编辑器被关闭";
}
}
?>
[/php]
测试的结果
[quote]
文本编辑器打开
文本编辑器关闭
[/quote]
逻辑搞清楚后,这个过程非常容易理解。
EditOnCommand 和 EditOffCommand类封装具体的命令和命令接受者的绑定
EditControl类是接受者,他绑定上命令发送者。在模拟按钮(onButtonPress)的时候就能执行具体的命令。但这个按钮按下去他自己是不知道他在执行哪一个东西的命令。只知道自己执行了这个命令发送者定义好的方法。也就是服务员调用了餐厅老板定义好的:“准备饭菜”这样的方法。具体执行是在命令类的内部。所以完成了命令发布者和接受者之间的解耦。
命令模式还有一个特性是能够撤销命令的
看看我们怎么来实现他。
在命令接口里面添加这样一个方法。
[quote]
public function upcmd();
[/quote]
然后在具体命令类里面实现这个方法:
[php]
class EditOnCommand implements Command {
private $Edit;
public function __construct(Edit $_edit){
$this->Edit = $_edit;//存储好这个对象的引用,以后可以多次调用。
}
public function execute(){
$this->Edit->on();
}
//这里是为了撤销命令的
public function upcmd(){
$this->Edit->off();
}
}
[/php]
接着是在控制器里面完成调用。你需要记录上一步的状态。所以需要一个变量来记录。
[php]
class EditControl{
private $oncommand;
private $offcommand;
private $upcmd;
public function addCommand(Command $_oncommand,Command $_offcommand){
$this->oncommand = $_oncommand;
$this->offcommand = $_offcommand;
}
public function onButtonPress(){
$this->oncommand->execute();
$this->upcmd = $this->oncommand;//执行完命令后记录下这个状态
}
public function offButtonPress(){
$this->offcommand->execute();
$this->upcmd = $this->offcommand;//执行命令后记录下这个状态
}
public function uncmdButtonPress(){
$this->upcmd->upcmd();//返回上一步的命令状态
}
}
[/php]
完成~
测试
在初始化上面都是一样的。
然后是模拟按钮有点不同
[quote]
//模拟按钮被按下来的情况
$control->onButtonPress();
$control->uncmdButtonPress();
$control->offButtonPress();
$control->uncmdButtonPress();
[/quote]
结果是
[quote]
文本编辑器被打开
文本编辑器被关闭
文本编辑器被关闭
文本编辑器被打开
[/quote]
当然你可以在代码里面控制得更好。比如说已经关闭了了再按关闭是没效果的。
例子我只举一部分。。。需要看全部代码的我没打算把这个例子的全部代码写出来,可能会应用到我的类库中。所以关注我的blog吧。当完成这个模式的应用时我会发表新的日志的。当然。作为初学,希望你能来完成这个程序盒子的全部模拟过程。中间出现什么问题可以和我沟通。比如说,如果撤销的状态前一部是有属性的呢(编辑器的初始化配置)等等。
现在你大概的了解了整个过程中命令模式类和是如何互动了。
现在让我们来定义命令模式
[quote]
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
[/quote]
我们知道一个命令对象通过在特定接受者上绑定一组动作来封装一个请求(就像上面的EditOnCommand类需要绑定Edit的对象,然后将动作绑定到他的execute方法中),要达到这一点,命令对象将动作和接受者包进对象中。这个对象只会暴露出一个execute方法。此方法被调用的时候,接收者就会进行这些动作。从外面来看。其他对象并不知道究竟哪个接收者进行了哪些动作。只知道如果调用execute方法,请求的目的就能达到。这样做到完全的解耦。
我们也看到了利用命令来参数化对象的一些例子。我们先用打开文本编辑器命令。然后可能以后又用到打开图片编辑器(具体例子自己实现。我就不写了)的命令。就和餐厅里的服务员一样。控制器并不在乎所拥有的是什么命令对象,只要这个命令对象实现了Command接口就可以了。
下面让我们来看看类图是怎么回事。可能更为清晰
[attach]27215[/attach]
具体的就这些了。说明都在类图上面的。不多解释了。
命令模式还能在三个方面进行应用:
宏命令(多个或一组对象同时进行命令请求。比如说一次打开多个程序。);
队列请求(php本身不支持多线成,所以这个应用不进行解释)
日志请求(比如说大量数据存储都进行日志存储,当出错后调用execute返回到日志的上一步错误点。可以说这个命令模式的应用也能应用到编辑器的保存数据上的设计上。概念是差不多的)
但具体怎么应用,暂时没有好的例子。所以。。关注鄙人的博客吧。在这个帖子只是介绍这些设计模式。不打算对每个应用都进行详细的论述。
本来鄙人是要把这个模式的例子全部完成的。但是担心是在做授人以鱼而不是渔了。所以只完成一部分。剩下的自己完成,出现问题可以到这个帖子下面跟帖,也可以到我博客给我留言。
[[i] 本帖最后由 某个人 于 2008-11-17 14:39 编辑 [/i]]
第七章 随遇而安 适配器和外观模式
占楼用的第八章 封装算法 模板方法模式
占楼用的第九章 管理良好的集合 迭代器和组合模式
今天,我们来讲迭代器模式。事实上,迭代器模式在php中以SPL内置的各种迭代器已经完全满足我们使用的需求。有特殊需求的只要重写SPL提供的类和实现特定的接口就能完成了。但是,我还是希望我们能够了解一个迭代器具体是如何工作的。
首先我们可能经常遭遇一个问题。
就是数组,散列表或者对象列表这些等等元素的集合处理问题。比如说在无限分类中可能要涉及到处理数组。也可能自定义一群对象后,通过数组存储。我们经常操作的系统目录也属于集合的范畴。数据库的查询。。等等等。集合的涉及范围是非常广的。那我们就希望有一个统一的接口来完成一个迭代工作。而不用担心具体的迭代过程是怎么样的。简单的说:迭代器让我们能够游走于聚合内的每一个元素,而又不暴露其内部的表示(比如循环。各种不同的集合的循条件是不同的。)。
如果不明白?我可以举个例子。比如我们需要对一个字符串的每个字节进行处理。
那就可能for($i=0;i<strlen($string);$i++)这样操作来处理字符串的每个字节。
另一个例子是数组。可能会for($i=0;i<count($array);$i++)这样来操作处理的字符串。
当我们同一个项目里不停的使用循环的时候。就得不停的转换两种循环模式和循环条件。面向对象是Everything is Object!
当我们把数组或者其他形式的集合通过对象的形式调用和处理的时候。可能会遭遇一些问题。比如说下面这个现实生活中的例子。
有两家书店。他们卖了很多书(废话。去死.囧)。他们加入了一个书市联盟。联盟希望他们提供自己店的书目清单。看起来不错的主意,但是出现了一个问题。
两家书店的书目存储的方式完全不同。A书店用的是数组存储。B书店因为种种原因用的字符串处理。可联盟又希望获取到他们的数目清单。这该如何操作呢?毕竟两家书店的数据都是庞大的。不可能因为一个联盟的要求而改变自己的数组存放方式。A和B也并不愿意这样。
好的。先看看他们两家的代码如何。可能会在这里找到问题的结症。
书店A:
[php]
<?php
/**
* 数目是用数组存储的
*/
class BookA{
private $bookarray = array();
public function __construct(){
$this->addItem("深入浅出设计模式");
$this->addItem("think in java");
$this->addItem("php手册");
}
public function addItem($_string){
$this->bookarray[]=$_string;
}
public function getBookName(){
return $this->bookarray;
}
}
?>
[/php]
书店B
[php]
<?php
/**
* 书目都是用字符串存储的
*/
class BookB{
private $bookindex="";
public function __construct(){
$this->addItem("深入浅出设计模式");
$this->addItem("PHP");
$this->addItem("think in java");
}
public function addItem($_string){
$this->bookindex.="|".$_string;
}
public function getBookIndex(){
return trim($this->bookindex,"|");//附带的处理而已
}
}
?>
[/php]
现在有一个共同的书目手册让他们输出自己的书目列表。
[php]
class BookList{
private $bookarray;
private $bookstring;
public function __construct(){
$bookA = new BookA();
$this->bookarray = $bookA->getBookName();
$bookB = new BookB();
$this->bookstring = $bookB->getBookIndex();
//看起来上面的两个都是一样的。但实际上类型完全不同。
}
public function toString(){
//由于类型完全不同。你就必须得写两个循环来处理他们。
echo "书店A的书目:"."</br>";
foreach ($this->bookarray as $value){
echo $value."</br>";
}
echo "书店B的书目:"."</br>";
//字符串你没办法遍历。只能处理一下,然后转化成数组再输出。
$array = explode("|",$this->bookstring);
foreach ($array as $value){
echo $value."</br>";
}
}
}
[/php]
看起来并没有什么问题。但是,如果有更多的书店来加入这个联盟呢?而有的是用XML。有的是用数据库。甚至有的是用单一对象呢?
难道有多少就要做多少个循环处理吗?
no!绝对不行。记得一直强调的设计原则吗?一定一定要记住!改变是面向对象的敌人。一切问题都是因为改变而带来的。
改变的地方很明显了。就是输出时要进行多次的循环处理。
那用迭代器吧,将所有的循环封装起来。不再暴露出循环处理过程。
迭代器是个接口动作。所以我们需要创建一个接口,然后为这个接口实现提供一个迭代类。
当然,在此之前。我们必须了解迭代器是如何工作的。
[quote]
$interator = $BookA->getIteratro();//返回一个迭代器对象。
while($interator->hasNext()){
$next=$interator->next();
}
[/quote]
只是BookA和BookB两者都通过getItterator方法返回迭代器对象。这个对象封装好了hasNext方法判断是否有下一项。而next方法封装跳到下一项。这样一来。对于外部来讲。只知道BookA正在遍历内部的对象。而不需要知道任何这个便利过程。好了。让这就是我们的迭代器模式的威力。。看看我们如何实现他吧。跟着我动手吧。
可能你还不知道该如何下手。记住。从上往下写,总有帮助的。所以先来完成接口
[php]
<?php
/**
* 迭代器的公用接口
*/
interface NewIterator{
public function hasNext();
public function Next();
}
?>
[/php]
实现具体的迭代器
[php]
<?php
/**
* 书目的迭代器,实现NewIterator接口
*/
class BookIterator implements NewIterator {
private $array = array();//记录整个内容
private $num = 0;//记录索引
public function __construct($_string){
//因为在我的例子里需要这样处理。
if (is_array($_string)){
$this->array = $_string;
}else{
$this->array = explode("|",$_string);
}
}
public function next(){
//记录下一项的内容
$arrayA = $this->array[$this->num];
//索引增加1
$this->num = $this->num + 1;
return $arrayA;
}
public function hasNext(){
if($this->num >= count($this->array) || $this->array[$this->num] == null){
return false;
}else{
return true;
}
}
}
?>
[/php]
对书店的书目调用得改,需要返回一个迭代器对象
[php]
<?php
/**
* 数目是用数组存储的
*/
class BookA{
private $bookarray = array();
public function __construct(){
$this->addItem("深入浅出设计模式");
$this->addItem("think in java");
$this->addItem("php手册");
}
public function addItem($_string){
$this->bookarray[]=$_string;
}
//这里不再返回一个数组。而是一个真正的对象。数组被传递到了迭代器中。实现和书目调用的解耦
public function getIterator(){
return new BookIterator($this->bookarray);
}
}
?>
[/php]
同样的书店B
[php]
<?php
/**
* 书目都是用字符串存储的
*/
class BookB{
private $bookindex="";
public function __construct(){
$this->addItem("深入浅出设计模式");
$this->addItem("PHP");
$this->addItem("think in java");
}
public function addItem($_string){
$this->bookindex.="|".$_string;
}
public function getIterator(){
return new BookIterator(trim($this->bookindex,"|"));//附带的处理而已
}
}
?>
[/php]
最后,对我们的书目做修改
[php]
<?php
/**
* 输出两个书店的书目
*/
require "NewIterator.php";
require 'BookA.php';
require 'BookB.php';
require "BookIterator.php";
class BookList{
private $bookarray;
private $bookstring;
public function __construct(BookA $_booka,BookB $_bookb){
$this->bookarray = $_booka;
$this->bookstring = $_bookb;
//改装成了只记录对象引用;
}
public function Menu(){
$bookaiterator = $this->bookarray->getIterator();
echo "书店A的书目:"."</br>";
$this->toString($bookaiterator);
echo "</br>";
$bookbiterator = $this->bookstring->getIterator();
echo "书店B的书目:"."</br>";
$this->toString($bookbiterator);
}
public function toString(NewIterator $_iterator){
while ($_iterator->hasNext()){
echo $_iterator->Next()."</br>";
}
}
}
?>
[/php]
实现一下
$booka=new BookA();
$bookb=new BookB();
$a = new BookList($booka,$bookb);
$a->Menu();
显示和上面的例子是一样的。但是我们看看我们对这个例子做了这么多修改干了些什么。
让他不再针对的是具体的数组还是字符串了。管他的。我只知道书目正在遍历一个对象。他会给我看东西的。过程我并不需要知道。
让他更具扩展性。当第3个书店加入进来的时候,只需要修改menu方法。其他的都不用管。最重要的是。你不需要知道这个新的书店是什么东西存储的(当然,在我的例子里,我的迭代器只处理了数组和字符串的形式。SPL提供了更多的真正的迭代器,下面我会讲的)。
不再需要两个或者更多的循环。因为有人会完成这个工作的。一个循环遍历这个对象就够了。看仔细哦。这个循环的表象是在对这个对象进行循环。而不是任何特定的种类。解耦性良好。
当然。这还有缺陷,就是不是针对的公共的接口,而是两个不同的接口( 主要是说这里。public function __construct(BookA $_booka,BookB $_bookb))。当我们学习了SPL提供的封装的迭代器。这个问题会有很好的解决方案。
需要我画类图么?应该不需要。这个迭代器模式的类图是非常简单的。当然。。我也偶尔偷一下懒。不可能什么都要我来给你完成。还是自己动手吧。不懂的再来问我怎么做。
下面定义一下迭代器到底是怎么回事。其实上面已经说了他的定义。再来回顾一下把:
[quote]
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
[/quote]
当然,我的例子里只是对数组这样操作了。看其来并没多大意义。但是,迭代器最大的意义是让你可以将任何形式的循环遍历都能够像对数组一样简单的操作。迭代器这样做是很有意义的:这个模式提供了一种方法,可以顺序访问一个聚集对象中的元素,而又不用知道内部是如何表示的。
另一个对你的设计造成重要影响的,是迭代器模式把在元素之间游走的责任交给了迭代器,而不是聚合对象。这不仅让聚合的接口和实现变得简介,也可以让聚合更专注于在它所应该专注的事情上面,而不必去理会遍历的事情。
迭代器原理讲解完毕。下面要讲的是SPL提供的种种迭代类库。可能有人会问为什么一开始不讲SPL的迭代器。道理很简单。因为你不知道本质是如何运行的。你就不知道如何去使用。这是软件工程的基本原理。虽然封装很重要。但那是针对客户,而不是你自己。你必须大致的了解如何创建一个迭代器。才能更好的使用SPL提供的迭代器。因为总有不顺手的武器,既然已经提供了种种接口,就用那些零件拼装出适合自己的武器吧。然而如果你不知道武器的原理。你根本不可能完成这一点
[[i] 本帖最后由 某个人 于 2008-11-19 16:57 编辑 [/i]]
第十章 事物的状态 状态模式
占楼用的第十一章 控制对象的访问 代理模式
占楼用的第十二 模式中的模式 复合模式
占楼用的 [quote]原帖由 [i]studyphp[/i] 于 2008-11-2 16:52 发表 [url=http://bbs.phpchina.com/redirect.php?goto=findpost&pid=684995&ptid=87905][img]http://bbs.phpchina.com/images/common/back.gif[/img][/url]dz的什么时候更新啊。 [/quote]
我刚下玩CHM手册和搭建好环境。。。还在下zend。。万恶的网速。。。。 支持楼主哦!精神值得敬佩,坐下来好好学习一下啦!楼主要注意单词的拼写哦。manager. 而不是楼主的Mannage 设计模式的本质都是为了面向对象的那几个基本原则
首先你得让大家知道为什么会有这些设计模式.不能盲目的去学习.
最好先介绍一下 开闭原则 里矢代换原则 依赖倒转原则
比如策略模式和这些原则的关系:
我们依赖于抽象策略而不是具体策略 --依赖倒转原则
每一种具体的策略都能取代抽象策略的位置 --里矢代换原则
每当出现新的策略,直接添加,而不需要任何修改 --开闭原则 tukiz15
同意ZF的,
基本原则, 是OO设计的核心.
模式, 是设计的经验
《深入浅出 设计模式》是本好书, 很适合初学者 刚配置了半天php环境。。vista上配置总是出些稀奇古怪的错误。一上来就看到ZF大人的建议。说得很好。本来我也是这么考虑的。但我结合自己的一些经历。。如果有个人单独跟我说这些原则,我会觉得很枯燥。所以学深入浅出这本书中的办法,把原则写在每个例子中,这样更容易理解一点。。
不过表现得不是很好哈。呵呵~~很好的意见。。以为自己懂了。。别人也明白了。。就一笔带过了。。过错过错。 更新了顶楼说明。。。还是把我对设计模式的粗浅理解整理了出来。
现在开始完成第二章 第二章 策略模式完。留了两道课后思考题目。明天来写第三章的时候来给答案。希望能看到一部分的思考过程
