为什么我的班级比本书中的班级等级(初学者OOP)更糟糕?


11

我正在阅读PHP对象,模式和实践。作者正试图在大学里模拟一堂课。目标是输出课程类型(讲座或研讨会),并且该课程的费用取决于它是每小时课程还是固定价格课程。所以输出应该是

Lesson charge 20. Charge type: hourly rate. Lesson type: seminar.
Lesson charge 30. Charge type: fixed rate. Lesson type: lecture.

当输入如下时:

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

我这样写:

class Lesson {
    private $chargeType;
    private $duration;
    private $lessonType;

    public function __construct($chargeType, $duration, $lessonType) {
        $this->chargeType = $chargeType;
        $this->duration = $duration;
        $this->lessonType = $lessonType;
    }

    public function getChargeType() {
        return $this->getChargeType;
    }

    public function getLessonType() {
        return $this->getLessonType;
    }

    public function cost() {
        if($this->chargeType == 'fixed rate') {
            return "30";
        } else {
            return $this->duration * 5;
        }
    }
}

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

foreach($lessons as $lesson) {
    print "Lesson charge {$lesson->cost()}.";
    print " Charge type: {$lesson->getChargeType()}.";
    print " Lesson type: {$lesson->getLessonType()}.";
    print "<br />";
}

但是根据这本书,我错了(我也很确定自己也错了)。相反,作者给出了较大的类层次结构作为解决方案。在上一章中,作者说了以下“四个路标”,这是我考虑更改班级结构的时间:

  • 代码重复
  • 对上下文了解得太多的班级
  • 万事通 -类,尝试做很多事情
  • 条件语句

我能看到的唯一问题是条件语句,而且也含糊其词-那么为什么要重构呢?您认为我未曾预见的将来会出现什么问题?

更新:我忘了提-这是作者提供的类结构作为解决方案- 策略模式

策略模式


29
不要从PHP书籍中学习面向对象的编程。
giorgiosironi 2012年

4
@giorgiosironi我已经放弃评估语言了。我每天都使用PHP,因此对我而言,学习新概念最快。总是有人讨厌一种语言(Java:slidesha.re/91C4pX)-诚然,找到Java讨厌者比发现PHP困难者要难得多,但仍然如此。PHP的OO可能存在问题,但是目前我不能花时间学习Java语法,“ Java方式” OOP。此外,桌面编程对我来说是陌生的。我是一个完整的网络人。但是据称,在使用JSP之前,您需要先了解桌面Java(没有引证,我错了吗?)
Aditya MP

使用常量作为“固定费率” /“每小时费率”和“研讨会” /“讲座”,而不是字符串。
Tom Marthenal

Answers:


19

这很有趣,这本书并没有明确说明,但为什么它比如果同一类中的语句可能是有利于一类层次的原因开/关原则这个广为人知的软件设计规则规定,一类应关闭修改,但可以扩展。

在您的示例中,添加新的课程类型将意味着更改Lesson类的源代码,使其变得脆弱且易于回归。如果您有一个基类和每种课程类型的一个派生类,则只需要添加另一个子类即可,该子类通常被认为更为简洁。

如果可以的话,可以将该规则放在“条件声明”部分下,但是我认为“路标”有点含糊。如果语句通常只是代码气味,症状。它们可能是由各种错误的设计决策引起的。


3
使用功能扩展而不是继承是一个更好的主意。
DeadMG

6
当然,还有其他/更好的方法可以解决此问题。但是,这显然是一本有关面向对象程序设计的书,开放/闭合原理使我震惊,是解决OO中这种设计难题经典方法。
guillaume31'4

解决问题的最佳方法如何?仅仅因为它是面向对象的,教一个劣等的解决方案是没有意义的。
DeadMG

16

我想这本书的教学目的是避免出现以下情况:

public function cost() {
    if($this->chargeType == 'fixed rate') {
        return "30";
    } else {
        return $this->duration * 5;
    }
}

我对这本书的解释是,您应该上三节课:

  • 摘要课程
  • HourlyRateLesson
  • 固定利率课程

然后,您应该cost在两个子类上重新实现该函数。

我个人的意见

对于像这样的简单情况:不要。奇怪的是,您的书倾向于更深层次的类层次结构,以避免简单的条件语句。而且,根据您的输入规范,Lesson必须是一个带有条件语句的调度工厂。因此,借助书本解决方案,您将拥有:

  • 3个班级而不是1个
  • 1个继承级别而不是0
  • 相同数量的条件语句

没有更多好处的情况下,这更加复杂。


