ทำไมชั้นเรียนของฉันถึงแย่กว่าลำดับชั้นของหนังสือ (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

4
@giorgiosironi ฉันเลิกการประเมินภาษาแล้ว ฉันใช้ PHP ทุกวันดังนั้นมันจึงเป็นวิธีที่เร็วที่สุดสำหรับฉันในการเรียนรู้แนวคิดใหม่ ๆ มีคนที่เกลียดภาษาอยู่เสมอ (Java: slidesha.re/91C4pX ) - เป็นที่ยอมรับกันว่าหาผู้เกลียดชัง Java ได้ยากกว่าคนที่แยก PHP แต่ก็ยังเป็นเรื่องยาก อาจมีปัญหากับ PHP ของ OO แต่ในขณะนี้ฉันไม่สามารถเรียนรู้ไวยากรณ์ Java, "Java way" และ 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;
    }
}

การตีความหนังสือของฉันคือคุณควรมีสามคลาส:

  • AbstractLesson
  • HourlyRateLesson
  • FixedRateLesson

คุณควรปรับใช้costฟังก์ชันบนคลาสย่อยทั้งสองอีกครั้ง

ความคิดเห็นส่วนตัวของฉัน

สำหรับกรณีง่าย ๆ เช่นนั้น: ไม่ เป็นเรื่องแปลกที่หนังสือของคุณให้ลำดับชั้นของชั้นลึกกว่าเพื่อหลีกเลี่ยงข้อความที่มีเงื่อนไขง่าย ๆ ยิ่งไปกว่านั้นด้วยข้อมูลจำเพาะของคุณLessonจะต้องเป็นโรงงานที่มีการส่งข้อมูลซึ่งเป็นข้อความสั่งแบบมีเงื่อนไข ดังนั้นด้วยโซลูชันหนังสือคุณจะมี:

  • 3 คลาสแทน 1
  • 1 ระดับการสืบทอดแทน 0
  • จำนวนข้อความสั่งที่มีเงื่อนไขเท่ากัน

นั่นคือความซับซ้อนมากขึ้นโดยไม่มีผลประโยชน์เพิ่มเติม


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.

สำหรับ "ป้ายบอกทาง":

  • การทำสำเนารหัส

    รหัสของคุณไม่มีการซ้ำซ้อน รหัสที่ผู้เขียนคาดหวังว่าคุณจะเขียนทำ

  • ชั้นที่รู้มากเกินไปเกี่ยวกับบริบทของเขา

    คลาสของคุณเพิ่งส่งสตริงจากอินพุตไปยังเอาต์พุตและไม่รู้อะไรเลย รหัสที่คาดหวังในทางกลับกันสามารถถูกวิพากษ์วิจารณ์ได้เพราะรู้มากเกินไป

  • The Jack of All Trades - คลาสที่พยายามทำหลายสิ่งหลายอย่าง

    อีกครั้งคุณเพิ่งผ่านสายไม่มีอะไรเพิ่มเติม

  • คำสั่งแบบมีเงื่อนไข

    มีคำสั่งเงื่อนไขหนึ่งคำในรหัสของคุณ สามารถอ่านและเข้าใจได้ง่าย


ในความเป็นจริงผู้เขียนคาดว่าคุณจะเขียนโค้ดที่ได้รับการปรับปรุงใหม่มากกว่าโค้ดที่มีหกคลาสขึ้นไป ตัวอย่างเช่นคุณสามารถใช้รูปแบบโรงงานสำหรับบทเรียนและค่าใช้จ่ายเป็นต้น

+ 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 ของคำตอบ :( โอ้ฉันจะทำอย่างไร!
Aditya MP

5

ก่อนอื่นคุณสามารถเปลี่ยน "หมายเลขมายากล" เช่น 30 และ 5 ในฟังก์ชั่น cost () เป็นตัวแปรที่มีความหมายมากขึ้น :)

