Mengapa kelas saya lebih buruk daripada hierarki kelas dalam buku (OOP pemula)?

11

Saya membaca Objek PHP, Pola, dan Praktek . Penulis sedang mencoba membuat model pelajaran di sebuah perguruan tinggi. Tujuannya adalah untuk menghasilkan jenis pelajaran (kuliah atau seminar), dan biaya untuk pelajaran tergantung pada apakah itu pelajaran harga per jam atau tetap. Jadi hasilnya seharusnya

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

ketika inputnya adalah sebagai berikut:

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

Saya menulis ini:

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 />";
}

Tetapi menurut buku itu, saya salah (saya cukup yakin saya juga). Sebagai gantinya, penulis memberikan hierarki kelas yang besar sebagai solusinya. Dalam bab sebelumnya, penulis menyatakan 'empat rambu-rambu' sebagai waktu ketika saya harus mempertimbangkan untuk mengubah struktur kelas saya:

Satu-satunya masalah yang bisa saya lihat adalah pernyataan kondisional, dan itu juga secara samar-samar - jadi mengapa refactor ini? Menurut Anda, masalah apa yang mungkin muncul di masa depan yang belum saya ramalkan?

Pembaruan : Saya lupa menyebutkan - ini adalah struktur kelas yang penulis berikan sebagai solusi - pola strategi :

Pola strategi

Aditya MP
sumber
29
Jangan belajar pemrograman berorientasi objek dari buku PHP.
giorgiosironi
4
@giorgiosironi Saya sudah menyerah mengevaluasi bahasa. Saya menggunakan PHP setiap hari, dan itulah yang tercepat bagi saya untuk mempelajari konsep-konsep baru di dalamnya. Selalu ada orang yang membenci suatu bahasa (Java: slidesha.re/91C4pX ) - memang lebih sulit untuk menemukan pembenci Java daripada perbedaan PHP, tapi tetap saja. Mungkin ada masalah dengan OO PHP, tetapi untuk saat ini saya tidak dapat meluangkan waktu untuk mempelajari sintaks Java, "Java way", dan OOP. Selain itu, pemrograman desktop asing bagi saya. Saya orang web yang lengkap. Tetapi sebelum Anda bisa sampai ke JSP, Anda diduga perlu mengetahui desktop Java (Tidak ada kutipan, apakah saya salah?)
Aditya MP
Gunakan konstanta untuk "tarif tetap" / "tarif per jam" dan "seminar" / "kuliah" alih-alih string.
Tom Marthenal

Jawaban:

19

Lucu bahwa buku itu tidak menyatakannya dengan jelas, tetapi alasan mengapa buku ini menyukai hierarki kelas jika pernyataan di dalam kelas yang sama mungkin adalah prinsip Terbuka / Tertutup Aturan desain perangkat lunak yang terkenal ini menyatakan bahwa kelas harus ditutup untuk modifikasi tetapi terbuka untuk ekstensi.

Dalam contoh Anda, menambahkan jenis pelajaran baru berarti mengubah kode sumber kelas Pelajaran, menjadikannya rapuh dan rentan mengalami regresi. Jika Anda memiliki kelas dasar dan satu turunan untuk setiap jenis pelajaran, Anda hanya perlu menambahkan subkelas lain, yang umumnya dianggap lebih bersih.

Anda dapat meletakkan aturan itu di bawah bagian "Pernyataan Bersyarat" jika Anda mau, namun saya menganggap bahwa "rambu" agak kabur. Jika pernyataan pada umumnya hanya kode bau, gejala. Mereka dapat dihasilkan dari berbagai macam keputusan desain yang buruk.

guillaume31
sumber
3
Ini akan menjadi ide yang jauh lebih baik untuk menggunakan ekstensi fungsional daripada pewarisan.
DeadMG
6
Tentu saja ada / pendekatan lain yang lebih baik untuk masalah ini. Namun, ini jelas sebuah buku tentang pemrograman berorientasi objek dan terbuka / tertutup prinsip hanya memukul saya sebagai yang cara klasik mendekati jenis desain dilema di OO.
guillaume31
Bagaimana cara terbaik untuk mendekati masalah ? Tidak ada gunanya mengajarkan solusi yang lebih rendah hanya karena itu OO.
DeadMG
16

