Bagaimana memperdebatkan pola pikir "sepenuhnya publik" tentang desain kelas objek bisnis ini

9

Kami melakukan banyak pengujian unit dan refactoring objek bisnis kami, dan saya tampaknya memiliki pendapat yang sangat berbeda tentang desain kelas daripada rekan-rekan lainnya.

Contoh kelas yang saya bukan penggemar:

public class Foo
{
    private string field1;
    private string field2;
    private string field3;
    private string field4;
    private string field5;

    public Foo() { }

    public Foo(string in1, string in2)
    {
        field1 = in1;
        field2 = in2;
    }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
    }

    public Prop1
    { get { return field1; } }
    { set { field1 = value; } }

    public Prop2
    { get { return field2; } }
    { set { field2 = value; } }

    public Prop3
    { get { return field3; } }
    { set { field3 = value; } }

    public Prop4
    { get { return field4; } }
    { set { field4 = value; } }

    public Prop5
    { get { return field5; } }
    { set { field5 = value; } }

}

Di kelas "nyata", mereka tidak semua string, tetapi dalam beberapa kasus kami memiliki 30 bidang dukungan untuk properti publik sepenuhnya.

Saya benci kelas ini, dan saya tidak tahu apakah saya hanya pilih-pilih. Beberapa hal yang perlu diperhatikan:

  • Bidang dukungan pribadi tanpa logika di properti, tampaknya tidak perlu dan membengkak kelas
  • Banyak konstruktor (agak ok) tetapi ditambah dengan
  • semua properti memiliki setter publik, saya bukan penggemar.
  • Secara potensial tidak ada properti yang akan diberi nilai karena konstruktor kosong, jika penelepon tidak mengetahui, Anda berpotensi mendapatkan beberapa yang sangat tidak diinginkan dan sulit untuk diuji perilaku.
  • Terlalu banyak properti! (dalam 30 kasus)

Saya merasa jauh lebih sulit untuk benar - benar mengetahui keadaan objek Foosaat itu, sebagai pelaksana. Argumen dibuat "kita mungkin tidak memiliki informasi yang diperlukan untuk ditetapkan Prop5pada saat pembangunan objek. Ok, saya kira saya bisa mengerti itu, tetapi jika itu hanya membuat Prop5setter publik, tidak selalu hingga 30 properti di kelas.

Apakah saya hanya menjadi pemilih dan / atau gila karena menginginkan kelas yang "mudah digunakan" sebagai lawan dari "mudah untuk menulis (semuanya publik)"? Kelas-kelas seperti berteriak di atas kepada saya, saya tidak tahu bagaimana ini akan digunakan, jadi saya hanya akan membuat semuanya publik untuk berjaga-jaga .

Jika saya tidak terlalu pilih-pilih, argumen apa yang bagus untuk memerangi pemikiran seperti ini? Saya tidak pandai mengartikulasikan argumen, karena saya menjadi sangat frustrasi ketika mencoba menyampaikan pendapat saya (tentu saja tidak sengaja).

Kritner
sumber
3
Objek harus selalu dalam keadaan valid; yaitu, Anda seharusnya tidak perlu memiliki objek yang sebagian instantiated . Beberapa informasi bukan bagian dari keadaan inti suatu objek sementara informasi lainnya adalah (misalnya meja harus memiliki kaki tetapi kain itu opsional); informasi opsional dapat diberikan setelah instantiation, informasi inti harus disediakan di instantiation. Jika beberapa informasi adalah inti tetapi tidak diketahui di Instansiasi maka saya akan mengatakan kelas perlu refactoring. Sulit untuk mengatakan lebih banyak tanpa mengetahui konteks kelas.
Marvin
1
Mengatakan "kami mungkin tidak memiliki informasi yang diperlukan untuk mengatur Prop5 pada saat pembangunan objek" membuat saya bertanya "apakah Anda mengatakan bahwa akan ada beberapa kali Anda akan memiliki informasi itu pada saat konstruksi dan saat lain Anda tidak akan punya informasi itu? " Itu membuat saya bertanya-tanya apakah sebenarnya ada dua hal berbeda yang coba diwakili oleh kelas yang satu ini, atau mungkin apakah kelas ini harus dipecah menjadi kelas yang lebih kecil. Tetapi jika itu dilakukan "berjaga-jaga" itu juga buruk, IMO; yang mengatakan kepada saya bahwa tidak ada desain yang jelas di tempat bagaimana benda dibuat / dihuni.
Apprentice Dr. Wily
3
Untuk bidang dukungan pribadi tanpa logika di properti, apakah ada alasan mengapa Anda tidak menggunakan properti otomatis ?
Carson63000
2
@Kritner seluruh poin dari properti otomatis adalah bahwa jika Anda benar-benar membutuhkan logika di sana di masa depan, Anda dapat menambahkannya di tempat otomatis getatau set:-)
Carson63000

