java多线程    Java入门    vsftp    ftp    linux配置    centos    FRP教程    HBase    Html5缓存    webp    zabbix    分布式    neo4j图数据库    

面向对象的SOLID例子学习

这些年,月小升同学发现自己不会读书于是买了一本《如何阅读一本书》,发现自己不会做笔记就买了一本《如何做笔记》,写代码久了,发现自己一直在用的面向对象不是很了解,经常把代码写成一坨一坨的,于是回头来学习怎么面向对象。那些不熟练的基础,总要还债的。

出来混总是要还的

SOLID 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则

S: 单一职责原则 (SRP) Single Responsibility Principle
O: 开闭原则 (OCP) Open/Closed Principle
L: 里氏替换原则 (LSP) Liskov Substitution Principle
I: 接口隔离原则 (ISP) Interface Segregation Principle
D: 依赖反转原则 (DIP) Dependency Inversion Principle

1. 单一责任原则:


当需要修改某个类的时候原因有且只有一个(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。

举例子:不要让一个类,负责发邮件,还负责修改客户名称
不良代码:

class Email{
	public function sendemail(){
		//...
	}
	public function changeUsername(){
		//...
	}
}

改进版:

class Email{
	public function sendemail(){
		//...
	}
}

class User{
	public function changeUsername(){
		//...
	}
}

2. 开放封闭原则 Open/Closed Principle (OCP)

软件实体应该是可扩展,而不可修改的。也就是说,对"扩展是开放的,而对修改是封闭的"。这个原则是在说明应该允许用户在不改变已有代码的情况下增加新的功能。

实现开闭原则的关键就在于“抽象”。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。

看例子

name;
    }
}

class  car extends vehicle
{
    public function __construct()
    {
        parent::__construct();
        $this->name = 'car';
    }
}

class bike extends vehicle
{
    public function __construct()
    {
        parent::__construct();

        $this->name = 'bike';
    }
}

class task
{
    private $vehicle;

    public function __construct($vehicle)
    {
        $this->vehicle = $vehicle;
    }

    public function dotask($kilometer)
    {
        $vName = $this->vehicle->getName();

        if ($vName === 'car') {
            return $this->CarRun($kilometer);
        } elseif ($vName === 'bike') {
            return $this->BikeRun($kilometer);
        }
    }

    private function CarRun($kilometer)
    {
        echo '小汽车跑了'.$kilometer.'公里
'; } private function BikeRun($kilometer) { echo '自行车跑了'.$kilometer.'公里
'; } } echo ''; $car = new car; $bike = new bike; $task = new task($car); $task->dotask(10); $task = new task($bike); $task->dotask(10); ?>

这个例子,如果增加一个车,bus,那么就要改动task任务这个类的底层代码

改良版本

';
    }
}

class bike implements vehicle
{
    public function run($kilometer)
    {
        echo '自行车跑了'.$kilometer.'公里
'; } } class task { private $vehicle; public function __construct($vehicle) { $this->vehicle = $vehicle; } public function dotask($kilometer) { $this->vehicle->run($kilometer); } } echo ''; $car = new car; $bike = new bike; $task = new task($car); $task->dotask(10); $task = new task($bike); $task->dotask(10); ?>

改良后的车辆,任意增加新车型,都不会改动task的内容

2022.02.28 觉得接口在这里不好,应该用抽象类说明这个问题

';
    }
}
 
class bike extends vehicle
{
    public function run($kilometer)
    {
        echo '自行车跑了'.$kilometer.'公里
'; } } class task { private $vehicle; public function __construct($vehicle) { $this->vehicle = $vehicle; } public function dotask($kilometer) { $this->vehicle->run($kilometer); } } echo ''; $car = new car; $bike = new bike; $task = new task($car); $task->dotask(10); $task = new task($bike); $task->dotask(10); ?>

3. 里氏替换原则


当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
可以理解为:只要有父类出现的地方,都可以使用子类来替代。而且不会出现任何错误或者异常。但是反过来却不行。子类出现的地方,不能使用父类来替代。

定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
理解为“只要有父类出现的地方,都可以使用子类来替代”

定义2:所有引用基类的地方必须能透明地使用其子类的对象。
问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

不良的设计:违背了里氏替换原则
假设一个父亲厨师会个炒鸡蛋的手艺,辣椒炒鸡蛋,儿子应该会这个手艺,但是儿子不会用辣椒,只会用大葱,所以炒出来的鸡蛋不一样了。

';
    }
}

