深入理解设计模式 - 精通面向对象编程

silverwq
2022-09-27 / 0 评论 / 384 阅读 / 正在检测是否收录...

设计模式

介绍

面向对象编程里有 6 大原则和 24 种设计模式。

什么是设计模式

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

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

设计模式的种类

23 种设计模式里,可以按照以下 3 种进行分类:

  1. 创建型:如何创建对象

  2. 结构型:如何实现类和对象的组合的

  3. 行为型:类或者对象怎样交互,以及怎样分配职责

有一个“简单工厂模式”不属于Gof23种设计模式,但是大部分设计模式的书籍里,都会对它进行专门的介绍,所以目前设计模式的种类可以分为:

GoF的23种设计模式+简单工厂模式 = 24种设计模式

六大原则

单一职责原则 (Single Responsibility Principle)

指的是:类和方法的职能要单一。这样做好处非常多:可以降低复杂度,提高代码的重用性、可靠性、可读性、可维护性。所以这个原则很重要,一定要满足!

尽量避免方法功能的重复,不然不知道情况的人,就不知道调用哪个方法了,容易造成困扰。

  1. 对于php代码来说,就是class和method职责要单一

  2. 对于go语言来说,就是结构体和method职责要单一

但是,也要不能拆分的过细,不然类就会太多,耦合度就高,要追求高内聚,低耦合

package main

import "fmt"

type WorkerClothes struct {
}

func (w WorkerClothes) Style() {
    fmt.Println("穿工作服")
}

type ShopClothes struct {
}

func (s ShopClothes) Style() {
  fmt.Println("穿工作服")
}

func main() {
 // 每个类的职责单一,即使两个方法打印的结果都是一样,也不会歧义
   // 只需要从类的职责上就可以发现,一个是工作服,一个是逛街衣服
    wc := WorkerClothes{}
   wc.Style()

  sc := ShopClothes{}
 sc.Style()
}

里是替换原则(Liskov Substitution Principle)

指的是:将一个基类对象替换成它的子类对象,程序将不会有任何问题,主要是对类继承的一种约束,这样做好处是不会破坏原来的职能,提升可靠性:

  1. 不能随便继承不合适的、有多余方法或属性的类。(例子 1)

  2. 子类可以扩展父类的功能(比如增加方法),但不能改变父类原有的功能 (比如覆盖父类非抽象方法)。(例子 2)

package main

import "fmt"

// 抽象层
type Car interface {
 Run()
}

type Driver interface {
   Drive(car Car)
}

// 实现层,依赖抽象
type Benz struct {
}

func (b Benz) Run() {
  fmt.Println("benz is running...")
}

type Bwm struct {
}

func (b Bwm) Run() {
  fmt.Println("bwm is running....")
}

type ZhangSang struct {
}

func (z ZhangSang) Drive(car Car) {
 car.Run()
}

type LiSi struct {
}

func (l LiSi) Drive(car Car) {
 car.Run()
}

// 业务层,也要依赖抽象
func main() {
    var benz Car // 依赖抽象
    benz = new(Benz)
    var zs Driver // 依赖抽象
   zs = new(ZhangSang)
 zs.Drive(benz)

  var bwm Car // 依赖抽象
 bwm = new(Bwm)
  var ls Driver // 依赖抽象
   ls = new(LiSi)
  ls.Drive(bwm)

}

依赖倒置原则 (Dependence Inversion Principle)

指的是:依赖于抽象接口,不要依赖具体实现类,也就是针对接口编程。

如果保证业务逻辑层向下依赖抽象层,实现层向上依赖抽象层,这样我们只需要关心抽象层有什么方法即可!!,如果依赖具体类,我们还得去看具体类有哪些方法。

package main

import "fmt"

// 抽象层
type Car interface {
 Run()
}

type Driver interface {
   Drive(car Car)
}

// 实现层,依赖抽象
type Benz struct {
}

func (b Benz) Run() {
  fmt.Println("benz is running...")
}

type Bwm struct {
}

func (b Bwm) Run() {
  fmt.Println("bwm is running....")
}

type ZhangSang struct {
}

func (z ZhangSang) Drive(car Car) {
 car.Run()
}