8
这种情况下,是的,它太复杂了。但是在一个更大的系统中,您将添加更多的课程,例如SemesterLessonMasterOneWeekLesson等。抽象根类,或者更好的是一个接口,绝对是接下来的方法。但是,只有两种情况时,由作者决定。
Michael K

5
我总体上同意你的看法。但是,教育性的例子通常是人为设计的,目的是为了说明这一点。您暂时会忽略其他注意事项,以免混淆眼前的问题,并在以后介绍这些注意事项。
Karl Bielefeldt

+1彻底分解。评论“没有更多好处的情况下更加复杂”说明了完美编程中“机会成本”的概念。理论!=实用性。
伊万·普赖斯

7

书中的例子很奇怪。从您的问题来看,原始语句是:

目的是输出课程类型(讲座或研讨会),以及根据每小时或固定价格的课程收费。

在有关OOP的一章中,这意味着作者可能希望您具有以下结构:

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

但随后出现您引用的输入:

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

也就是所谓的字符串类型的代码,而实际上,在这种情况下,当读者打算学习OOP时,这是可怕的。

这也意味着,如果我对本书作者的意图是正确的(即,创建上面列出的那些抽象类和继承类),这将导致重复的代码以及非常难以理解和丑陋的代码:

const string $HourlyRate = 'hourly rate';
const string $FixedRate = 'fixed rate';

// [...]

Charge $charge;
switch ($chargeType)
{
    case $HourlyRate:
        $charge = new HourlyCharge();
        break;

    case $FixedRate:
        $charge = new FixedCharge();
        break;

    default:
        throw new ParserException('The value of chargeType is unexpected.');
}

// Imagine the similar code for lecture/seminar, and the similar code for both on output.

至于“路标”:

  • 代码重复

    您的代码没有重复。作者希望您编写的代码可以。

  • 对自己的背景了解太多的班级

    您的类只将字符串从输入传递到输出,什么都不知道。另一方面,期望的代码可能因了解太多而受到批评。

  • 万事通-尝试做很多事情的课程

    同样,您只是传递字符串,仅此而已。

  • 条件语句

    您的代码中只有一个条件语句。它是可读且易于理解的。


也许,实际上,作者希望您编写的重构代码比上面包含六个类的重构代码还要多。例如,您可以将工厂模式用于课程和收费等。

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

+ LessonFactory

+ ChargeFactory

+ Parser // Uses factories to transform the input into `Lesson`.

在这种情况下,您将获得九个类,这将是过度架构的完美示例。

相反,您最终得到了一个易于理解的干净代码,该代码要短十倍。


哇,你的猜测很准确!看一下我上传的图片-作者推荐了完全一样的东西。
Aditya MP

我会这么喜欢接受这个答案,但我也很喜欢@ ian31的回答:(呵呵,我该怎么办!
阿迪亚MP

5

首先,您可以将cost()函数中的“魔术数字”(例如30和5)更改为更有意义的变量:)

老实说,我认为您不必为此担心。当您需要更改班级时,您将对其进行更改。首先尝试创造价值,然后再进行重构。

为何您认为自己错了?这堂课对你来说不够好吗?你为什么担心一些书;)


1
谢谢回答!确实,重构将在以后出现。但是,我编写此代码是为了学习,试图以自己的方式解决书中提出的问题,并看看我会怎么做...
Aditya MP

2
但是您稍后会知道。当此类将在系统的其他部分使用时,您将不得不添加一些新内容。没有“灵丹妙药”解决方案:)某些事情可能好坏取决于系统,要求等。学习OOP时,您必须失败很多:P然后随着时间的推移,您会注意到一些常见的问题和模式。这个例子太简单了,无法提出建议。哦,我真的推荐Joel 撰写的
Michal Franc

@adityamenon然后您的答案是“重构将在以后出现”。仅在需要使其他代码简化时才添加其他类-通常,这是第三个类似用例出现的时候。参见西蒙的答案。
Izkata 2012年

3

绝对不需要实现任何其他类。您MAGIC_NUMBERcost()函数中有一些问题,仅此而已。其他任何事情都是巨大的过度工程。但是,不幸的是,经常给出非常糟糕的建议,例如Circle继承Shape。绝对不能以任何方式派生一个类来简单继承一个函数。您可以改用功能方法来自定义它。


1
这很有趣。您能否举例说明如何做到这一点?
guillaume31'4

我同意+1,没有理由过度设计/复杂化这么少的功能。
GrandmasterB 2012年

@ ian31:具体做什么?
DeadMG

@DeadMG“使用功能方法”,“使用功能扩展而不是继承”
guillaume31'4
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.