面向对象设计的五原则:单一职责原则、接口隔离原则、开闭原则、替换原则、依赖倒置原则。这些原则主要总结在RobertC.Martin的书《敏捷软件开发——原则、方法、与实践》中。这五个原则也是23种设计模式的基础。SingleResponsibilityPrincipleSinglePospirationPrinciple,SRP在MVC框架中,表单插入数据库字段过滤和安全检查是应该在控制层处理还是在模型层处理,这些问题都可以归为单一职责范围。单一职责有两层含义:避免将相同的职责分散到不同的类中一个类承担过多的职责遵守SRP的好处:降低类之间的耦合度,提高类的复用性在实际代码开发中的应用:工厂模式、命令模式、代理模式等。工厂模式(Factory)允许对象在代码执行时被实例化。之所以叫工厂模式,是因为它负责“生产”对象。以数据库为例,工厂需要的是根据不同的参数生成不同的实例化对象。它只负责生产对象,不负责对象的具体内容。定义一个适配器接口:定义MySQL数据库操作类:_dbLink=@mysql_connect($config->host.(empty($config->port)?'':':'.$config->port),$config->user,$config->password,true)){if(@mysql_select_db($config->database,$this->_dbLink)){if($config->charset){mysql_query("SETNAMES'{$config->charset}'",$this->_dbLink);}返回$this->_dbLink;}}//数据库异常thrownewDbException(@mysql_error($this->_dbLink));}/***执行数据库查询*@paramstring$query数据库查询SQL字符串*@parammixed$handle连接对象*@returnresource*/publicfunctionquery($query,$handle){if($resource=@mysql_query($query,$handle)){返回$resource;}}}?>SQLite数据库操作类:_dbLink=sqlite_open($config->file,0666,$error)){返回$this->_dbLink;}抛出新的DbException($error);}/***执行数据库查询*@paramstring$querydatabaseQuerySQLstring*@parammixed$handleconnectionobject*@returnresource*/publicfunctionquery($query,$handle){if($resource=@sqlite_query($query,$handle)){返回$resource;}}}?>定义一个工厂类,根据传入的参数生成需要的类:调用:$db=sqlFactory::factory('MySQL');$db=sqlFactory::factory('SQLite');命令模式分离“命令”的“请求者”和“命令的执行者”方面的职责模拟服务员和厨师的流程:cook=$cook;}publicfunctionexecute(){$this->cook->meal();//传递消息给厨师,让厨师做饭}}classDrinkCommandimplementsCommand{private$cook;//绑定命令接收者publicfunction__construct(cook$cook){$this->cook=$cook;}publicfunctionexecute(){$this->cook->drink();}}?>模拟顾客和服务员的流程:mealCommand=$mealCommand;$this->drinkCommand=$drinkCommand;}publicfunctioncallMeal(){$this->mealCommand->execute();}publicfunctioncallDrink(){$this->drinkCommand->execute();}}?>实现命令模式:$control=newcookControl;$cook=newcook;$mealCommand=newMealCommand($cook);$drinkCommand=newDrinkCommand($cook);$control->addCommand($mealCommand,$drinkCommand);$control->callMeal();$control->callDrink();接口隔离原则,ISP接口隔离原则(InterfaceSegregationPrinciple,ISP)表示客户端不应该强制实现一些不会被使用的接口。应该将胖接口分组并替换为多个接口。每个接口服务于一个子模块。简单地说,使用多个专用接口比使用单个接口要好得多。ISP的要点:1、一个类对另一个类的依赖应该以最小的接口为准。ISP可以做到不强迫客户(接口用户)依赖他们不用的方法,接口的实现类应该只作为单一职责角色出现(遵守SRP原则)。ISP可以减少客户端之间的交互——当一个客户端程序需要新的职责(需求变化)并强制改变接口时,影响其他客户端程序的可能性会很小。2、客户端程序不要依赖自己不需要的接口方法(函数)。ISP强调的是接口对客户端的承诺尽量少,一定要具体。接口污染是给接口增加不必要的责任。“接口隔离”其实就是定制化服务设计的原则。通过接口的多重继承实现不同接口的组合,从而对外提供组合功能——实现“按需提供服务”。对于接口的污染,使用以下两种处理方法:使用委托来分离接口。使用多重继承来分离接口。在委托模式中,两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象处理,如策略模式、代理模式等,都应用了委托的概念。开闭原则随着软件系统规模的不断增大,软件系统维护和修改的复杂度不断增加。这种困境促使法国工程院士贝特朗迈耶于1998年提出了“开闭原则”(Open-ClosePrinciple,OCP)原则,其基本思想是:行为的开放(Openforextension)模块必须是开放的并且支持扩展,而不是僵化。关闭(Closedformodification)在扩展模块的功能时,不应大规模影响或影响现有的程序模块。也就是说,开发者需要在不修改系统已有功能代码(源代码或二进制代码)的情况下,扩展应用系统的软件功能。总结一句话:一个模块在扩展性上应该是开放的,在可变性上应该是封闭的。开闭可以提高系统的扩展性和可维护性,但这也是相对的。以播放器为例,首先定义一个抽象接口:interfaceProcess{publicfunctionprocess();}然后扩展这个接口实现解码输出功能:classplayerEncodeimplementsProess{publicfunctionprocess(){echo"encode\r\n";}}classplayerOutputimplementsProcess{publicfunctionprocess(){echo"output\r\n";}}播放器的各种功能都在这里打开。只要遵守约定,实现流程接口,就可以为播放器添加新的功能模块。接下来是定义播放器的线程调度管理器。一旦播放器收到通知(可以是外部点击行为,也可以是内部通知行为),就会回调实际的线程处理:classplayProcess{private$message=null;publicfunction__construct(){}publicfunctioncallback(Event$event){$this->message=$event->click();if($this->messageinstanceofProcess){$this->message->process();}}}具体产品出来了,这里定义一个MP4类,这个类比较封闭,里面定义了事件处理逻辑:classMP4{publicfunctionwork(){$playProcess=newplayProcess();$playProcess->callback(newEvent('encode'));$playProcess->callback(newEvent('output'));}}最后是事件排序的处理类,负责对事件进行排序,判断用户或内部行为,生成正确的“线程”,供播放器内置的线程管理器调度:classEvent{private$m;公共函数__construct($me){$this->m=$me;}publicfunctionclick(){switch($this->m){case'encode':returnnewplayerEncode();休息;case'output':返回新的playerO输出();休息;}}}Run:$mp4=newMP4;$mp4->work();//打印结果encodeoutput如何遵守开闭原则实现开闭核心思想是抽象编程的核心思想抽象编程,而不是具体编程,因为抽象比较稳定,类依赖于固定的抽象,这样的修改是封闭的;通过面向对象的继承和多态机制,实现抽象体的继承。编写它的方法来改变固有的行为并实现新的扩展方法,因此它对扩展是开放的。1、在设计中充分运用“抽象”和封装的思想。一方面,需要找出软件系统中各种可能的“可变因素”,并封装起来;另一方面,一个可变因素不应该分散在多个不同的代码模块中,而应该封装到一个对象中。2、在系统功能编程实现中应用面向接口编程。当需求发生变化时,可以提供一个新的接口实现类来适应变化。面向接口的编程要求函数类实现接口,对象声明为接口类型。在设计模式中,装饰模式明显使用了OCP。代换原则代换原则又称里氏代换原则(LSP),其定义和主要思想如下:我们没有认真理性地思考应用系统中各个类之间的继承关系是否合适,派生类是否可以正确重写其基类中的一些方法等等。因此,经常会出现滥用继承或者错误继承等现象,给系统后期的维护带来很多麻烦。LSP声明:子类型必须能够替换它们的父类型并出现在父类型可以出现的任何地方。LSP主要是针对继承的设计原则,继承和派生(多态)是OOP的主要特征。如何遵守LSP设计原则:父类的方法必须在子类中实现或重写,派生类只实现其抽象类中声明的方法,不应给出多余的方法定义或实现。在客户段程序中,只需要使用父类对象,不能直接使用子类对象,这样就可以实现运行时绑定(动态绑定)。如果A和B这两个类违??反了LSP的设计,通常的做法是新建一个抽象类C作为这两个具体类的超类,将A和B的共同行为移到C上来解决A和B。B的行为并不完全一致。DependenceInversionPrincipleDependenceInversionPrinciple,DIP依赖??倒置简单来说就是将依赖倒置为依赖接口。具体概念如下:上层模块不应该依赖下层模块,它们都依赖一个抽象(父类不能依赖子类,它们依赖抽象类)。抽象不能依赖于具体,具体应该依赖于抽象。为什么要依赖接口?因为接口体现了问题的抽象,又因为抽象一般是比较稳定的或者比较少变化的,具体是多变的。因此,依赖抽象是实现代码扩展和运行时绑定(多态性)的基础:抽象类的子类只要实现,就可以被类的使用者使用。工作();}}classworkB{private$e;publicfunctionset(employee$e){$this->e=$e;}publicfunctionwork(){$this->e->working();}}$worka=newworkA;$worka->work();$workb=newworkB;$workb->set(newteacher());$workb->work();在workA中,工作方法取决于老师实施;在workB中,工作转为依赖抽象,通过参数传入需要的对象。在workB中,通过setter方法传入teacher实例,从而实现了工厂模式。由于这个实现是硬编码的,为了实现代码的进一步扩展,在配置文件中写了这个依赖,说明workB需要一个teacher对象,专门用一个程序来检测配置是否正确(比如如依赖的类文件是否存在)以及依赖加载配置的实现,这个检测程序称为IOC容器。IOC(控制反转)是依赖倒置原则(DIP)的同义词。依赖注入(DI)和依赖查找(DS)是IOC的两种实现方式。