Saya kira apa yang ingin diajarkan buku ini adalah untuk menghindari hal-hal seperti:

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

Interpretasi saya terhadap buku ini adalah Anda harus memiliki tiga kelas:

  • AbstractLesson
  • HourlyRateLesson
  • FixedRateLesson

Anda kemudian harus mengimplementasikan kembali costfungsi pada kedua subkelas.

Pendapat pribadi saya

untuk kasus sederhana seperti itu: jangan. Aneh bahwa buku Anda mendukung hierarki kelas yang lebih dalam untuk menghindari pernyataan kondisional yang sederhana. Terlebih lagi, dengan spesifikasi input Anda, Lessonharus menjadi pabrik dengan pengiriman yang merupakan pernyataan bersyarat. Maka dengan solusi buku Anda akan memiliki:

  • 3 kelas, bukan 1
  • 1 tingkat warisan bukannya 0
  • jumlah pernyataan bersyarat yang sama

Itu lebih rumit tanpa manfaat tambahan.

Simon Bergot
sumber
8
Dalam hal ini , ya, ini terlalu rumit. Namun dalam sistem yang lebih besar, Anda akan menambahkan lebih banyak pelajaran, seperti SemesterLesson, MasterOneWeekLesson, dll Sebuah kelas akar abstrak, atau lebih baik lagi interface, pasti cara untuk pergi kemudian. Tetapi ketika Anda hanya memiliki dua kasus, itu tergantung pada kebijaksanaan penulis.
Michael K
5
Saya setuju dengan Anda secara umum. Namun, contoh-contoh pendidikan sering dibuat-buat dan dirancang secara berlebihan untuk menggambarkan suatu hal. Anda mengabaikan pertimbangan lain sejenak untuk tidak membingungkan masalah yang ada, dan memperkenalkan pertimbangan itu nanti.
Karl Bielefeldt
+1 Rincian lengkap yang bagus. Komentar, "Itu lebih rumit tanpa manfaat tambahan" menggambarkan konsep 'biaya peluang' dalam pemrograman dengan sempurna. Teori! = Kepraktisan.
Evan Plaice
7

Contoh dalam buku ini sangat aneh. Dari pertanyaan Anda, pernyataan aslinya adalah:

Tujuannya adalah untuk menghasilkan Jenis Pelajaran (Kuliah atau Seminar), dan Biaya untuk pelajaran tergantung pada apakah itu pelajaran harga per jam atau tetap.

yang, dalam konteks bab tentang OOP, akan berarti bahwa penulis mungkin ingin Anda memiliki struktur berikut:

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

Tapi kemudian muncul input yang Anda kutip:

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

yaitu sesuatu yang disebut kode yang diketik secara ketat , dan yang, jujur, dalam konteks ini ketika pembaca dimaksudkan untuk belajar OOP, mengerikan.

Ini juga berarti bahwa jika saya benar tentang maksud dari penulis buku (yaitu membuat kelas-kelas abstrak dan diwariskan yang saya sebutkan di atas), ini akan menyebabkan kode duplikat dan sangat tidak dapat dibaca dan jelek:

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.

Adapun "rambu-rambu":

  • Duplikasi Kode

    Kode Anda tidak memiliki duplikasi. Kode yang penulis harapkan agar Anda tulis.

  • Kelas Yang Tahu Terlalu Banyak Tentang Konteksnya

    Kelas Anda hanya meneruskan string dari input ke output, dan tidak tahu apa-apa. Kode yang diharapkan di sisi lain dapat dikritik karena tahu terlalu banyak.

  • Jack of All Trades - Kelas yang mencoba melakukan banyak hal

    Sekali lagi, Anda hanya melewati string, tidak lebih.

  • Pernyataan bersyarat

    Ada satu pernyataan kondisional dalam kode Anda. Itu mudah dibaca dan mudah dimengerti.