type LiSi struct {
}

func (l LiSi) Drive(car Car) {
 car.Run()
}

// 业务层,也要依赖抽象
func main() {
    var benz Car // 依赖抽象
    benz = new(Benz)
    var zs Driver // 依赖抽象
   zs = new(ZhangSang)
 zs.Drive(benz)

  var bwm Car // 依赖抽象
 bwm = new(Bwm)
  var ls Driver // 依赖抽象
   ls = new(LiSi)
  ls.Drive(bwm)

}
<?php
interface IRead{
    public function getConent();
}

// 低层具体类
class Book extends IRead
{
    public function getConent() {
        echo "很久很久以前";
    };
}

// 低层具体类
class Newspaper extends IRead
{
    public function getConent() {
        echo "php的薪资很高";
    };
}

// 高层通用阅读类,可以阅读各种类型的文章内容
class YueDu {
    // 不应该直接依赖底层具体模块,例如Book、Newspaper类,而应该依他们的的接口
    public read(IRead $iread)
    {
        $iread->getConent();
    }
}

接口隔离原则 (InterfaceSegregation Principles)

接口隔离和单一职责类似,只是接口隔离针对的是接口,接口里的方法都应该和该接口的职能强相关的。这样做的好处是,可以提升接口的重用性、可读性

但是需要注意的是:接口尽量小,但是也不能拆分的太小。

迪米特原则 (Law of Demeter)

又叫做最少知道原则,一个对象应该对其他对象保特最少的了解,要使用的时候只需要调用相应的方法即可。这样做可以提升代码的重用性、可读性、可维护性。

具体做法就是高内聚低耦合

  1. 高内聚:将职能范围内的逻辑都封装在相应方法的里 (某一程度上说也算是单一职责原则)

  2. 低耦合:并且该方法要减少依赖,保持功能的独立性

开闭原则 (Open Close Principle)

很重要,写代码的时候,要注意啊自己的代码是否符合开闭原则。

指的是:对扩展开放,对修改关闭。这样做可以提升代码的可靠性。也就是对类的改动,是通过增加代码进行的,而不是修改源码

具体做法是:通过继承的方式,对原来的功能进行扩展,尽量不要去修改原来的功能,因为对旧代码进行修改,会容易出现问题,还要重新测试。

package main

import "fmt"

type AbstractBanker interface {
    DoBusi()
}

type SaveBanker struct {
}

func (s SaveBanker) DoBusi() {
    fmt.Println("进行了 存款业务")
}

// TranferBanker 新增转账业务,只需要增加代码即可,不需要修改原来的存款业务类
type TranferBanker struct {
}

func (s TranferBanker) DoBusi() {
  fmt.Println("进行了 转账业务")
}

func BankBusiness(banker AbstractBanker) {
    banker.DoBusi()
}

func main() {
   sb := SaveBanker{}
  sb.DoBusi()

 tb := TranferBanker{}
   tb.DoBusi()

 // 可以优化上面的代码
    BankBusiness(&SaveBanker{})
 BankBusiness(&TranferBanker{})
}

设计模式之-创建型模式

创建型模式的一个重要的思想其实就是封装,利用封装,把直接 new 获得一个对象,改为通过一个封装的类方法获得一个对象。

单例模式 (singleton)

我们把对象的生成从 new 改为通过静态方法的控制,使得我们总是返回同一个实例给调用者,确保了系统只有一个实例。

好处:使用单例模式,则可以避免大量的 new 操作消耗系统资源。

场景:

  1. 数据库连接实例使用单例模式,对数据库的操作很频繁,避免一直 new。

  2. 框架系统组件,可以设置为单例模式,保证整个框架都是只有一个对象

设计示例:3 私 1 公

class DbMysql {
    // 私有属性存储对象
    private static $ins = null;

    // 私有化构造函数,避免被外部new
    private function __construct(){}

    // 公有静态方法,创建实例
    public static function  createIns() {
        if(self::$ins){
            self::$ins = new self();
        }
        return self::$ins;
    }

    // 私有化克隆方法,避免在类外克隆
    private function __clone(){}
}

