내 수업이 책의 클래스 계층 구조보다 더 나쁜 이유는 무엇입니까 (초보자 OOP)?


11

PHP Objects, Patterns 및 Practice을 읽고 있습니다. 저자는 대학에서 수업을 모델링하려고합니다. 목표는 수업 유형 (강의 또는 세미나)과 시간당 또는 고정 가격 수업인지에 따라 수업 요금을 출력하는 것입니다. 따라서 출력은

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

4
@giorgiosironi 나는 언어 평가를 포기했다. 나는 매일 PHP를 사용하므로 새로운 개념을 배우는 것이 가장 빠릅니다. 언어 싫어하는 사람들이 항상있다 (자바 : slidesha.re/91C4pX은 ) - 틀림없이 그것은하지만 여전히 PHP의 dissers보다 자바 싫어하는를 찾을 수 어렵습니다. PHP의 OO에 문제가있을 수 있지만 현재로서는 Java 구문, "Java 방식" OOP 를 배울 시간이 없습니다 . 게다가, 데스크톱 프로그래밍은 나에게 외계인입니다. 나는 완전한 웹 사람입니다. 그러나 JSP에 도달하기 전에 데스크탑 Java를 알고 있어야합니다 (인용, 인용이 잘못 되었습니까?)
Aditya MP

문자열 대신 "고정 비율"/ "시간별 비율"및 "세미나"/ "강의"에 상수를 사용하십시오.
Tom Marthenal

답변:


19

이 책에 명확하게 언급되어 있지 않은 것은 재밌지 만, 같은 클래스 내부의 문장이 공개 / 폐쇄 원칙 인 경우 클래스 계층 구조를 선호하는 이유는 이 널리 알려진 소프트웨어 설계 규칙은 클래스를 수정하지만 확장을 위해 열려 있습니다.

예를 들어, 새로운 수업 유형을 추가하면 수업 수업의 소스 코드가 변경되어 깨지기 쉽고 회귀가 발생하기 쉽습니다. 각 수업 유형마다 기본 수업과 파생 상품이 하나있는 경우 다른 하위 수업을 추가하면됩니다 (일반적으로 더 깔끔한 것으로 간주 됨).

원한다면이 규칙을 "조건문"섹션에 넣을 수 있지만 "푯말"이 약간 모호하다고 생각합니다. 진술이 일반적으로 코드 냄새, 증상 인 경우. 다양한 잘못된 설계 결정으로 인해 발생할 수 있습니다.


3
상속보다는 기능 확장을 사용하는 것이 훨씬 더 좋습니다.
DeadMG

6
문제에 대한 다른 / 더 나은 접근법이 있습니다. 그러나,이 명확 것처럼 생각 난 객체 지향 프로그래밍 및 개방 / 폐쇄 원칙에 관한 책이다 OO에서 디자인 딜레마의 종류에 접근하는 고전적인 방법.
guillaume31

문제 에 접근하는 가장 좋은 방법은 어떻습니까? OO이기 때문에 열등한 솔루션을 가르치는 데는 아무런 소용이 없습니다.
DeadMG

16

이 책이 가르치는 목표는 다음과 같은 것을 피하는 것입니다.

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

이 책에 대한 나의 해석은 세 가지 수업이 필요하다는 것입니다.

  • 초록
  • 시간별 수업
  • 고정 등급 수업

그런 다음 cost두 서브 클래스 모두 에서 함수 를 다시 구현해야합니다 .

내 개인적인 의견

그런 간단한 경우 :하지 마십시오. 간단한 조건문을 피하기 위해 책이 더 깊은 클래스 계층 구조를 선호한다는 것은 이상합니다. 또한 입력 사양 Lesson과 함께 조건문 인 디스패치가있는 팩토리 여야합니다. 따라서 도서 솔루션을 통해 다음을 얻을 수 있습니다.

  • 1 대신 3 수업
  • 0 대신 1 상속 수준
  • 같은 수의 조건문

추가 혜택없이 더 복잡합니다.


8
에서 이 경우 , 예, 그것은 너무 복잡합니다. 그러나 더 큰 시스템에서, 당신은 더 많은 교훈 같은 추가 할 예정 SemesterLesson, MasterOneWeekLesson등 추상 루트 클래스, 또는 더 나은 아직 인터페이스는, 다음 갈 방법이 확실히입니다. 그러나 두 가지 경우에만 저자의 재량에 달려 있습니다.
Michael K

5
나는 당신에게 일반적으로 동의합니다. 그러나 교육적인 예는 종종 요점을 설명하기 위해 고 안되고 지나치게 설계되었습니다. 당면한 문제를 혼동하지 않기 위해 잠시 다른 고려 사항을 무시하고 나중에 고려할 사항을 소개합니다.
Karl Bielefeldt

