设计模式 - 原则及实例讲解

关于设计模式

设计模式是一些典型问题的优秀实践,是自下而上的,设计模式来源于实践。

在编程过程中,相同的问题不断出现,程序员一次次的解决,久而久之,总结出了特定问题的解决方式,并将其看作是优秀的实践。这些优秀实践就是设计模式。从这里可以看出,设计模式是针对场景而言的,并不是使用越多越好,因为设计模式不是科学理论,而是注重实效的。即使是同一个设计模式,在不同的上下文中实现也可能不同。

通常,设计模式由以下几部分组成

  • 命名。命名直接体现出了问题和解决方案,必须兼顾简洁性和描述性。
  • 问题。模式是为了解决问题而存在的,因此问题及问题发生的环境是设计模式的基础。
  • 解决方案。模式的解决方案并非真正的解决方案,更像是一个半成品,编码人员需要根据具体的场景来完成具体的实现。
  • 效果。设计模式会带来某种结果,除了收益外,还需要考虑风险。

原则

对于人类来说,存在一些普适原则,适用于不同的人,例如「积极主动」、「要事第一」等。而对于软件开发而言,同样也有普适原则的存在,设计模式之所以称之为优秀实践,就是因为它们的设计符合一些软件开发的通用原则:

  • 组合优于继承
  • 保持组件的松耦合
  • 针对接口编程,而非针对实现编程。
  • 封装变化。

案例分析

需求

课程 (Lesson) 分为 固定收费课程 (FixedPriceLesson) 与 计时收费课程 (TimedPriceLesson),前者一次收费 30,后者一小时收费 5块。

设计

实现

<?php

abstract class Lesson
{

    /**
     * 课程时长
     *
     * @var int
     */
    protected $duration;

    /**
     * Lesson constructor.
     *
     * @param int $duration
     */
    public function __construct(int $duration)
    {
        $this->duration = $duration;
    }

    /**
     * 收费
     *
     * @return int
     */
    abstract function cost(): int;

    /**
     * 收费类型
     *
     * @return string
     */
    abstract function chargeType() : string;
}


class FixedPriceLesson extends Lesson
{
    public function cost(): int
    {
        return 30;
    }

    public function chargeType(): string
    {
        return '固定收费';
    }
}

class TimedPriceLesson extends Lesson
{
    public function cost(): int
    {
        return $this->duration * 5;
    }
    public function chargeType(): string
    {
        return '计时收费';
    }
}

当我们要对课程类型进一步扩展时,就会变得很麻烦。例如,要将课程要分成讲座(Lecture)和 研讨会(Seminar),每种课程又包含了固定收费和即时收费。如果按照继承的思想来设计的话,就会涉及到大量的改动,并且后期的扩展性很差

<?php

abstract class Lesson {}

class Lecture extends Lesson {}
class FixedPriceLesson extends Lecture {}
class TimedPriceLesson extends Lecture {}

我们从设计模式的几个原则来分析下这个例子

  • 违反了「组合优于继承」原则 - 所有的功能都通过继承来实现,导致 Lesson 类承担过多的职责;
  • 违反了「保持组件的松耦合」原则 - Lesson 类与收费这两者之间是紧密耦合的,这样的话,一个组件的变化必然涉及到另外一个组件的变化,当我们引入新的课程时,就同时需要涉及到 Lesson 和收费的变动,导致代码复杂度增加;
  • 违反了「针对接口编程,而非针对实现编程」原则。在该例子中,我们始终是针对具体的实现去编程;
  • 没有「封装变化」。收费策略是不断变化的,在该例子中,我们没有将变化的要素封装起来。

重构

按照设计原则,将变化的因素封装起来,这里变化的因素就是「收费」这一行为,同时,针对接口编程,所以需要定义一个抽象的收费策略接口。

设计

定义收费策略接口

<?php

abstract class CostStrategy
{
    abstract public function cost(Lesson $lesson): int;
    abstract public function chargeType(): string;
}

不同的收费策略具体的实现

<?php

class FixedCostStrategy extends CostStrategy
{
    public function cost(Lesson $lesson): int
    {
        return 30;
    }

    public function chargeType(): string
    {
        return "固定收费";
    }
}

class TimedCostStrategy extends CostStrategy
{
    public function cost(Lesson $lesson): int
    {
        return ($lesson->getDuration() * 5);
    }

    public function chargeType(): string
    {
        return "即时收费";
    }
}

父类 Lesson 只需要传入抽象的收费策略接口即可,保证了职责的单一性

<?php

abstract class Lesson
{
    private $duration;
    private $costStrategy;  

    public function __construct(int $duration, CostStrategy $strategy)
    {
        $this->duration = $duration;
        $this->costStrategy = $strategy; 
    }

    public function cost(): int
    {
        return $this->costStrategy->cost($this);
    }

    public function chargeType(): string
    {
        return $this->costStrategy->chargeType();
    }

    public function getDuration(): int
    {
        return $this->duration;
    }
}

class Lecture extends Lesson {}
class Seminar extends Lesson {}

测试

<?php

$lessons1 = new Seminar(4, new TimedCostStrategy());
$lessons2 = new Lecture(4, new FixedCostStrategy());

$lesson1->cost();
$lesson1->chargeType();

$lesson2->cost();
$lesson2->chargeType();

消息通知

最后,在现有的基础上增加消息通知功能,即课程注册成功后,发送通知。对于该功能,可以进行简单的分析

  • 通知可以分很多种,邮件、短信等等,因此,需要将这一不断变化的行为进行封装;
  • 需要有一个通知管理类,用于注册课程和发送通知;

设计

消息通知

<?php

abstract class Notifier
{

    public static function getNotifier(): Notifier
    {   

        if (rand(1, 2) === 1) { // 方便测试,使用了随机返回,实际上可以根据配置文件来决定返回哪种通知
            return new MailNotifier();
        } else {
            return new TextNotifier();
        }
    }

    abstract public function inform($message);
}

class MailNotifier extends Notifier
{
    public function inform($message)
    {
        print "邮件通知: {$message}\n";
    }
}

class TextNotifier extends Notifier
{
    public function inform($message)
    {
        print "短信通知: {$message}\n";
    }
}

通知管理

<?php

/**
 * 客户端用于注册课程,并发送通知
 */
class RegistrationMgr
{
    public function register(Lesson $lesson)
    {
        $notifier = Notifier::getNotifier();
        $notifier->inform("新课程花费 ({$lesson->cost()})");
    }
}

测试

<?php

$mgr = new RegistrationMgr();
$mgr->register($lessons1);
$mgr->register($lessons2);

参考

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!