1. 什么是设计模式

    设计模式是一套被反复使用、容易被他人理解的、可靠的代码设计经验的总结。

    那么我们常说的架构、框架设计模式有什么关系呢?

    • 架构是一套体系结构,是项目的整体解决方案;
    • 框架是可供复用的半成品软件,是具体程序代码。
    • 架构一般会涉及到采用什么样的框架来加速和优化某部分问题的解决,而好的框架代码里合理使用了很多设计模式。

    比如:laravel框架中就使用到了很多的设计模式。其中包括单例模式,门面模式,注册树模式(DI/IOC),适配器模式,观察者模式等多种设计模式。

    设计模式的目的是为了更好的代码重用性,可读性,可靠性,可维护性。

  2. 设计模式的作用

    设计模式能解决:

    • 替换杂乱无章的代码,形成良好的代码风格
    • 代码易读,工程师们都能很容易理解
    • 增加新功能时不用修改接口,可扩展性强
    • 稳定性好,一般不会出现未知的问题

    设计模式不能解决:

    • 设计模式是用来组织你的代码的模板,而不是直接调用的库
    • 设计模式并非最高效,但是代码的可读性和可维护性更重要
    • 不要一味追求并套用设计模式,重构时多考虑
  3. 设计模式–六大原则

    • 单一职责原则

      1. 定义

        不要存在多于一个导致类变更的原因。既一个类只负责一项职责。

      2. 场景

        类T负责两个不同的职责;职责P1,职责P2,当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

        <?php
        class Animal
        {
            public function bre($animal)
            {
                echo $animal."呼吸空气";
            }
        }
        
        class Program
        {
            public function main()
            {
                $animal = new Animal();
                $animal->bre('猫');
                $animal->bre('狗');
                $animal->bre('鱼');
            }
        }
        
      3. 修正

        遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

        <?php
        class Ter
        {
                public function bre($animal)
            {
                echo $animal."呼吸空气";
            }
        }
        
        class Aqu
        {
                public function bre($animal)
            {
                echo $animal."呼吸水";
            }
        }
        
        class Program
        {
            public function main()
            {
                $ter = new Ter();
                $ter->bre('猫');
                $ter->bre('狗');
                $aqu = new Aqu();
                $aqu->bre('鱼');
            }
        }
        
      4. 优点

        • 可以降低类的复杂度,一个类只负责一项职责,逻辑简单;
        • 提高类的可读性,提高系统的可维护性
        • 变更引起的风险降低,变更时必然的。
    • 里氏替换原则

      1. 定义

        所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类可以扩展父类的功能,但不能改变父类原有的功能

      2. 场景

        有一个功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

        <?php
        class A 
        {
            public function sub($a, $b)
            {
                return $a-$b;
            }
        }
        
        class B
        {
            public function main()
            {
                $class = new C();
                echo $class->sub(100,50);
                echo $class->sub(90,40);
            }
        }
        
        class C extends A
        {
            public function sub($a, $b)
            {
                return $a+$b;
            }
        
            public function qua($a, $b)
            {
                return $this->sub($a, $b)*100;
            }
        }
        
        $b = new B();
        $b->main();
        
        <?php
        //修正
        abstract class D
        {
            public abstract function sub($a, $b);
            public abstract function qua($a, $b)
            {
                return $this->sub($a, $b)*100;
            }
        }
        
        //通过子类继承实现抽象方法达到扩展目的
        class A extends D 
        {
            public function sub($a, $b)
            {
                return $a - $b;
            }
        }
        
        class B extends D
        {
            public function sub($a, $b)
            {
                return $a + $b;
            }
        }
        
    • 依赖倒置原则

      1. 定义

        高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

      2. 场景

        类A(高层模块)直接依赖类B(低层模块),假如要将类A改为依赖类C(低层模块),则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

      3. 修正

        将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

        <?php
        class Book
        {
            public function getContent()
            {
                return 'hello world';
            }
        }
        
        class NewsPaper
        {
            public function getContent()
            {
                return 'new day';
            }
        }
        
        class Mother
        {
            public function narrate(Book $book)
            {
                echo $book->getContent();
            }
        }
        
        $m = new Mother();
        $m->narrate(new Book());
        
        //修正
        interface IReader
        {
            public function getContent();
        }
        
        class Book1 implements IReader
        {
            public function getContent()
            {
                return 'hello world';
            }
        }
        
        class NewsPaper1 implements IReader
        {
            public function getContent()
            {
                return 'new day';
            }
        }
        
        class Mother1
        {
            public function narrate(IReader $ireader)
            {
                echo $ireader->getContent();
            }
        }
        
        $m1 = new Mother1();
        $m1->narrate(new Book1());
        $m1->narrate(new NewsPaper1());
        
      4. 优点

        • 低层模块尽量都要有抽象类或者接口,或者两者都有
        • 变量的声明类型尽量是抽象类或接口
        • 使用继承时遵循里氏替换原则
    • 接口隔离原则

      1. 定义

        客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

      2. 场景

        类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须趣实现他们不需要的方法。

      3. 修正

        将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

        <?php
        interface I 
        {
            public function method1();
            public function method2();
            public function method3();
            public function method4();
            public function method5();
        }
        
        class A
        {
            public function depend1(I $i)
            {
            	$i->method1();    
            }
                public function depend2(I $i)
            {
            	$i->method2();    
            }
                public function depend3(I $i)
            {
            	$i->method3();    
            }
        }
        
        class C 
        {
                public function depend1(I $i)
            {
            	$i->method4();    
            }
                public function depend2(I $i)
            {
            	$i->method5();    
            }
        }
        
        class B implements I 
        {
            public function method1()
            {
        
            }
            public function method2()
            {
        
            }
            public function method3()
            {
        
            }
            public function method4()
            {
        
            }
            public function method5()
            {
        
            }
        }
        
        class D implements I 
        {
            public function method1()
            {
        
            }
            public function method2()
            {
        
            }
            public function method3()
            {
        
            }
            public function method4()
            {
        
            }
            public function method5()
            {
        
            }
        }
        
        $a = new A();
        $b = new B();
        $c = new C();
        $d = new D();
        $a->depend1($b);
        $a->depend2($b);
        $a->depend3($b);
        $c->depend1($d);
        $c->depend2($d);
        
        #----------------------修正---------------------
        class A1
        {
            public function depend1(IA $ia)
            {
            	$ia->method1();    
            }
                public function depend2(IA $ia)
            {
            	$ia->method2();    
            }
                public function depend3(IA $ia)
            {
            	$ia->method3();    
            }
        }
        
        class C1
        {
                public function depend1(IC $ic)
            {
            	$ic->method4();    
            }
                public function depend2(IC $ic)
            {
            	$ic->method5();    
            }
        }
        
        interface IA 
        {
            public function method1();
            public function method2();
            public function method3();
        }
        
        interface IC
        {
            public function method4();
            public function method5();
        }
        
        class B1 implements IA 
        {
            public function method1()
            {
        
            }
            public function method2()
            {
        
            }
            public function method3()
            {
        
            }
        }
        
        class D1 implements IC 
        {
            public function method4()
            {
        
            }
            public function method5()
            {
        
            }
        }
        
        $a1 = new A1();
        $b1 = new B1();
        $c1 = new C1();
        $d1 = new D1();
        $a1->depend1($b1);
        $a1->depend2($b1);
        $a1->depend3($b1);
        $c1->depend1($d1);
        $c1->depend2($d1);
        
      4. 接口隔离原则和单一职责原则的区别

        • 单一职责原则是注重这个类的职责,而接口隔离原则注重对接口依赖的隔离
        • 单一职责约束的是类,其次是方法,针对的是程序中的实现和细节,而接口隔离原则约束的是接口,是抽象,是程序框架整体的构建。
    • 迪米特原则

      1. 定义

        一个对象应该对其他对象保持最少的了解。(最少知道原则)

      2. 场景

        • 类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
        • 简单的理解就是高内聚低耦合,一个类尽量减少对其他对象的依赖,并且这个类的方法和属性能用私有的就尽量私有化。
        • 说明:
          • 高内聚:尽可能类的每个成员方法只完成一件事(最大限度的聚合)
          • 低耦合:减少类内部,一个成员方法调用另一个成员方法(包括对其他类的调用)
        <?php
        class Test
        {
            public function getAll()
            {
                $arr = [];
                for($i = 0; $i < 10; $i++){
                    $arr[] = $i;
                }
        
                return $arr;
            }
        }
        
        class Dev
        {
            public function getAll(Test $test)
            {
                for($i = 0; $i < 10; $i++){
                    echo  $i.'dev';
                }
        
                $arr = $test->getAll();
                foreach($arr as $val){
                    echo $val.'test';
                }
        
            }
        }
        
        $dev = new Dev();
        $dev->getAll(new Test());
        
        #----------------修正-----------------
        class Test
        {
            public function getAll()
            {
                $arr = [];
                for($i = 0; $i < 10; $i++){
                    $arr[] = $i;
                }
        
                foreach($arr as $val){
                    echo $val.'test';
                }
            }
        }
        
        class Dev
        {
            public function getAll(Test $test)
            {
                for($i = 0; $i < 10; $i++){
                    echo  $i.'dev';
                }
        
                $arr = $test->getAll();
            }
        }
        
        $dev = new Dev();
        $dev->getAll(new Test());
        
    • 开闭原则

      1. 定义

        一个软件实例如类、模块和函数应该对扩展开放,对修改关闭。

      2. 场景

        在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

      这六大原则任何面向对象的语言都应该遵守

    • 设计模式分类

      1. 创建型模式:

        概念:创建型模式的一个总要的思想其实就是封装,利用封装,把直接获得一个对象改为通过一个接口获得一个对象。

        单例模式、工厂模式(简单工厂、工厂方法、抽象工厂)

      2. 结构型模式:

        概念:解析类和对象的内部结构和外部组合,通过优化程序结构解决模块之间的耦合问题。

        适配器模式、门面模式、装饰器模式、注册树模式(IOC/DI)、代理模式、管道模式

      3. 行为型模式:

        概念:行为型模式用于描述程序在运行时复杂的流程控制,既描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

        策略模式、观察者模式、命令模式、迭代器模式