+1 좋은 철저한 고장. "추가 혜택없이 더 복잡해집니다"라는 의견은 프로그래밍에서 '기회 비용'의 개념을 완벽하게 보여줍니다. 이론! = 실용성.
Evan Plaice

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.

"푯말"에 관해서는 :

  • 코드 복제

    코드에 중복이 없습니다. 저자가 작성하길 기대하는 코드는 그렇지 않습니다.

  • 그의 상황에 대해 너무 많이 알고있는 반

    클래스는 입력에서 출력으로 문자열을 전달하며 전혀 알지 못합니다. 반면에 예상되는 코드는 너무 많이 알고 있다고 비난받을 수 있습니다.

  • 모든 거래의 잭-많은 일을하려고하는 수업

    다시 말하지만, 당신은 단지 문자열을 전달하고 있습니다.

  • 조건문

    코드에는 하나의 조건문이 있습니다. 읽기 쉽고 이해하기 쉽습니다.


실제로 저자는 위의 6 개의 클래스가있는 코드보다 훨씬 더 리팩토링 된 코드를 작성해야한다고 예상했을 수 있습니다. 예를 들어 수업 및 요금 등에 팩토리 패턴 을 사용할 수 있습니다 .

+ 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`.

이 경우 9 개의 클래스가 생겨 오버 아키텍처 의 완벽한 예가 됩니다.

대신, 이해하기 쉽고 깨끗한 코드를 만들었습니다.이 코드는 10 배 짧습니다.


와우, 당신의 추측은 정확합니다! 내가 업로드 한 이미지를보십시오-저자는 똑같은 것을 추천했습니다.
Aditya MP

나는 것이다 그래서 이 답변을 받아 사랑하지만 또한 @ ian31의 대답 :( 같은 오, 내가 뭘!
아 디트 MP

5

우선 cost () 함수에서 30과 5와 같은 "마법의 숫자"를보다 의미있는 변수로 변경할 수 있습니다. :)

그리고 솔직히 말해서, 당신은 이것에 대해 걱정해서는 안된다고 생각합니다. 수업을 변경해야 할 때 변경합니다. 먼저 가치를 창출 한 다음 리팩토링으로 넘어가십시오.

왜 당신이 틀렸다고 생각하십니까? 이 수업이 "충분하지 않습니까?" 왜 당신은 어떤 책에 대해 걱정하고 있습니까?)


1
대답 해줘서 고마워! 실제로 리팩토링은 나중에 올 것이다. 그러나 저는이 코드를 학습용으로 작성했습니다.이 책에서 제시된 문제를 제 자신의 방식으로 해결하려고 노력하고 제가 어떻게 잘못되는지 보려고 노력했습니다.
Aditya MP

2
그러나 나중에 알게 될 것입니다. 이 클래스가 시스템의 다른 부분에서 사용될 때 새로운 것을 추가해야 할 때. "은 탄환"해결책은 없습니다 :) 어떤 것이 좋든 나쁘 든 시스템, 요구 사항 등에 따라 다릅니다. OOP를 배우는 동안 많은 실패를해야합니다 .P 그러면 시간이 지남에 따라 몇 가지 일반적인 문제와 패턴이 나타납니다. 이 예는 조언을하기에는 너무 단순합니다. Ohh 나는 정말로이 게시물을 Joel에서 추천합니다 :) 건축 우주 비행사는 joelonsoftware.com/items/2008/05/01.html을 인수합니다
Michal Franc

@adityamenon 그러면 대답은 "리팩토링은 나중에 올 것입니다"입니다. 코드를 더 단순하게 만드는 데 필요한 다른 클래스 만 추가하십시오. 일반적으로 3 번째 유사한 유스 케이스가 등장합니다. 시몬의 대답을보십시오.
Izkata

3

추가 클래스를 구현할 필요가 전혀 없습니다. 함수 MAGIC_NUMBER에 약간의 문제가 cost()있지만 그게 전부입니다. 다른 것들은 방대한 오버 엔지니어링입니다. 그러나 불행히도 매우 나쁜 조언을하는 경우가 일반적입니다. Circle은 Shape를 상속합니다. 한 함수의 간단한 상속을 위해 클래스를 파생시키는 것은 어떤 식으로도 효율적이지 않습니다. 대신 기능적인 접근 방식을 사용하여이를 사용자 정의 할 수 있습니다.


1
이것은 흥미 롭다. 예를 들어 어떻게 그렇게 할 수 있습니까?
guillaume31

+1, 나는 그러한 작은 기능을 과도하게 엔지니어링하거나 복잡하게 만들 이유가 없다고 동의한다.
GrandmasterB

@ ian31 : 구체적으로 무엇을합니까?
DeadMG

@DeadMG "기능적 접근 방식 사용", "상속보다는 기능적 확장 사용"
guillaume31
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.