ฉันคิดว่าคุณไม่ควรกังวลเกี่ยวกับเรื่องนี้ เมื่อคุณจะต้องเปลี่ยนชั้นเรียนแล้วคุณจะเปลี่ยน ลองสร้างมูลค่าก่อนจากนั้นย้ายไปยังการปรับโครงสร้างซ้ำ

และทำไมคุณคิดว่าคุณผิด คลาสนี้ไม่ดีพอสำหรับคุณหรือเปล่า ทำไมคุณถึงเป็นห่วงหนังสือบางเล่ม;)


1
ขอบคุณสำหรับคำตอบ! แน่นอนการฟื้นฟูจะมาในภายหลัง อย่างไรก็ตามฉันเขียนโค้ดนี้เพื่อการเรียนรู้พยายามที่จะแก้ปัญหาที่ปรากฏในหนังสือในแบบของฉันเองและเห็นว่าฉันผิดไปไหน ...
Aditya MP

2
แต่คุณจะรู้เรื่องนี้ในภายหลัง เมื่อคลาสนี้จะถูกใช้ในส่วนอื่น ๆ ของระบบและคุณจะต้องเพิ่มสิ่งใหม่ ไม่มีวิธีแก้ปัญหา "กระสุนเงิน" :) สิ่งที่ดีหรือไม่ดีขึ้นอยู่กับระบบความต้องการ ฯลฯ ในขณะที่การเรียนรู้ OOP คุณต้องล้มเหลวมาก: P จากนั้นเมื่อเวลาผ่านไปคุณจะสังเกตเห็นปัญหาและรูปแบบที่พบบ่อย Tbh ตัวอย่างนี้ง่ายเกินไปที่จะให้คำแนะนำ โอ้ฉันขอแนะนำโพสต์นี้จาก Joel :) สถาปัตยกรรมนักบินอวกาศเข้าครอบครองjoelonsoftware.com/items/2008/05/01.html
Michal Franc

@adityamenon จากนั้นคำตอบของคุณคือ "การสร้างใหม่จะมาในภายหลัง" เพิ่มคลาสอื่น ๆ เมื่อจำเป็นต้องทำให้โค้ดง่ายขึ้น - โดยทั่วไปนั่นคือเมื่อกรณีการใช้งานที่คล้ายกันลำดับที่ 3 เกิดขึ้น ดูคำตอบของ Simon
Izkata

3

ไม่จำเป็นต้องใช้คลาสเพิ่มเติมใด ๆ เลย คุณมีปัญหาเล็กน้อยMAGIC_NUMBERในcost()ฟังก์ชั่นของคุณแต่นั่นเป็นปัญหา สิ่งอื่นใดก็คือวิศวกรรมที่ยิ่งใหญ่ อย่างไรก็ตามมันเป็นเรื่องปกติที่น่าเสียดายสำหรับคำแนะนำที่ไม่ดีมากที่จะได้รับ - Circle สืบทอดรูปร่างเช่น แน่นอนว่ามันไม่ได้มีประสิทธิภาพ แต่อย่างใดในการสืบทอดคลาสสำหรับการสืบทอดอย่างง่ายของฟังก์ชันหนึ่ง คุณสามารถใช้วิธีการทำงานเพื่อปรับแต่งแทน


1
สิ่งนี้น่าสนใจ คุณสามารถอธิบายได้ว่าคุณจะทำเช่นนั้นกับตัวอย่างได้อย่างไร
guillaume31

+1, ฉันเห็นด้วย, ไม่มีเหตุผลอะไรที่จะทำให้วิศวกรทำงานมากเกินไป / ซับซ้อนเล็กน้อย
GrandmasterB

@ ian31: ทำอะไรเป็นพิเศษ
DeadMG

@DeadMG "ใช้วิธีการทำงาน", "ใช้ส่วนขยายการทำงานมากกว่าการถ่ายทอดทางพันธุกรรม"
guillaume31
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.