工厂模式

工厂模式是我们最常用的实例化对象模式,是用静态工厂方法代替 new 操作的一种模式。

好处:如果你想要更改所实例化的类名等,则只需更改该工厂方法内容即可,不需逐一寻找代码中具体实例化的地方 (nw 处) 修改了。为系统结构提供灵活的动态扩展机制,减少了耦合。

简单工厂

一个工厂,创建多种对象。如果要创新新的对象,则需要修改工厂类的静态方法

// 简单工厂
class DbMysql
{
    public function connect()
    {
        echo "连接mysql";
    }
}

class DbSqlLite
{
    public function connect()
    {
        echo "连接sqlLite";
    }
}

class DbFactory
{
    // 一个工厂,可以制造很多不同的商品
    public static function createIns($type)
    {
        if ($type == 'mysql') {
            return new DbMysql();
        } elseif ($type == 'sqlite') {
            return new DbSqlLite();
        }
    }
}
$mysql = DbFactory::createIns('mysql');
$mysql->connect();

工厂方法模式

每个对象都有专门的工厂类来创建。如果要新增一个类的对象,可以新增一个工厂类来创建,就不需要修改别的工厂,符合开闭原则

interface Db
{
    public function connect();
}
class DbMysql  implements Db
{
    public function connect()
    {
        echo "连接mysql";
    }
}
class DbSqlLite implements Db
{
    public function connect()
    {
        echo "连接sqlLite";
    }
}

interface Factory
{
    public static function createIns();
}
class MysqlFactory implements Factory
{
    public static function createIns()
    {
        return new DbMysql();
    }
}
class SqliteFactory implements Factory
{
    public static function createIns()
    {
        return new DbSqlLite();
    }
}
$mysql = MysqlFactory::createIns();

抽象工厂模式

一个工厂生产多种产品。例如百事公司的苹果汁、香蕉汁都放在百事工厂类里实现

//饮料接口
interface Fruit{
  function getFruitName();
}
class BaishiAppleFruit implements Fruit{
  function getFruitName()
  {
    echo '百事苹果味饮料';
  }
}
class BaishiBananaFruit implements Fruit{
  function getFruitName()
  {
    echo '百事香蕉味饮料';
  }
}

class ColeiAppleFruit implements Fruit{
  function getFruitName()
  {
    echo '可口可乐苹果味饮料';
  }
}
class ColeBananaFruit implements Fruit{
  function getFruitName()
  {
    echo '可口可乐香蕉味饮料';
  }
}

//工厂接口
interface FruITFactory{
  //生产饮料方法
  function makeAppleFruit();
  function makeBananaFruit();
}
//百事饮料工厂
class BaishiFruitFactory implements FruitFactory{
  function makeAppleFruit()
  {
    //生产百事苹果饮料
    return new BaishiAppleFruit();
  }
  function makeBananaFruit()
  {
    //生产百事香蕉饮料
    return new BaishiBananaFruit();
  }
}
//可口可乐饮料工厂
class ColeFruitFactory implements FruitFactory{
  function makeAppleFruit()
  {
    //生产可口可乐苹果饮料
    return new ColeiAppleFruit();
  }
  function makeBananaFruit()
  {
    //生产可口可乐香蕉味饮料
    return new ColeBananaFruit();
  }
}
$baishi_factory = new BaishiFruitFactory();
$baishi_factory->makeAppleFruit()->getFruitName();
$baishi_factory->makeBananaFruit()->getFruitName();

$cole_factory = new ColeFruitFactory();
$cole_factory->makeAppleFruit()->getFruitName();
$cole_factory->makeBananaFruit()->getFruitName();

设计模式之-结构型模式

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

适配器模式

比如 php 的返回的数据结构,java 不能解析,可以通过适配器,将返回结果转为 json,然后 java 就可以解析了。

interface Weather{
    public function show();
}

class PhpWeather implements Weather {
    public function show(){
        $info = ['weather'=>'小雨','tep'=>'6'];
        // 不能直接改这个,不然违法开闭原则
        return serialize($info);
    }
}

interface WeatherAdapter{
    public function getWeather();
}
class JavaWeather implements WeatherAdapter{
    protected $weather;

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