class son1 extends father{
    public function cookeEgg(){
        echo '大葱炒鸡蛋
'; } } echo ''; $f = new father; $f->cookeEgg(); $s1 = new son1; $s1->cookeEgg(); ?>
辣椒炒鸡蛋
大葱炒鸡蛋

违背了原则1:只要有父类出现的地方,都可以使用子类来替代。
现在父亲会辣椒炒鸡蛋,换成儿子来炒,结果儿子因为不敢碰辣椒,炒成了大葱烧鸡蛋。 这个儿子就不是好儿子。我们假设这个是大儿子。

改良版本:出来一个好的二儿子的样子

';
    }
}
class son2 extends father{
    public function cookeEggWithScallion(){
        echo '大葱炒鸡蛋
'; } } echo ''; $f = new father; $f->cookeEgg(); $s2 = new son2; $s2->cookeEgg(); $s2->cookeEggWithScallion(); ?>
辣椒炒鸡蛋
辣椒炒鸡蛋
大葱炒鸡蛋

这个儿子,复合了定义1,父亲出来的地方,儿子出来就能提到,父亲会辣椒炒鸡蛋,儿子也会,所以儿子自动继承,但是二儿子还会大葱炒鸡蛋,二儿子就会两个炒蛋了

按解决方案:不要重写父亲的方法
规则1: 不要重写父亲的方案
规则2: 所有孩子都会有父亲的技能(父亲能辣椒炒鸡蛋,儿子就会,父亲出现的地方,儿子就可以替代)
规则3: 孩子会额外的技能,自己单独再写函数。(子类出现的地方,父亲不一定能替代)

4. 依赖反转原则

1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
2. 抽象不应该依赖于细节,细节应该依赖于抽象

实际理解为:高级逻辑层代码,不要因为底层的模块代码改变而变动。

高层模块:业务逻辑层,比如群发邮件,我决定发给购买者和没有购买者,那么群发邮件这个send的工作就是业务逻辑层的高层模块,而决定哪些用户时购买者的底层数据库查询操作属于底层模块。

举例子:老张开宝马,这个动作,开车是业务逻辑层,宝马车跑动是底层。
不良设计

run();
	}
}

echo '';
$bwm = new Bwm;
$zhang = new Driver();
$zhang->drive($bwm);

$audi = new Audi;
$zhang->drive($audi); //奥迪我没法开了。 此处代码会报错。
?>

有个办法就是业务层,再写一个函数funciton driveAudi() 是不是很熟悉,我们因为要负责处理额外的情况,又写了个看起来很重复函数。

改良的版本,把车做成接口
宝马和奥迪都是来实现车的底层逻辑函数。这样再新车进入的时候,就不用改动逻辑层的代码了。

run();
	}
}

echo '';
$bwm = new Bwm;
$zhang = new Driver();
$zhang->drive($bwm);

$audi = new Audi;
$zhang->drive($audi);
?>

现在老张可以开宝马也可以开奥迪,你拿个大众,我也照样开。

再次理解这句话:高级逻辑层代码,不要因为底层的模块代码改变而变动。

5. 接口分离原则


不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

interface Employee
{
    public function work();

    public function eat();
}

class Human implements Employee
{
    public function work()
    {
        // ....working
    }

    public function eat()
    {
        // ...... eating in lunch break
    }
}

机器人雇员不能吃,但是被强迫必须实现吃的接口

class Robot implements Employee
{
    public function work()
    {
        //.... working much more
    }

    public function eat()
    {
        //.... robot can't eat, but it must implement this method
    }
}

改良版本

interface Workable
{
    public function work();
}

interface Feedable
{
    public function eat();
}

interface Employee extends Feedable, Workable
{
}

class Human implements Employee
{
    public function work()
    {
        // ....working
    }

    public function eat()
    {
        //.... eating in lunch break
    }
}

// robot can only work
class Robot implements Workable
{
    public function work()
    {
        // ....working
    }
}

对面向对象的领悟,有助于在大型代码量的工程里,实现有效分离函数,互不干扰,团队协作。

什么时候用抽象,什么时候用接口,抽象指向一类实体比如鸟,接口指向一类功能比如飞行,抓虫子

1.飞机会飞,鸟会飞,他们都继承了同一个接口“飞”;但是F22属于飞机抽象类,鸽子属于鸟抽象类;
2. 就像铁门木门都是门(抽象类),你想要个门我给不了(不能实例化),但我可以给你个具体的铁门或木门(多态);而且只能是门,你不能说它是窗(单继承),一个门可以有锁(接口)也可以有门铃(多实现)。门(抽象类)定义了你是什么,接口(锁)规定了你能做什么(一个接口最好只能做一件事,你不能要求锁也能发出声音吧(接口污染))。

参考资料:
https://blog.csdn.net/weixin_43198436/article/details/88739519
https://www.cnblogs.com/zgxblog/p/10614044.html
https://www.cnblogs.com/liebrother/p/10193334.html
https://www.cnblogs.com/bobdeng/p/8477072.html
https://www.cnblogs.com/554006164/articles/2530844.html


This entry was posted in JAVA, PHP and tagged , . Bookmark the permalink.
月小升QQ 2651044202, 技术交流QQ群 178491360
首发地址:月小升博客https://java-er.com/blog/solid-class-study/
无特殊说明,文章均为月小升原创,欢迎转载,转载请注明本文地址,谢谢
您的评论是我写作的动力.

Leave a Reply