Jawaban:

10

Kelas yang sepenuhnya publik memiliki pembenaran untuk situasi tertentu, dan juga yang ekstrem lainnya, kelas dengan hanya satu metode publik (dan mungkin banyak metode pribadi). Dan kelas dengan beberapa metode publik, beberapa pribadi juga.

Itu semua tergantung pada jenis abstraksi yang akan Anda modelkan dengan mereka, lapisan mana yang Anda miliki dalam sistem Anda, tingkat enkapsulasi yang Anda butuhkan di lapisan yang berbeda, dan (tentu saja) aliran pemikiran mana yang berasal dari kelas penulis dari. Anda dapat menemukan semua jenis ini dalam kode SOLID.

Ada seluruh buku yang ditulis tentang kapan harus memilih jenis desain, jadi saya tidak akan mencantumkan aturan apa pun di sini tentang hal itu, ruang di bagian ini tidak akan cukup. Namun, jika Anda memiliki contoh dunia nyata untuk abstraksi yang ingin Anda modelkan dengan sebuah kelas, saya yakin komunitas di sini dengan senang hati akan membantu Anda untuk meningkatkan desain.

Untuk membahas poin Anda yang lain:

  • "bidang dukungan pribadi tanpa logika di properti": Ya, Anda benar, untuk getter dan setter sepele ini hanya "noise" yang tidak perlu. Untuk menghindari "bloat" semacam ini, C # memiliki sintaks pintas untuk metode get / set properti:

Jadi, bukannya

   private string field1;
   public string Prop1
   { get { return field1; } }
   { set { field1 = value; } }

menulis

   public string Prop1 { get;set;}

atau

   public string Prop1 { get;private set;}
  • "Beberapa konstruktor": itu bukan masalah. Itu mendapat masalah ketika ada duplikasi kode yang tidak perlu di sana, seperti ditunjukkan dalam contoh Anda, atau hirarki panggilan berbelit-belit. Ini dapat dengan mudah diselesaikan dengan refactoring bagian-bagian umum menjadi fungsi yang terpisah, dan dengan mengatur rantai konstruktor secara searah

  • "Secara potensial tidak ada properti yang akan diberi nilai karena konstruktor kosong": dalam C #, setiap tipe data memiliki nilai default yang jelas. Jika properti tidak diinisialisasi secara eksplisit dalam konstruktor, mereka mendapatkan nilai default ini ditetapkan. Jika ini digunakan dengan sengaja , itu benar-benar oke - jadi konstruktor kosong mungkin ok jika penulis tahu apa yang dia lakukan.

  • "Terlalu banyak properti! (Dalam 30 kasus)": ya, jika Anda bebas merancang kelas seperti itu dengan cara greenfield, 30 terlalu banyak, saya setuju. Namun, tidak semua dari kita memiliki kemewahan ini (bukankah Anda menulis di komentar di bawah ini adalah sistem warisan?). Terkadang Anda harus memetakan catatan dari database yang ada, atau file, atau data dari API pihak ketiga ke sistem Anda. Jadi untuk kasus-kasus ini, 30 atribut mungkin sesuatu yang harus dijalani seseorang.

