设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
七大原则
单一职责:
一个接口或类应该只有一种职能
一个类只完成它应该完成的职责
Demo:
class Phone{
//拨号
public function dial(){
return "dialing....";
}
//挂断
public function hangup(){
return "hangup!!!";
}
//发送短信
public function send(){
return "send info!!!";
}
//接收短信
public function receive(){
return "received!!!";
}
}
//若修改接收方式的话则所有依赖Phone类的都需要修改
//改进后:按职责拆分为两个接口
interface IConnection{
public function dial();
public function hangup();
}
interface ICommunication{
public function send();
public function receive();
}
class Phone implements IConnection, {
public function dial(){
return "dialing....";
}
public function hangup(){
return "hangup!!!";
}
}
class Communication implements ICommunication{
public function send(){
return "send!!!";
}
public function receive(){
return "received!!!";
}
}
好处:
降低类的复杂性,实现什么样的职责都有清晰的定义
提高可读性
提高可维护性
降低变更引起的风险,对系统扩展性和维护性很有帮助
开闭原则:
开放扩展,关闭修改
为什么这么做?
在开发阶段,我们都知道,如果对一个功能进行扩展,如果只是一味地对方法进行修改,可能会造成一些问题,诸如 可 能会引入新的bug,或者增加代码的复杂度,对代码结构造成破坏、冗余,还需要重新进行全面的测试。那么该怎么解决这些问题?很简单,这就需要系统能够支持扩展,只有扩展性良好的系统,才能在不进行修改已有实现代码的基础上,引进新的功能。
我们应该怎么做?
要做到开闭原则,就需要多使用抽象类或者接口,将相似的类进行抽象,将公有的功能引入到抽象类中,这样在进行扩展时,只需要依据抽象类,生成新的子类即可。
好处:
可复用性好
可维护性好
依赖注入原则:
要依赖于抽象,不要依赖于具体的实现(Spring)面向接口编程
为什么这么做?
减少类间的耦合性,提高代码的可读性和可维护性。
我们应该怎么做?
1、每个类尽量都有接口和抽象类,或者抽象类和接口都有。
2、变量的表面类型尽量是接口或者是抽象类。
3、任何类都不应该从具体类派生。(但是在做二次开发的时候,我们无法获得高层代码的时候例外),规则不是绝对的。
4、尽量不要覆写基类已经实现好的方法。
Demo:
比如有这么条需求,用户注册完成后要发送一封邮件,然后你有如下代码:
先有邮件类'Email.class.php'
class Mail{
public function send()
{
/*这里是如何发送邮件的代码*/
}
}
然后又注册的类'Register.class.php'
class Register{
private $_emailObj;
public function doRegister()
{
/*这里是如何注册*/
$this->_emailObj = new Mail();
$this->_emailObj->send();//发送邮件
}
}
然后开始注册
include 'Mail.class.php';
include 'Register.class.php';
$reg = new Register();
$reg->doRegister();
看起来事情很简单,你很快把这个功能上线了,看起来相安无事... xxx天过后,产品人员说发送邮件的不好,要使用发送短信的,然后你说这简单我把'Mail'类改下...
又过了几天,产品人员说发送短信费用太高,还是改用邮件的好... 此时心中一万个草泥马奔腾而过...
这种事情,常常在产品狗身上发生,无可奈何花落去...
以上场景的问题在于,你每次不得不对'Mail'类进行修改,代码复用性很低,高层过度依赖于底层。那么我们就考虑'依赖倒置原则',让底层继承高层制定的接口,高层依赖于接口。
interface Mail
{
public function send();
}
class Email implements Mail()
{
public function send()
{
//发送Email
}
}
class SmsMail implements Mail()
{
public function send()
{
//发送短信
}
}
class Register
{
private $_mailObj;
public function __construct(Mail $mailObj)
{
$this->_mailObj = $mailObj;
}
public function doRegister()
{
/*这里是如何注册*/
$this->_mailObj->send();//发送信息
}
}
下面开始发送信息
/* 此处省略若干行 */
$reg = new Register();
$emailObj = new Email();
$smsObj = new SmsMail();
$reg->doRegister($emailObj);//使用email发送
$reg->doRegister($smsObj);//使用短信发送
/* 你甚至可以发完邮件再发短信 */
上面的代码解决了'Register'对信息发送类的依赖,使用构造函数注入的方法,使得它只依赖于发送短信的接口,只要实现其接口中的'send'方法,不管你怎么发送都可以。上例就使用了" 注入 "这个思想,就像注射器一样将一个类的实例注入到另一个类的实例中去,需要用什么就注入什么。当然" 依赖倒置原则 "也始终贯彻在里面。" 注入 "不仅可以通过构造函数注入,也可以通过属性注入,上面你可以可以通过一个"setter"来动态为"mailObj"这个属性赋值。
好处:
减少类间的耦合性
提高系统的稳定
降低并行开发引起的风险
提高代码的可读性和可维护性。
里式替换原则:
里氏替换原则是对类继承的一种约束。对里氏替换原则有两种理解:
不能随便去继承不合适的,有多余方法或者属性的类。(例子1)
子类可以扩展父类的功能,但不能改变父类原有的功能。(例子2)
为什么这么做?
采用里氏替代原则可以增强程序的健壮性,版本升级的时候可以保持非常好的兼容性,即使增加子类,原有的子类也可以继续运行。
我们应该怎么做?
在引用基类的地方就能引用子类实现
//例子1
class Bird{
protect function fly(){
}
}
//翠鸟
class KingFisher extends Bird{
}
//鸵鸟
class Ostrich extends Bird{
//鸵鸟不会飞啊
}
//例子2
class A{
protected function add($a, $b){
return $a + $b;
}
}
//重载
class B extends A{
protected function add($a, $b){
return $a + $b + 100;
}
}
看了第二个例子,有人会说那岂不是和重载矛盾了。初看是有点,但仔细理解,并不矛盾,我们可以这样处理矛盾:
//例子2
class A{
protect function add($a, $b){
return $a + $b;
}
}
//重载
class B extends A{
protected function add($a, $b, $c){
return isset($c) ? $a + $b + 100 : $a + $b;
}
}
里氏替换原则包含一下几个隐藏含义:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
好处:
提高代码的重用性,子类拥有父类的方法和属性;
提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性;
迪米特原则:
一个对象应该对其他对象了解最少。
具体体现:
1、在类的划分上,应该创建有弱耦合的类;
2、在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
3、在类的设计上,只要有可能,一个类应当设计成不变类;
4、在对其他类的引用上,一个对象对其它对象的引用应当降到最低;
5、尽量降低类的访问权限;
6、谨慎使用序列化功能(类或接口在客户端变更,却未在服务端同步更新,引发序列化失败,,项目管理易疏忽);
7、不要暴露类成员,而应该提供相应的访问器(属性)。
为什么这么做?
降低类之间的耦合。
我们应该怎么做?
在应用中最直接的实现就是在两个类中间建一个中介类。但是这样可能会造成中介类的澎爆。
迪米特法则主要运用在观察者模式和中介者模式中
Demo:
class Teacher {
//老师对学生发布命令,清一下女生
public function commond(GroupLeader $groupLeader){
//初始化女生
for($i=0; $i<20; $i++){
$listGirls[] = new Girl();
}
//告诉体育委员开始执行清查任务
$groupLeader -> countGirls($listGirls);
}
}
class GroupLeader {
//有清查女生的工作
public function countGirls($listGirls = array()){
echo '女生的数量是:' . sizeof($listGirls);
}
}
class Girl {
}
class Client {
public static function doing() {
$teacher= new Teacher();
//老师发布命令
$teacher -> commond(new GroupLeader());
}
}
Client :: doing();
//改进后
class Teacher {
public function commond(GroupLeader $groupLeader){
//告诉体育委员开始执行清查任务
$groupLeader -> countGirls();
}
}
class GroupLeader {
private $_listGirls = array();
//传递全班的女生
public function __construct($listGirls){
$this -> _listGirls = $listGirls;
}
//有清查女生的工作
public function countGirls(){
echo "女生数量是:" . sizeof($this -> _listGirls);
}
}
class Girl {
}
class Client {
public static function doing() {
//初始化女生
for($i=0; $i<20; $i++){
$listGirls[] = new Girl();
}
$teacher= new Teacher();
//老师发布命令
$teacher -> commond(new GroupLeader($listGirls));
}
}
Client :: doing();
接口隔离原则:
一个接口或者类应该拥有尽可能少的行为(那么,什么叫尽可能少?就是少到恰好能完成它自身的职责)
不要迫使实现接口的类去实现和该类无关的方法。
为什么这么做?
避免让接口的实现类实现一些不必要的功能
我们应该怎么做?
建立单一的接口,不要建立臃肿的庞大的接口,也就是说接口的方法尽量少。
Demo:
interface IBird{
public function walk();
public function chirp();
public function fly();
}
class Ostrich implements IBird{
//鸵鸟实现,出问题了,不会飞
}
接口隔离原则看起来确实很简单,但要注意在拆分接口的时候的粒度,不能太细,例如这个例子不能把每个动作都写个接口吧
优先使用组合而不是继承原则:
组合Demo:
class person{
public $name;
public $gender;
public function say(){
echo $this->name," \tis ",$this->gender,"\r\n";
}
}
class family{
public $people;
public $location;
public function construct($p,$loc){
$this->people=$p;
$this->location=$loc;
}
}
$student=new person();
$student->name='Tom';
$student->gender='male';
$student->say();
$tom=new family($student,'peking');
以上代码中,定义了两个类,一个是person,一个是family;在family类中创建person类中的对象,把这个对象视为family类的一个属性,并调用它的方法处理问题,这种复用方式就叫“组合”。
网友评论