Banyak kelas kecil vs. logika yang rumit (tetapi) rumit

24

Saya bertanya-tanya apa yang lebih baik dalam hal desing OOP yang baik, kode bersih, fleksibilitas dan menghindari bau kode di masa depan. Situasi gambar, di mana Anda memiliki banyak objek yang sangat mirip, Anda perlu mewakili sebagai kelas. Kelas-kelas ini tanpa fungsi spesifik apa pun, hanya kelas data dan berbeda hanya berdasarkan nama (dan konteks) Contoh:

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

Apakah lebih baik menyimpannya di kelas terpisah, untuk mendapatkan keterbacaan "lebih baik", tetapi dengan banyak pengulangan kode, atau akan lebih baik menggunakan pewarisan untuk menjadi lebih KERING?

Dengan menggunakan warisan, Anda akan berakhir dengan sesuatu seperti ini, tetapi nama kelas akan kehilangan makna kontekstualnya (karena is-a, bukan has-a):

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

Saya tahu pewarisan bukan dan tidak boleh digunakan untuk penggunaan kembali kode, tapi saya tidak melihat cara lain yang mungkin bagaimana mengurangi pengulangan kode dalam kasus seperti itu.

pengguna969153
sumber

Jawaban:

58

Aturan umum berbunyi "Memilih delegasi atas warisan", bukan "menghindari semua warisan". Jika objek memiliki hubungan logis dan B dapat digunakan di mana pun A diharapkan, adalah praktik yang baik untuk menggunakan warisan. Namun, jika objek kebetulan memiliki bidang dengan nama yang sama, dan tidak memiliki hubungan domain, jangan gunakan warisan.

Cukup sering, membayar untuk mengajukan pertanyaan "alasan perubahan" dalam proses desain: Jika kelas A mendapat bidang tambahan, apakah Anda berharap bahwa kelas B juga mendapatkannya? Jika itu benar, benda-benda berbagi hubungan, dan warisan adalah ide bagus. Jika tidak, menderita pengulangan kecil untuk menjaga konsep yang berbeda dalam kode yang berbeda.

thiton
sumber
Tentu, alasan perubahan adalah poin yang baik, tetapi bagaimana dalam situasi, di mana kelas-kelas ini berada dan akan selalu konstan?
user969153
20
@ user969153: Ketika kelas akan benar-benar konstan, itu tidak masalah. Semua rekayasa perangkat lunak yang baik adalah untuk pengelola masa depan, yang perlu melakukan perubahan. Jika tidak akan ada perubahan di masa depan, semuanya berjalan, tapi percayalah, Anda akan selalu memiliki perubahan kecuali jika Anda memprogram untuk trashpile.
thiton
@ user969153: Yang sedang berkata, "alasan perubahan" adalah heuristik. Ini membantu Anda untuk memutuskan, tidak lebih.
thiton
2
Jawaban yang bagus. Alasan untuk berubah adalah S dalam singkatan SOLID paman bob yang akan membantu dalam merancang perangkat lunak yang baik.
Klee
4
@ user969153, Dalam pengalaman saya, "di mana kelas-kelas ini berada dan akan selalu konstan?" bukan kasus penggunaan yang valid :) Saya belum bekerja pada aplikasi berukuran penuh yang tidak mengalami perubahan yang sama sekali tidak terduga.
cdkMoose
18

Dengan menggunakan warisan, Anda akan berakhir dengan sesuatu seperti ini, tetapi nama kelas akan kehilangan makna kontekstualnya (karena is-a, bukan has-a):

Persis. Lihat ini, di luar konteks:

Class D : C
{
    String age;
}

Properti apa yang dimiliki D? Dapatkah kau ingat? Bagaimana jika Anda memperumit hierarki lebih lanjut:

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

Lalu bagaimana jika, horor demi horor, Anda ingin menambahkan bidang imageUrl ke F tetapi tidak E? Anda tidak dapat memperolehnya dari C dan E.

Saya telah melihat situasi aktual di mana banyak tipe Komponen yang berbeda semuanya memiliki nama, ID, dan Deskripsi. Tapi ini bukan karena sifat komponen mereka, itu hanya kebetulan. Masing-masing memiliki ID karena mereka disimpan dalam database. Nama dan deskripsi untuk ditampilkan.

Produk juga memiliki ID, Nama dan Deskripsi, untuk alasan yang sama. Tetapi mereka bukan komponen, juga bukan komponen produk. Anda tidak akan pernah melihat dua hal bersama.

Tetapi, beberapa pengembang telah membaca tentang KERING dan memutuskan untuk menghapus setiap tanda duplikasi dari kode. Jadi dia menyebut semuanya produk dan menghilangkan kebutuhan untuk mendefinisikan bidang-bidang itu. Mereka didefinisikan dalam Produk dan segala sesuatu yang membutuhkan bidang-bidang itu dapat berasal dari Produk.

Saya tidak bisa mengatakan ini dengan cukup kuat: Ini bukan ide yang baik.

KERING bukan tentang menghilangkan kebutuhan untuk properti umum. Jika suatu objek bukan Produk, jangan sebut itu Produk. Jika suatu Produk tidak MEMERLUKAN Deskripsi, karena sifatnya merupakan suatu Produk, jangan mendefinisikan Deskripsi sebagai Properti Produk.

Akhirnya, kami memiliki Komponen tanpa deskripsi. Jadi kami mulai memiliki Deskripsi nol di objek tersebut. Tidak dapat dibatalkan. Batal. Selalu. Untuk Produk apa pun dari tipe X ... atau lebih tinggi Y ... dan N.

Ini adalah pelanggaran Prinsip Pengganti Liskov yang mengerikan.

KERING adalah tentang tidak pernah menempatkan diri Anda pada posisi di mana Anda dapat memperbaiki bug di satu tempat dan lupa melakukannya di tempat lain. Ini tidak akan terjadi karena Anda memiliki dua objek dengan properti yang sama. Tak pernah. Jadi jangan khawatir tentang itu.

Jika konteks kelas membuat jelas bahwa Anda harus, yang akan sangat jarang, maka dan hanya kemudian Anda harus mempertimbangkan kelas orang tua yang sama. Tapi, jika ini properti, pertimbangkan mengapa Anda tidak menggunakan antarmuka.

pdr
sumber
4

Warisan adalah cara untuk mencapai perilaku polimorfik. Jika kelas Anda tidak memiliki perilaku, maka warisan tidak ada gunanya. Dalam hal ini, saya bahkan tidak akan menganggapnya objek / kelas, tetapi struktur sebagai gantinya.

Jika Anda ingin dapat menempatkan struktur yang berbeda di berbagai bagian perilaku Anda, maka pertimbangkan antarmuka dengan metode / properti get / set, seperti IHasName, IHasAge, dll. Ini akan membuat desainnya jauh lebih bersih dan memungkinkan komposisi yang lebih baik dari ini semacam hiearchies.

Euforia
sumber
3

Bukankah ini untuk apa antarmuka? Kelas yang tidak terkait dengan fungsi yang berbeda tetapi dengan properti yang serupa.

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;
Pieter B
sumber
1

Pertanyaan Anda tampaknya dibingkai dalam konteks bahasa yang tidak mendukung pewarisan berganda tetapi tidak secara spesifik menentukan ini. Jika Anda bekerja dengan bahasa yang mendukung banyak pewarisan, ini menjadi mudah untuk dipecahkan dengan hanya satu tingkat pewarisan.

Berikut adalah contoh bagaimana hal ini dapat diselesaikan menggunakan multiple inheritance.

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
pgraham
sumber