Doc Brown
sumber
Terima kasih, ya saya tahu POCO / POJO memiliki tempatnya, dan contoh saya cukup abstrak. Saya tidak berpikir saya bisa lebih spesifik tanpa pergi ke novel.
Kritner
@Kritner: lihat hasil edit saya
Doc Brown
ya biasanya ketika saya membuat kelas baru saya pergi rute properti otomatis. Memiliki bidang dukungan pribadi "hanya karena" adalah (dalam pikiran saya) tidak diperlukan dan bahkan menyebabkan masalah. Ketika Anda memiliki ~ 30 properti ke sebuah kelas dan 100+ kelas, tidak terlalu jauh untuk mengatakan bahwa akan ada beberapa pengaturan properti atau mendapatkan dari bidang dukungan yang salah ... Saya sudah menemukan ini beberapa kali di refactoring kami sebenarnya :)
Kritner
terima kasih, nilai default untuk string nullbukan? jadi jika salah satu properti yang sebenarnya digunakan dalam .ToUpper(), tetapi nilai tidak pernah ditetapkan untuk itu, itu akan membuang pengecualian runtime. Ini adalah contoh yang baik mengapa diperlukan data untuk kelas harus memiliki harus ditetapkan selama konstruksi objek. Tidak hanya menyerahkannya kepada pengguna. Terima kasih
Kritner
Selain terlalu banyak properti yang tidak disetel, masalah utama dengan kelas ini adalah bahwa ia memiliki logika nol (ZRP) dan merupakan pola dasar untuk Model Anemik. Tanpa kebutuhan untuk ini yang dapat diekspresikan dengan kata-kata, seperti DTO, itu adalah desain yang buruk.
user949300
2
  1. Dengan mengatakan bahwa "Bidang dukungan pribadi tanpa logika di properti, tampaknya tidak perlu dan membesar-besarkan kelas" Anda sudah memiliki argumen yang layak.

  2. Sepertinya bukan beberapa konstruktor yang menjadi masalah di sini.

  3. Sedangkan untuk memiliki semua properti sebagai publik ... Mungkin pikirkan seperti ini, jika Anda ingin menyinkronkan akses ke semua properti ini di antara utas Anda akan mengalami kesulitan karena Anda mungkin harus menulis kode sinkronisasi di mana pun mereka berada bekas. Namun, jika semua properti tertutup oleh getter / setter maka Anda dapat dengan mudah membangun sinkronisasi ke dalam kelas.

  4. Saya pikir ketika Anda mengatakan "sulit untuk menguji perilaku" argumen itu berbicara sendiri. Tes Anda mungkin harus memeriksa apakah bukan nol atau sesuatu seperti itu (setiap tempat digunakan properti). Saya tidak benar-benar tahu karena saya tidak tahu seperti apa tes / aplikasi Anda.

  5. Anda benar terlalu banyak properti, dapatkah Anda mencoba menggunakan pewarisan (jika propertinya cukup mirip) dan kemudian memiliki daftar / array menggunakan obat generik? Kemudian Anda bisa menulis getter / setter untuk mengakses informasi dan menyederhanakan cara Anda akan berinteraksi semua properti.

Mengintip
sumber
1
Terima kasih - # 1 dan # 4 Saya pasti merasa paling nyaman dengan membuat argumen saya. Tidak yakin apa yang Anda maksud di # 3. # 5 sebagian besar properti di kelas membentuk kunci utama pada db. Kami menggunakan kunci kompetitif, terkadang hingga 8 kolom - tapi itu masalah lain. Saya sedang berpikir tentang mencoba untuk menempatkan bagian-bagian yang membentuk kunci ke dalam kelas / struct sendiri, yang akan menghilangkan banyak properti (setidaknya di kelas ini ) - tetapi ini adalah aplikasi warisan dengan ribuan baris kode dengan banyak penelepon. Jadi saya kira saya harus banyak memikirkan :)
Kritner
Eh. Jika kelas tidak dapat diubah, tidak akan ada alasan untuk menulis kode sinkronisasi apa pun di dalam kelas.
RubberDuck
@RubberDuck Apakah kelas ini tidak berubah? Apa maksudmu?
Mengintai
3
@StevieV pasti bukan abadi. Kelas apa pun dengan seter properti dapat berubah.
RubberDuck
1
@Kritner Bagaimana cara properti digunakan sebagai kunci utama? Itu mungkin memerlukan beberapa logika (pemeriksaan nol) atau aturan (mis. NAME lebih diprioritaskan daripada AGE). Menurut OOP, kode ini termasuk dalam kelas ini. Bisakah Anda menggunakannya sebagai argumen?
user949300
2

