不一样的面向对象(三)

设计模式六大原则

里式替换原则(LSP)

定义:所有引用基类的地方都必须能透明地使用其子类进行替换(简单说就是:子类可以扩展基类的功能,但是不能改变基类原有的功能)

里式替换原则是继承复用的基石,只有当子类可以替换掉基类,且其它功能不受到影响,基类才算真正的能够被复用,子类可以在基类的基础上增加新的方法

里式替换原则核心就是继承,通过继承,引用基类的地方就可以使用子类的对象了

继承的优点

(1)代码共享,每个子类都拥有父类的方法和属性

(2)提高了代码的重用性

(3)子类可以在父类的基础上扩展自己特有的功能

继承的缺点

(1)继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法

(2)降低代码的灵活性。子类必须拥有父类的属性和方法

(3)增强了耦合性。当父类的常量、变量和方法被修改时,必需要考虑子类的修改

使用里式替换原则注意的点

(1)子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法

(2)子类中可以增加自己特有的方法

(3)当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

(4)当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

1、子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法

<?php


abstract class BaseClass
{
    public abstract function abstractAction();
    public function notAbstractAction() {
        echo "我是基类中的非抽象方法".PHP_EOL;
    }
}

class SonClass extends BaseClass {
    public function abstractAction()
    {

        echo "我是子类,我实现了父类的抽象方法";
    }

    public function notAbstractAction()
    {
        echo "我是子类,我重写了基类的非抽象方法";
    }
}

class SonClass2 extends BaseClass {
    public function abstractAction()
    {

        echo "我是子类2,我实现了父类的抽象方法";
    }
    
    public function mySelfAction() {
        echo "我是子类2,这是我特有的方法";
    }
}

//如果将参数由基类替换成子类,该方法输出的结果就会改变
function testFunction(BaseClass $obj) {
    $obj->notAbstractAction();
}

testFunction(new SonClass2());
testFunction(new SonClass());

输出:
我是基类中的非抽象方法
我是子类,我重写了基类的非抽象方

在上边的例子里边,子类SonClass重写了基类中的非抽象发方法notAbstractAction(),如果将参数中的基类替换成SonClass,就会导致打印的结果发生改变。这样就会导致在父类出现的地方,不能由子类完全替换,违背了“里氏替换原则”

2、子类中可以增加自己特有的方法

还是借用上边的那个例子,SonClass2类中实现了自己特有的方法mySelfAction,子类可以拥有自己特有的方法,去实现其他的业务逻辑

依赖倒置原则(DIP)

定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,其核心思想是:要面向接口编程,不要面向实现编程

通俗来说就是:抽象不应该依赖于细节,细节应当依赖于抽象。 换言之,要针对接口编程,而不是针对实现编程

因为细节很容易发生改变,不稳定。而抽象通常是一个规范,不经常改变

依赖倒置原则的实现方法
  • 每个类尽量提供接口或抽象类
  • 变量的声明类型应该尽量是接口或者是抽象类
  • 任何类都不应该从具体类派生
  • 使用继承的时候,要尽量遵循上边提到的里式替换原则

代码示例

<?php

//射手类
class Shooter
{
    //每一个射手都能够进行射击
    public function shot(Baili $obj) {
        $obj->shot();
    }
}

class Baili
{
    public function shot() {
        echo "百里开始射击了".PHP_EOL;
    }
}

class Mengya
{
    public function shot() {
        echo "蒙犽开始射击了".PHP_EOL;
    }
}

$shooter = new Shooter();
$shooter->shot(new Baili());

上边的类,正常的功能实现了,但是,因为射手类的射击(shot)这个功能是基于具体的类百里类(Baili)实现的,这就给以后的扩展带来了麻烦。这个时候有一个新的射手蒙犽,但是根据射手类的射击方法,蒙犽没法进行射击,除非对射手类的射击方法进行修改。按理说,只要是射手属性的英雄,就应该能够进行射击

改进,射手类中的射击方法,不应该依赖具体的某一个射手英雄,而应该是射手的泛指

<?php

//射手类
class Shooter implements BaseShooter
{
    //每一个射手都能够进行射击
    public function shot(BaseShooter $obj) {
        $obj->attack();
    }

    public function accack()
    {

    }
}

interface BaseShooter {
    public function shot(BaseShooter $shooter);
    public function accack();
}

class Baili extends Shooter
{
    public function attack() {
        echo "百里开始射击了".PHP_EOL;
    }
}

class Mengya extends Shooter
{
    public function attack() {
        echo "蒙犽开始射击了".PHP_EOL;
    }
}


$shooter = new Shooter();
$shooter->shot(new Baili());

这样再进行扩展的话,就会相对的容易一些,耦合度没有上边那种方式大

image.png

阅读 152

推荐阅读