一.单一职责
定义: 一个类承担的职责不宜过多,或者说就一个类而言,应该仅有一个引起它变化的原因
如果一个类的职责承担过多,如果涉及到其中每一个职责变动的时候,都要修改这个类,而且在我们要复用这个类中的其中一个职责的时候也没法做到复用。
class Act{
public function run(){
$data = $this->curl($url);
}
public function curl($url, $data = array(), $timeout = 5) {
$ch = curl_init();
if (!empty($data) && $data) {
if(is_array($data)){
$formdata = http_build_query($data);
} else {
$formdata = $data;
}
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $formdata);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_ENCODING, '');
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
看到项目里经常有把请求第三方接口的curl方法写在当前类里,比如上面的活动类Act耦合了curl方法,如果此时又有另外一个类要使用curl方法,这个类并不需要活动类的其它功能此时就不能复用Act的curl方法 只能在这个类里在增加一个curl方法,如此下去,随着类越来越多,项目里到处是curl方法,假如某一天需要把超时改成10秒,那就惨了,得一个个类里去修改。其实我们完全可以把curl方法放到curl类里,将Act类和curl解耦, 这样项目统一使用curl类的curl方法,新增功能的时候可复用Curl的curl方法,修改的时候只需修改Curl类。
class Act{
public function run(){
$data = Curl::curl($url);
}
}
class Curl{
public static function curl($url, $data = array(), $timeout = 5) {
/***代码略***/
}
}
二.开放封闭原则
定义: 软件实体(类,函数模块) 应该是可以扩展的,但是不可以修改
这应该是软件开发的理想境界,其实我们不可能做到完全对修改关闭,只是尽量做到即可。 比如游戏里的支付回调,可以抽象为3个步骤,验证第三方参数,发道具给玩家,返回报文给第三方。
abstract class Notify{
//回调状态码
protected $_status = 0;
protected abstract function checkSign();
protected abstract function SendProp();
protected abstract function OutputMessage();
public final function run(){
if($this->checkSign()){
if($this->SendProp()){
//成功写入数据记录
$this->_writeDBLog();
$this->_status = 1;
}else{
$this->_status = -1;//发道具错误
}
}else{
$this->_status = 2;//第三方参数错误
}
$this->OutputMessage();
}
private function _writeDBLog(){
echo "写入DB日志\n";
}
}
class Weixin extends Notify{
protected function checkSign(){
return true;
}
protected function SendProp(){
return true;
}
protected function OutputMessage(){
if($this->_status == 1){
die('ok');
}else{
die('fail');
}
}
}
$weixin = new Weixin();
$weixin ->run();
上面是一个第三方回调的主体代码,我抽掉了其他的业务逻辑,每个支付子类继承子一个抽象父类,这样做的好处是可以把公共的逻辑放到父类里,无需每个子类重复写公共逻辑,假如要新增支付宝, 只需要新增一个支付宝类,然后实现子类特有的业务逻辑即可。当然如果某天要新增文件日志,那必须改父类了,团队里开发人员水平参差不齐,我们可以把 Notify父类这个文件设置为指定人员可改。 这样基本做到了对扩展开防,对修改关闭。
三.里氏替换原则
定义: 子类型必须能够替换掉他们的父类型
意思是尽量不要重写父类的方法,因为父类的方法一般是公用的
由于世界上最好的语言是弱类型,这个原则PHP实在无法理解,这里使用java来理解这个原则
class Car{
String carType;
public Car(String carType){
this.carType = carType;
}
public void run(){
System.out.println(carType + "跑");
}
}
class HondaCar extends Car{
public HondaCar(String carType){
super(carType);
}
/*
public void run(){
throw new Exception();
}
*/
}
class FordCar extends Car{
public FordCar(String carType){
super(carType);
}
}
public class Client {
public static void main(String[]agrs){
Car h = new HondaCar("丰田");
h.run();
FordCar f = new FordCar("福特");
f.run();
}
}
子类型FordCar f对象,可以替换掉父类型Car h对象,但假如 HondaCar类重写父类的run方法,显然替换会造成程序异常。 然而有的时候我们不得不重写父类的方法,只要重写父类方法能给我们带来大利大于弊的时候即可。
四.依赖倒转原则
定义 : 抽象不应该依赖于细节,细节应该依赖于抽象,要针对接口编程而不是对具体实现编程
这个原则还是用java来理解
class Members{
public void set(){
Redis redis = new Redis();
redis.set("u_key", "u_value");
}
}
class Redis{
public void set(String key, String val){
System.out.println("Redis设置key:" + key + "value :" + val + "成功!");
}
}
public class Client {
public static void main(String[]agrs){
Members m = new Members();
m.set();
}
}
假如存贮换为Mysql那么我们得修改Members类,我们改下代码,增加一个Istore接口,Redis和Mysql类实现set方法。具体的类名我们可以写到配置文件里,然后通过反射实例化相应的对象, 换存贮只需要修改配置文件,增加对应的存储类即可做到符合开闭原则。
interface Istore{
public void set(String key, String val);
}
class Members{
public void set(){
Istore store = new Mysql();
store.set("u_key", "u_value");
}
}
class Mysql implements Istore{
public void set(String key, String val){
System.out.println("Mysql设置key:" + key + "value :" + val + "成功!");
}
}
class Redis implements Istore{
public void set(String key, String val){
System.out.println("Redis设置key:" + key + "value :" + val + "成功!");
}
}
public class Client {
public static void main(String[]agrs){
Members m = new Members();
m.set();
}
}
五.迪米特法则
定义 : 一个类应该尽量降低自己成员的访问权限,如果两个类不必发生直接通讯,那么这两个类就不应该直接发生相互作用,如果其中一个类要调用另外一个类的方法,可以通过第三者转发这个调用
这个原则强调尽量降低类和类之间的耦合度,一个处于松耦合的类一旦被修改,不会对关联的类造成太大的影响
class Cache{
function getResult(){
return array('id'=>1, 'name'=>'PHP');
}
}
class Mysql{
function getResult(){
return array('id'=>1, 'name'=>'PHP');
}
}
class Client{
public static function main(){
$data = array();
$cache = new Cache();
if(!$data = $cache->getResult()){
$db = new Mysql();
$data = $db->getResult();
}
print_r($data);
}
}
Client::main();
上面这个是我们经常碰到的业务场景,先从cache里取数据,取不到就从db里取,万一要加个文件里取,就得修改客户端配置,如果调用的地方很多,每个地方都得改。 此时根据迪米特法则完全可以引入一个对象当中间人,使得客户端不用直接和db,cache交互。
class Cache{
function getResult(){
return array('id'=>1, 'name'=>'PHP');
}
}
class Mysql{
function getResult(){
return array('id'=>1, 'name'=>'PHP');
}
}
class Proxy{
static function getResult(){
$data = array();
$cache = new Cache();
if(!$data = $cache->getResult()){
$db = new Mysql();
$data = $db->getResult();
}
return $data;
}
}
class Client{
public static function main(){
$data = Proxy::getResult();
print_r($data);
}
}
Client::main();
六.接口隔离原则
定义 : 不要建立庞大臃肿的接口,接口中的方法尽量少
如果违反这一原则,有时候不需要这些方法子类也不得不实现这个方法。
下面以高考作为一个栗子(纯属虚构), 最初教育部规定各省市考试有7科,部分文理,这样造成喜欢文科的也不得不考理科,喜欢理科的不得不考文科,上海表示抗议,要分文理科,于是上海私自做决定, 考试我还是考(实现全国卷统一接口),但是文科生理综不计入总分(空方法实现),理科生分科不计入总分(空方法实现), 接着北京,天津等直辖市纷纷效仿,深圳广州等城市还是按照综合考试,觉得这样有利于学生综合能力发展,这时候教育部想了个办法把学科分成主课+文+理(三个接口,这样做到了接口最小化), 愿意分文理就靠文科的,愿意综合的就靠所有的,这样大家都没意见了。
如果某些时候遵守规则让你开发效率更低,那还不如违反规则以得到更好的开发效率,规则是死的,人是活的,我们开发的时候尽量遵守这些规则即可