Seperti yang Anda katakan, Fooadalah objek bisnis. Itu harus merangkum beberapa perilaku dalam metode sesuai dengan domain Anda dan datanya harus tetap dienkapsulasi mungkin.

Pada contoh yang Anda tunjukkan, Foolebih mirip DTO dan bertentangan dengan semua prinsip OOP (lihat Model Domain Anemik )!

Sebagai perbaikan saya sarankan:

  • Jadikan kelas ini sebagai abadi mungkin. Seperti yang Anda katakan, Anda ingin membuat beberapa pengujian unit. Salah satu kemampuan wajib untuk pengujian unit adalah determinisme , dan determinisme dapat dicapai melalui ketidakmampuan karena itu memecahkan masalah efek samping .
  • Hancurkan kelas dewa ini menjadi beberapa kelas yang masing-masing hanya melakukan 1 hal (lihat SRP ). Unit menguji 30 atribut kelas dalam benar-benar PITA .
  • Hapus redundansi konstruktor dengan hanya memiliki 1 konstruktor utama dan yang lainnya memanggil konstruktor utama.
  • Hapus getter yang tidak perlu, saya benar-benar ragu semua getter benar-benar bermanfaat.
  • Kembalikan logika bisnis di kelas bisnis, di sinilah tempatnya!

Ini bisa menjadi kelas refactored potensial dengan komentar ini:

public sealed class Foo
{
    private readonly string field1;
    private readonly string field2;
    private readonly string field3;
    private readonly string field4;

    public Foo() : this("default1", "default2")
    { }

    public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
    { }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
        field4 = in4;
    }

    //Methods with business logic

    //Really needed ?
    public Prop1
    { get; }

    //Really needed ?
    public Prop2
    { get; }

    //Really needed ?
    public Prop3
    { get; }

    //Really needed ?
    public Prop4
    { get; }

    //Really needed ?
    public Prop5
    { get; }
}
Tutul
sumber
2

Bagaimana memperdebatkan pola pikir "sepenuhnya publik" tentang desain kelas objek bisnis ini

  • "Ada beberapa metode yang tersebar di kelas klien melakukan lebih dari 'tugas DTO belaka'. Mereka milik bersama."
  • "Ini mungkin 'DTO' tetapi memiliki identitas bisnis - kita perlu mengesampingkan Persamaan."
  • "Kita perlu mengurutkan ini - kita perlu mengimplementasikan IComparable"
  • "Kami merangkai beberapa properti ini. Mari kita menimpa ToString sehingga setiap klien tidak harus menulis kode ini."
  • "Kami memiliki banyak klien yang melakukan hal yang sama ke kelas ini. Kami perlu MENINGGALKAN kodenya."
  • "Klien memanipulasi koleksi hal-hal ini. Jelas itu seharusnya kelas koleksi kustom; dan banyak poin sebelumnya akan membuat koleksi kustom lebih fungsional daripada saat ini."
  • "Lihat semua manipulasi string yang membosankan, terbuka, dan merangkum bahwa dalam metode yang relevan dengan bisnis akan meningkatkan produktivitas pengkodean kami."
  • "Menarik metode ini bersama-sama ke dalam kelas akan membuatnya dapat diuji karena kita tidak harus berurusan dengan kelas klien yang lebih kompleks."
  • "Kita sekarang dapat menguji unit metode refactored itu. Karena, untuk alasan x, y, z klien tidak dapat diuji.

  • Sejauh argumen di atas dapat dibuat, secara agregat:

    • The fungsi menjadi dapat digunakan kembali.
    • KERING
    • Ini dipisahkan dari klien.
    • pengkodean terhadap kelas lebih cepat dan lebih sedikit kesalahan.
  • "Kelas yang dilakukan dengan baik menyembunyikan status dan memperlihatkan fungsionalitas. Ini adalah proposisi awal untuk merancang kelas OO."
  • "Hasil akhir melakukan pekerjaan yang jauh lebih baik dalam mengekspresikan domain bisnis; entitas yang berbeda dan interaksi eksplisit mereka."
  • "Fungsionalitas 'overhead' ini (yaitu bukan persyaratan fungsional eksplisit, tetapi harus dilakukan) jelas menonjol dan menganut Prinsip Tanggung Jawab Tunggal.
radarbob
sumber