迪米特原则:迪米特原则(Law of Demeter,LoD),也叫最少知识原则(Low knowledge Principle,LKP):一个对象应该对其他对象有最少的了解。也可以解释为只和朋友类进行通信,所谓朋友类就是直接和一个类产生耦合关系的类,而这个朋友类就是实现两个类之间低耦合的关键!
单一职责原则(Single Responsibility Principle)–SRP:There should never be more than one reason for a class to change -- 应该有且仅有一个原因引起类的变更,也就是说一个类应该只负责一种特定的功能!好处:便于维护,稳定性高,简化代码逻辑,代码易于理解!例如下面的代码:
/***情景 1:动物用通过空气呼吸*****/ class Animal { private $name; public function __construct ($name) { $this -> name = name; } public function breath () { echo $this -> name . "呼吸空气!"; } } /***情景 2:有一天,突然发现鱼呼吸水的*****/ //扩展方式1:方法体层面,这个严重违反了单一责任原则,因为能够导致改代码的情况是所有动物都换了呼吸方式,这里只是部分水生动物的呼吸方式换了 class Animal { private $name; public function __construct ($name) { $this -> name = name; } public function breath () { if ($this -> name == "鱼") { echo $this -> name . "呼吸水!"; } else { echo $this -> name . "呼吸空气!"; } } } //扩展方式2:方法层面,一定程度违反单一职责原则 class Animal { private $name; public function __construct ($name) { $this -> name = name; } public function breath () { $this -> name . "呼吸空气!"; } public function breath () { $this -> name . "呼吸水!"; } } //扩展方式3:类层面,符合单一职责原则 class luSheng { private $name; public function __construct ($name) { $this -> name = name; } public function breath () { $this -> name . "呼吸空气!"; } } class shuiSheng { private $name; public function __construct ($name) { $this -> name = name; } public function breath () { $this -> name . "呼吸水!"; } }
依赖倒置原则(Dependence Inversion Principle ,DIP)定义如下:High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts. -- 高层次模块不能依赖于低层次模块,高低层次模块都应该依赖于抽象们;抽象不应该以来与实现的细节,实现细节应该依赖于抽象!总体概括为高低层次模块+实现细节都应该依赖于抽象们!DIP是面向接口编程,基本上面向接口编程都是符合dip原则的!注:低层次模块是高层次模块的组成部分 -- 高层次模块属于一个大的逻辑,而低层次模块属于颗粒原子逻辑,大逻辑由颗粒原子逻辑组成!代码例子如下:
//依赖注入的三种方式:构造方法注入,setter注入,接口注入 /***情景1:小红看红楼梦,看了一会,想看个小说*****/ class XiaoHong{ private $book; public function __construct (HongLouMeng $book) { $this -> book = $book; } public function read () { $this -> book -> read(); } } class HongLouMeng { public function read () { echo "我在看红楼梦,四大名著之一喔!"; } } class Novel { public function read () { echo "看会小说,斗破苍穹!"; } } //注:上面是一个强依赖,因为小红只能看《红楼梦》,不能看其他书! //另外,XiaoHong(高层次模块)依赖于HongLouMeng(低层次模块),因此违反了依赖倒置原则! //功能:声明读物接口 interface Read { public function read(); } //功能:声明阅读者接口,接口依赖注入 interface Reader { public function read (Read $book); } class XiaoHong implements Reader { private $book; public function read (Read $book) { $book -> read(); } } class HongLouMeng implements Read { public function read () { echo "我在看红楼梦,四大名著之一喔!"; } } class Novel implements Read { public function read () { echo "看会小说,斗破苍穹!"; } } //上面的设计就可以读到小说以及所有实现了 Read 接口的书了,真正从抽象层面实现了依赖
接口隔离原则:客户端只依赖于它需要的接口,多余的接口都不要,这样子的话就要求接口设计尽量细化!
/*****盗用别人的例子,因为实在太好了****/ /***情景1:星探要发掘美女啦,当代美女的标准是 脸漂亮、好身材、好脾气 ***/ interface IPretty { public function IsGoodLooking (); public function IsGoodBody (); public function IsGoodTemper (); } abstract class Searcher { public setStandardPretty (IPretty $girl); public showStandardPretty (); } class PrettyGirl implements IPretty { public $name; public function __construct ($name) { $this -> name = $name; } public function IsGoodLooking () { echo $this -> name . "脸很漂亮!"; } public function IsGoodBody () { echo $this -> name . "身材火爆!"; } public function IsGoodTemper () { echo $this -> name . "脾气很好!"; } } class MySearcher extends Searcher { private $girl; public function setStandardPretty (IPretty $girl) { $this -> girl = $girl; } public function showStandardPretty () { echo $this -> name . "的个人情况如下:"; $this -> girl -> IsGoodLooking (); $this -> girl -> IsGoodBody(); $this -> girl -> IsGoodTemper(); } } $girl = new PrettyGirl("玛丽"); $searcher = new MySearcher(); $searcher -> setStandardPretty ($girl); $searcher -> showStandardPretty (); /***情景2:出现了另外一种美女的标准:只需要好脾气就行了,怎么办呢,那个接口 IPretty 根本不能满足****/ interface IPretty1 { public function IsGoodLooking (); public function IsGoodBody (); } interface IPretty2 { public function IsGoodTemper (); } interface IPretty extends IPretty1, IPretty2 { } abstract class Searcher { public setStandardPretty (IPretty $girl); public showStandardPretty (); public setTemperPretty (IPretty2 $girl); public showTemperPretty (); } class PrettyGirl implements IPretty { public $name; public function __construct ($name) { $this -> name = $name; } public function IsGoodLooking () { echo $this -> name . "脸很漂亮!"; } public function IsGoodBody () { echo $this -> name . "身材火爆!"; } public function IsGoodTemper () { echo $this -> name . "脾气很好!"; } } class TemperPrettyGirl implements IPretty2 { public $name; public function __construct ($name) { $this -> name = $name; } public function IsGoodTemper () { echo $this -> name . "脾气很好!"; } } class MySearcher extends Searcher { private $girl; private $temper; public function setStandardPretty (IPretty $girl) { $this -> girl = $girl; } public function setTemperPretty (IPretty2 $girl) { $this -> temper = $girl; } public function showStandardPretty () { echo $this -> name . "的个人情况如下:"; $this -> girl -> IsGoodLooking (); $this -> girl -> IsGoodBody(); $this -> girl -> IsGoodTemper(); } public function showTemperPretty () { echo $this -> name . "的个人情况如下:"; $this -> girl -> IsGoodTemper(); } } $girl = new PrettyGirl("玛丽"); $goodTemper = new TemperPrettyGirl("苏玛丽"); $searcher = new MySearcher(); $searcher -> setStandardPretty ($girl); $searcher -> showStandardPretty (); $searcher -> setTemperPretty ($goodTemper); $searcher -> showTemperPretty (); //上面代码将接口细化了,所以可以灵活地组合,更加方便扩展
开闭原则:Software entities like classes,modules and functions should be open for extension but closed for modifications -- 软件实体,例如类、模块和函数,应该通过扩展来适应变化,不要通过修改代码来适应变化。例如下面的代码:
/***情景1:一个书店卖一本书,正常售卖,封装好的代码如下*****/ interface IBook { public function getName (); public function getPrice (); public function getAuthor (); } class NovelBook implements IBook { protected $name; protected $price; protected $author; public function __construct ($name, $price, $author) { $this -> name = $name; $this -> price = $price; $this -> author = $author; } public function getName () { return $this -> name; } public function getPrice () { return $this -> price; } public function getAuthor () { return $this -> author; } } /***情景2:书店活动,该书需要打折,怎么办呢,有如下三种方式****/ //方式1:在接口 IBook 中添加 public function getOffPrice ();方法,这样子做就是在改代码, //同时还需要修改NovelBook中的代码,如果还有其他书实现了这个接口的话也是需要修改它们当中的代码,所以方式1不可取! //方式2:在 NovelBook 这个实现类中修改代码,修改 getPrice() 或者添加一个方法 getOffPrice (),这样子是可行,但是 //并不是最优的方式 //方式3:继承 NovelBook ,产生一个子类,然后在覆盖掉那个 getPrice () 方法,通过扩展方式去实现, //对现有底层代码没有影响,在高层次模块中进行代码修改! class OffNovelBook extends NovelBook { public function __construct ($name, $price, $author) { parent::construct ($name, $price, $author); } public function getPrice () { if ($this -> price > 40) { return $this -> price * 0.9; } else { return $this -> price * 0.8; } } } //注:变化有逻辑变化和子模块变化两种: //前者是一段逻辑的变化,但是结果的处理方式不变,这样子可以直接修改代码! //后者是模块的变化,这样子可能会影响到很多相关联的模块,尤其是底层模块发生变化时!
里氏替换原则:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. -- 将一段代码中的父类对象替换为该类的子类对象,代码仍然可以顺利正确地执行,这样子就叫做符合里氏替换原则。这在数学上类比集合的包含关系,e 属于集合 A,对于集合 B 包含集合 A,e 属于集合 B!通俗来说,子类继承了父类中的一切,父类能够做到的,子类也可以;相反,子类的个性化操作,父类没有办法做到!
里氏替换原则有4个规范: 1.子类必须完全实现父类的方法。如果子类不能完整地实现父类的方法,或者父类的一些方法在子类中已经发生畸变, 则建议断开继承关系,采用依赖,聚集,组合等关系代替继承。代码走起: abstract class AbstractGun{ public abstract function shoot(); } class Handgun extends AbstractGun{ public function shoot(){ echo "手枪射击..."; } } class Rifle extends AbstractGun{ public function shoot(){ echo "步枪射击..."; } } class Cachine extends AbstractGun{ public function shoot(){ echo "机枪射击..."; } } class Soldier{ private $gun; //这里就是使用父类的地方,可以使用子类(具体实现类对象) public function setGun(AbstractGun $gun){ $this -> gun = $gun; } public function killEnemy(){ echo "士兵开始射击..."; $this -> gun -> shoot(); } } $soldier = new Soldier(); //设置士兵手握手枪 $soldier -> setGun(new Handgun()); $soldier -> killEnemy(); //设置士兵手握步枪 $soldier -> setGun(new Rifle()); $soldier -> killEnemy(); //设置士兵手握机枪 $soldier -> setGun(new Cachine()); $soldier -> killEnemy(); 2.子类可以有自己的个性 class Aug extends Rifle { public function ZoomOut () { echo "通过放大镜观察。。。"; } public function shoot () { echo "AUG射击。。。"; } } class Snipper { public function killEnemy(AUG $aug){ $aug -> zoomOut(); $aug -> shoot(); } } $snipper = new Snipper(); $snipper -> killEnemy(new AUG()); 输出: 通过放大镜观察。。。AUG射击。。。 $snipper = new Snipper(); $snipper -> killEnemy(new Rifle()); 输出: 报错,报ZoomOut方法不存在。。。 3.覆盖或实现父类的方法时输入参数可以被放大(放大的意思是范围更加广泛,也就是祖先类;缩小就是更加精确分类到具体类) 需要用到 Java 来阐述,因为PHP中是不同的机制! import java.util.Collection; import java.util.HashMap; import java.util.Map; public class Father{ public Collection doSomething(HashMap map){ System.out.println("父类被执行"); return map.values(); } } public class Son extends Father{ //这里其实相当于重载了,所以并没有覆盖掉父类的doSomething方法 public Collection doSomething(Map map){ System.out.println("子类被执行"); return map.values(); } } public class Client{ public static void main(Strings[] args){ //父类存在的地方,子类应该可以存在 //Father father = new Father(); Son father = new Son(); //这两个都会输出 "父类被执行" 这句话,因为这是重载,如果需要输出 "子类被执行" 这句话,则需要完全覆盖掉 //父类中doSomething方法,参数类型一模一样才行! HashMap map = new HashMap(); father.doSomething(map); } } 4.覆盖或实现父类的方法时输出结果可以被缩小 父类的一个方法的返回值是一个类型T,子类的相同方法的返回值为S,那么里氏替换原则就要求S必须小于等于T。 也就是返回值也可以是父子关系!