Mungkin, pada kenyataannya, penulis mengharapkan Anda untuk menulis kode yang jauh lebih refactored daripada yang dengan enam kelas di atas. Misalnya Anda dapat menggunakan pola pabrik untuk pelajaran dan biaya, dll.

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

Dalam hal ini, Anda akan mendapatkan sembilan kelas, yang akan menjadi contoh sempurna arsitektur berlebih .

Alih-alih, Anda berakhir dengan kode yang mudah dipahami dan bersih, yang sepuluh kali lebih pendek.

Arseni Mourzenko
sumber
Wow, tebakan Anda tepat! Lihatlah gambar yang saya unggah - penulis merekomendasikan hal yang persis sama.
Aditya MP
Saya akan sangat senang untuk menerima jawaban ini, tapi saya juga seperti jawaban @ ian31 ini :( Oh, apa yang harus saya lakukan!
Aditya MP
5

Pertama-tama Anda dapat mengubah "angka ajaib" seperti 30 dan 5 dalam fungsi cost () menjadi variabel yang lebih bermakna :)

Dan jujur ​​saja, saya pikir Anda tidak perlu khawatir tentang ini. Ketika Anda perlu mengubah kelas maka Anda akan mengubahnya. Cobalah untuk menciptakan nilai terlebih dahulu kemudian beralih ke refactorization.

Dan mengapa Anda berpikir bahwa Anda salah? Bukankah kelas ini "cukup baik" untukmu? Mengapa Anda khawatir tentang buku;)

Michal Franc
sumber
1
Terimakasih telah menjawab! Memang, refactoring akan datang nanti. Namun, saya menulis kode ini untuk belajar, mencoba menyelesaikan masalah yang disajikan dalam buku dengan cara saya sendiri dan melihat bagaimana saya salah ...
Aditya MP
2
Tapi Anda akan tahu ini nanti. Ketika kelas ini akan digunakan di bagian lain dari sistem dan Anda harus menambahkan sesuatu yang baru. Tidak ada solusi "peluru perak" :) Sesuatu bisa baik atau buruk itu tergantung pada sistem, persyaratan dll. Saat belajar OOP Anda harus banyak gagal: P Kemudian dengan waktu Anda akan melihat beberapa masalah dan pola umum. Tbh contoh ini terlalu sederhana untuk memberi saran. Ohh, saya benar-benar merekomendasikan Posting ini dari Joel :) Arsitektur astronot mengambil alih joelonsoftware.com/items/2008/05/01.html
Michal Franc
@adityamenon Maka jawaban Anda adalah "refactoring akan datang nanti". Hanya tambahkan kelas-kelas lain begitu mereka diperlukan untuk membuat kode lebih sederhana - biasanya, saat itulah use case serupa ke-3 datang. Lihat jawaban Simon.
Izkata
3

Sama sekali tidak perlu menerapkan kelas tambahan apa pun. Anda memiliki sedikit MAGIC_NUMBERmasalah dalam cost()fungsi Anda , tetapi hanya itu. Yang lainnya adalah over-engineering besar-besaran. Namun, sayangnya itu umum untuk saran yang sangat buruk diberikan- Circle mewarisi Shape, misalnya. Jelas tidak efisien untuk menurunkan kelas untuk warisan sederhana dari satu fungsi. Anda bisa menggunakan pendekatan fungsional sebagai gantinya.

DeadMG
sumber
1
Ini menarik. Bisakah Anda menjelaskan bagaimana Anda melakukannya dengan sebuah contoh?
guillaume31
+1, saya setuju, tidak ada alasan untuk merekayasa berlebihan / menyulitkan sedikit fungsi tersebut.
GrandmasterB
@ ian31: Lakukan apa, khususnya?
DeadMG
@DeadMG "gunakan pendekatan fungsional", "gunakan ekstensi fungsional daripada warisan"
guillaume31