    public function getWeather(){
        $info = unserialize($this->weather->show());
        return json_encode($info);
    }
}

PHP 中的数据库操作有 MySQL, MySQLi, PDO 三种,可以用适配器模式统一成一致,使不同的数据库操作,统一成一样的 API。类似的场景还有 cache 适配器,可以将 memcache, redis, file, apc 等不同的缓存函数,统一成一致

装饰器模式

装饰器模式又叫装饰者模式。装饰模式是在不必改变原类文件(修改会违法开闭原则)和使用继承(继承会增加耦合)的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

interface IComponent {
    public function display();
}
// 不对这个类进行修改和继承
class Person implements IComponent{
    protected $name;
    public function __construct($name)
    {
        $this->name = $name;
    }
    public function display(){
        echo "装饰的{$this->name}";
    }
}

// 装饰类
class Clothes implements IComponent {
    protected $component;
    public function Decorate(IComponent $component)
    {
        $this->component = $component;
    }
    public function display()
    {
        if($this->component){
            $this->component->display();
        }
    }
}
class TShirt extends Clothes{
    public function display()
    {
        echo "T恤";
        parent::display();   
    }
}
class Shirt extends Clothes
{
    public function display()
    {
        echo "衬衫";
        parent::display();
    }
}

$qd = new Person('乔丹');
$tShirt = new TShirt();
$tShirt->Decorate($qd);
$tShirt->display();
$tShirt = new Shirt();
$tShirt->Decorate($qd);
$tShirt->display();

注册树模式

注册树模式也叫注册模式或注册器模式,顾名思义,注册树就是把对象实例注册到一棵全局的对象树上,需要对象的时候时候就从树上取下来,就好比树上涨的果子,需要的时候就摘一个下来,只是这个对象树果子是摘不完的。

注册树是控制反转(IOC)的思想:
控制:对象创建,属性赋值,对象生命周期管理【Bean 的生命周期】
反转:把管理对象的权限转移给了容器实现,由容器完成对象的管理
正转:使用 new 构造方法创建对象,开发人员掌握了对象的全部过程

<?php
class DbMysql{
    public function connect()
    {
        echo "连接mysql";
    }
}
class DbSqlite
{
    public function connect()
    {
        echo "连接mysql";
    }
}

// 注册树(IOC控制反转的思想)
class RegisterTree{
    protected static $object = [];

    public static function set($alias,$object)
    {
        self::$object[$alias] = $object;
    }

    public static function get($alias)
    {
        return self::$object[$alias];
    }
}

RegisterTree::set('DbMysql',new DbMysql());
RegisterTree::set('DbSqlite',new DbSqlLite());
$mysql = RegisterTree::get('DbMysql');
$mysql->connect();

门面模式

门面模式 (Facade) 又称外观模式,用于为子系统中的一组接口提供一个一致的界面门面模式定义了一个高层接口,这个接口使得子系统更加容易使用:引入门面角色之后,用户只需要直接与门面角色交互,用户与子系统之间的复杂关系由门面角色来实现,从而降低了系统的耦合度。

有了门面之后,我们一般不通过注册树进行调用组件,可以通过门面统一来调用,隐藏内部复杂的逻辑,用起来比较方便。

<?php
class Camera{
    public function turnOn()
    {
        echo "打开相机";
    }

    public function turnOff()
    {
        echo "关闭相机";
    }
}

class CameraFacade{

    public function __callStatic($name, $arguments)
    {
        $camera = new Camera();
        $camera->$name($arguments);
    }
}

CameraFacade::turnOn();

管道模式

管道 (Pipeline) 设计模式流水线模式,就是将会数据传递到一个任务序列中,管道扮演者流水线的角色,数据在这里被处理然后传递到下一个步骤。

管道需要三个角色:管道、阀门、载荷。

https://www.xiaoqiuyinboke.cn/usr/uploads/2022/10/298741850.png

<?php
class Pipeline {
    private $payload;
    private $pipes = [];// 阀门

    public function __construct($payload)
    {
        $this->payload = $payload;   
    }
    public function Pipe($stage)
    {
        $this->pipes[] = $stage;
        return $this;
    }

