【深入PHP 面向对象】读书笔记(六) - 模式原则

本文探讨了设计模式在软件开发中的应用,重点讲解了组合模式如何提高系统的灵活性,并通过策略模式的具体案例展示了组合模式的优势。此外,还讨论了解耦的重要性以及针对接口而非实现编程的原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【简介】设计模式不只是简单地描述了问题的解决方案,而且很重视解决方案的可重用性和灵活性。

组合:如何通过聚合对象来获得比只是用继承更好的灵活性。
解耦:如何降低系统中元素的依赖性。

8 模式原则

8.1 组合与继承

继承是应对变化的环境及上下文设计的有效方式,然而它会限制灵活性,尤其当类承担多重责任的时候。

8.1.1 问题

这里写图片描述

UML 描述了这个一个场景。有一个 Lesson 抽象类用来描述课程,它定义抽象的 cost() 方法和 chargeType() 方法用来描述收费类型和具体收费。同时,定义了两个实现类(FixedPriceLesson 和 TimedPriceLesson)用来实现具体的收费机制。

这样一个系统能够很好地在两种课程下的收费情况。但是,如果我们的课程需求改成了需要处理演讲和研讨会呢?两种课程类型下都有 Fixed 和 Timed 两种收费模式,如果我们只是用继承的方式来实现的话:
这里写图片描述

就会发现不得不出现重复的问题,定价策略在 Lecture 和 Seminar 类的子类中被重复实现。

如果考虑在父类 Lesson 中使用条件语句来实现不同情况下的收费机制,从而移除这些重复。

这里写图片描述

Lesson 类里面的判断逻辑代码:

abstract class Lesson {
    protected $duration;
    const FIXED = 1;
    const TIMED = 2;
    private $costtype;

    function __construct($duration, $costtype=1) {
        $this->duration = $duration;
        $this->costtype = $costtype;
    }

    function cost() {
        switch ($this->costtype) {
            case self::TIMED :
                return (5*$this->duration);
                break;
            case self::FIXED:
                return 30;
                break;
            default:
                $this->costtype = self::FIXED;
                return 30;
        }
    }

    function chargeType() {
        switch ($this->costtype) {
            case self::TIMED:
                return 'hourly rate';
                break;
            case self::FIXED:
                return 'fixed rate';
                break;
            default:
                $thsi->costtype = self::FIXED;
                return 'fixed rate';
        }
    }

    // Lesson的更多方法
}


class Lecture extends Lesson {
    // Lecture特定的实现
}

class Seminar extends Lesson {
    // Seminar特定的实现
}

以及测试代码:

$lecture = new Lecture(5, Lesson::FIXED);
echo $lecture->cost().$lecture->chargeType();

$seminar = new Seminar(3, Lesson::TIMED);
echo $seminar->cost().$seminar->chargeType();

虽然很好地解决了重复的问题,但是父类中使用了条件语句,这与我们通常用多态替换条件的重构思想背道而驰,是一种倒退。

8.1.2 使用组合

我们可以使用策略模式来解决这个问题。策略模式适用于将一组算法移入到一个独立的类型中。通过移走费用计算的代码,可以简化 Lesson 类型。

这里写图片描述

创建一个 CostStrategy 的抽象类,它定义了抽象方法 cost() 和 chargeType() 。cost() 方法需要一个 Lesson 实例,用于生成费用数据。并提供两个 CostStrategy 的两个实现类,用于明确两个情况下的费用算法。

有关 Lesson 类的代码:

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

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

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

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

    function getDuration() {
        return $this->duration;
    }
    // Lesson的更多方法
}


class Lecture extends Lesson {
    // Lecture特定的实现
}

class Seminar extends Lesson {
    // Seminar特定的实现
}

$lecture = new Lecture(5, Lesson::FIXED);
echo $lecture->cost().$lecture->chargeType();

$seminar = new Seminar(3, Lesson::TIMED);
echo $seminar->cost().$seminar->chargeType();

Lesson 类需要一个作为属性的 CostStrategy 对象。Lesson::cost() 方法只调用 CostStrategy::cost()。同样,Lesson::chargeType() 方法只调用 CostStrategy::chargeType()。这种显式调用另一个对象的方法来执行一个请求的方式便是「委托」。在示例代码中,CostStrategy 对象是 Lesson 的委托方。Lesson 类不再负责计费,而是把计费任务传给 CostStrategy 类,而具体计费的实现则是在 CostStrategy 的两个子类 TimedCostStrategy 和 FixedCostStrategy 中完成。

下面代码执行了委托操作:

1)Lesson 类的调用:

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

2) CostStrategy 类及其实现子类:

abstract class CostStrategy {
    abstract function cost(Lesson $lesson);
    abstract function chargeType();
}

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

    function chargeType() {
        return 'hourly rate';
    }
}
class FixedCostStrategy extends CostStrategy {
    function cost(Lesson $lesson) {
        return 30;
    }

    function chargeType() {
        return 'fixed rate';
    }
}

通过传递不同 CostStrategy 对象,可以在代码运行时改变 Lesson 对象计算费用的方式。这种方式有助于产生具有高度灵活性的代码。

实际调用:

$lesson[] = new Seminar(4, new TimedCostStrategy());
$lesson[] = new Lecture(4, new FixedCostStrategy());

foreach ($lessons as $lesson) {
    echo "lesson charge ".$lesson->cost();
    echo "Charge type: ".$lesson->chargeType();
}

组合使用对象比使用继承体系更灵活,但同时也会导致代码的可读性下降,因为组合需要更多的对象类型,而这些类型的关系并不像继承关系那般有固定的可预见性,所以要理解系统中类和对象的关系会有些困难。

8.2 解耦

系统中一个组件的改变迫使系统其他许多地方也发生改变的时候,这种称之为紧耦合,这是我们需要避免的,重用性设计是面向对象设计的主要目标之一。

8.3 针对接口编程,而不是针对实现编程

把不同的实现隐藏在父类所定义的共同接口下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值