    public function process()
    {
        foreach($this->pipes as $pipe){
            call_user_func([$pipe,'handle'],$this->payload);
        }
    }
}

class StageOne {
    public function handle($payload)
    {
        echo $payload."是个";
    }
}

class StageTwo
{
    public function handle($payload)
    {
        echo  "帅哥";
    }
}

$pipeLine = new Pipeline('Joke');
$pipeLine->Pipe(new StageOne())->Pipe(new StageTwo)->process();

代理模式

代理模式 (Proxy Pattern):构建了透明置于两个不同对象之内的一个对象,从而能够截取或代理这两个对象间的通信或访问。

interface Subject{
    public function request();
}

class RealSubject implements Subject{
    public function request(){
        echo "真实操作";
    }
}

class ProxySubject implements Subject{
    public $real;
    public function __construct()
    {
        $this->real = new RealSubject();
    }
    // 代理操作
    public function request()
    {
        echo "代理";
        $this->real->request();
    }
}

$proxy = new ProxySubject();
$proxy->request();

请注意代理模式与装饰器、适配器的区别:

  1. 装饰器,一般是对对象进行装饰,其中的方法行为会有增加,以修饰对象为主

  2. 适配器,一般会改变方法行为,目的是保持接口的统一但得到不同的实现

  3. 代理模式有几种形式:远程代理(例如:第三方接口 SDK)、虚代理(例如:异步加
    载图片)、保护代理&智能指引(例如:权限保护),而我们代码实现的最普通的代理,其实就是达代理类来代替真实类的操作

设计模式之-行为型模式

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

策略模式

将一组特定的行为和算法封装起来,以适应某些特定的上下文环境,并让它们可以相互替换,这种模式就是策略模式。

多个类只区别在表现行为不同,可以使用 Strategy 模式,在运行时动态选择具体要执行的行为。比如上学,有多种策略:走路,公交,地铁…

// 去学校
abstract class Strategy
{
    abstract function goSchool();
}
// 跑着去
class Run extends Strategy
{
    public function goSchool()
    {
        // TODO: Implement goSchool() method.
    }
}
// 走路去
class Subway extends Strategy
{
    public function goSchool()
    {
        // TODO: Implement goSchool() method.
    }
}
// 骑自行车去
class Bike extends Strategy
{
    public function goSchool()
    {
        // TODO: Implement goSchool() method.
    }
}

// 上下文
class Context
{
    protected $_stratege;//存储传过来的策略对象
    public function goSchoole()
    {
        $this->_stratege->goSchoole();
    }
}
//调用:
$contenx = new Context();
$avil_stratery = new Subway();
$contenx->goSchoole($avil_stratery);// 选择走路去

观察者模式

观察者模式属于行为模式,是定义对象间的一种 一对多 的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

当一个对象状态发生改变后,会影响到其他几个对象的改变,这时候可以用观察者模式。

观察者模式符合接口隔离原则,实现了对象之间的松散耦合。

角色:

  1. 被观察对象:部门领导,可以有各个不同的部门领导

  2. 观察者:相当于公司前台的角色,当部门领导请假后,前台通知该部门的员工

  3. 主题对象:相当于部门员工,有很多部门的员工,每个部门的员工可以增加或减少,当被通知领导请假后,就可以玩手机了

// 主题接口
interface Subject{
    public function register(Observer $observer);
    public function notify();
}
// 观察者接口
interface Observer{
    public function watch();
}
// 主题
class Action implements Subject{
    public $_observers=[];
    public function register(Observer $observer){
        $this->_observers[]=$observer;
    }

    public function notify(){
        foreach ($this->_observers as $observer) {
            $observer->watch();
        }

    }
}

// 观察者
class Cat1 implements Observer{
    public function watch(){
        echo "Cat1 watches TV<hr/>";
    }
}
 class Dog1 implements Observer{
     public function watch(){
         echo "Dog1 watches TV<hr/>";
     }
 }
 class People implements Observer{
     public function watch(){
         echo "People watches TV<hr/>";
     }
 }
// 调用实例
$action=new Action();
$action->register(new Cat1());
$action->register(new People());
$action->register(new Dog1());
$action->notify();

场景:一个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理的逻辑。当更新的逻辑增多之后,代码会变得难以维护。 这种方式是耦合的,侵入式的 ,增加新的逻辑需要修改事件的主体代码。

具体例子,可以看 thinkphp6 的事件机制

命令模式

命令模式,也称为动作或者事务模式

如用餐厅举列,菜单是这个实际的命令,服务员是这个命令的发送者,而厨师是这个命令的接收者。

那么,这个模式解决了什么呢?当你要修改菜单的时候,只需要和服务员说就好了,她会转达给厨师,也就是说,我们实现了顾客和厨师的解耦。也就是调用者与实现者的解耦。

但是命令模式能够做到的是让一个命令接收者实现多个命令(服务员下单、拿酒水、上菜),或者把一条命令转达给多个实现者(热菜厨师、凉菜厨师、主食师傅)。

<?php

class Invoker
{    // 命令发送者(服务员)
    private $command = [];

    public function setCommand(Command $command)
    {
        $this->command[] = $command;
    }

    public function exec()
    {   // 创建
        if (count($this->command) > 0) {
            foreach ($this->command as $command) {
                $command->execute();
            }
        }
    }

    public function undo()
    {   // 撤销
        if (count($this->command) > 0) {
            foreach ($this->command as $command) {
                $command->undo();
            }
        }
    }
}

abstract class Command
{    // 执行命令内容(菜单)
    protected $receiver;
    protected $state;
    protected $name;

    public function __construct(Receiver $receiver, $name)
    {
        $this->receiver = $receiver;
        $this->name = $name;
    }

    abstract public function execute();
}

class ConcreteCommand extends Command
{   // 具体命令内容
    public function execute()
    {   // 具体创建方法
        if (!$this->state || $this->state == 2) {
            $this->receiver->action();
            $this->state = 1;
        } else {
            echo $this->name . '命令正在执行,无法再次执行了!', PHP_EOL;
        }
    }

    public function undo()
    {   // 具体取消方法
        if ($this->state == 1) {
            $this->receiver->undo();
            $this->state = 2;
        } else {
            echo $this->name . '命令未执行,无法撤销了!', PHP_EOL;
        }
    }
}

class Receiver
{   // 命令接收者(厨师)
    public $name;
    public function __construct($name)
    {
        $this->name = $name;
    }
    public function action()
    {
        echo $this->name . '命令执行了', PHP_EOL;
    }
    public function undo()
    {
        echo $this->name . '命令撤销了', PHP_EOL;
    }
}

// 命令发送者(服务员)
$invoker = new Invoker();

// 命令接收者(厨师)
$receiverA = new Receiver('A');

// 具体执行的命令内容(菜单)
$commandOne = new ConcreteCommand($receiverA, 'A');

// 执行命令
$invoker->setCommand($commandOne);
$invoker->exec();
$invoker->undo();

// 新加一个单独的执行者,只执行一个命令
$invokerA = new Invoker();
$invokerA->setCommand($commandOne);
$invokerA->exec();

// 命令A已经执行了,再次执行全部的命令执行者,A命令的state判断无法生效
$invoker->exec();

迭代器模式

迭代器模式是遍历集合的成熟模式,迭代器模式的关键是将遍历集合的任务交给一个叫做迭代器的对象,它的工作时遍历并选择序列中的对象,而客户端程序员不必知道或关心该集合序列底层的结构。

迭代器模式 (lterator), 又叫做游标 (Cursor) 模式。提供一种方法访问一个容器 (Container) 对象中各个元素,而又不需暴露该对象的内部细节。

thinkphp6 里的模型集合,就是用迭代器进行遍历,实现 Iterrator 接口就好了。

参考

< https://www.bilibili.com/video/BV1vK4y1375m/?p=22&spm_id_from=pageDriver&vd_source=c38eab5c82d0c7cca57364b72f733942>
https://www.bilibili.com/video/BV1cg411e7mJ/?spm_id_from=pageDriver&vd_source=c38eab5c82d0c7cca57364b72f733942

0

评论 (0)

取消