Bagaimana konsep perubahan kelas ketika meneruskan data ke konstruktor alih-alih parameter metode?

12

Katakanlah kita membuat pengurai. Satu implementasi dapat:

public sealed class Parser1
{
    public string Parse(string text)
    {
       ...
    }
}

Atau sebaliknya, kami dapat meneruskan teks ke konstruktor:

public sealed class Parser2
{
    public Parser2(string text)
    {
       this.text = text;
    }

    public string Parse()
    {
       ...
    }
}

Penggunaannya sederhana dalam kedua kasus, tetapi apa artinya mengaktifkan input parameter Parser1, dibandingkan dengan yang lain? Pesan apa yang saya kirim ke sesama programmer, ketika mereka melihat API? Juga, apakah ada kelebihan / kekurangan teknis dalam kasus-kasus tertentu?

Pertanyaan lain muncul ketika saya menyadari bahwa antarmuka akan sangat tidak berarti dalam implementasi kedua:

public interface IParser
{
    string Parse();
}

... di mana antarmuka pada antarmuka pertama dapat melayani setidaknya beberapa tujuan. Apakah itu menandakan sesuatu yang khusus, bahwa suatu kelas "antarmuka" atau tidak?

ciscoheat
sumber
Ini adalah pertama kalinya saya melihat beberapa tanya jawab yang berguna tentang bagaimana semantik OO sebenarnya mengekspresikan niat. Sampai sekarang semuanya hanya gula sintaksis, bagi saya.

Jawaban:

11

Semantik berbicara, dalam OOP Anda hanya harus melewati konstruktor seperangkat parameter yang diperlukan untuk membangun kelas - sama ketika Anda memanggil suatu metode, Anda hanya harus memberikan parameter yang diperlukan untuk mengeksekusi logika bisnisnya.

Parameter yang harus Anda lewati di konstruktor adalah parameter yang tidak memiliki nilai default yang masuk akal dan jika kelas Anda tidak dapat diubah (atau memang a struct) maka semua properti non-default harus dilewati.

Mengenai dua contoh Anda:

  • jika Anda meneruskan textke konstruktor, itu mengisyaratkan bahwa Parser2kelas akan dibangun secara khusus untuk mem-parsing instance teks itu di lain waktu. Ini akan menjadi parser khusus. Ini biasanya terjadi ketika membangun kelas sangat mahal atau halus, sebuah RegEx mungkin dikompilasi dalam konstruktor, jadi setelah Anda memegang contoh Anda dapat menggunakannya kembali tanpa harus membayar biaya kompilasi; contoh lain adalah menginisialisasi PRNG - lebih baik jika dilakukan jarang.
  • jika Anda meneruskan textke metode, itu menandakan yang Parser1dapat digunakan kembali untuk mem-parsing teks yang berbeda melalui panggilan.
Sklivvz
sumber
3
Saya akan menambahkan bahwa ada, lemah, sinyal bahwa Parser1 mempertahankan keadaan - yaitu bahwa string teks tertentu dapat menghasilkan hasil yang berbeda tergantung pada yang sebelumnya telah dilakukan pada contoh tat. Ini belum tentu demikian, tetapi mungkin saja demikian.
jmoreno
8

Nah, mari kita ingat apa artinya melewatkan variabel sebagai parameter konstruktor: Anda menginisialisasi objek untuk menggunakan variabel instansinya dalam metode objek. Intinya adalah Anda mungkin ingin menggunakannya dalam lebih dari satu metode karena Anda ingin memiliki kohesi yang tinggi di kelas Anda.

Melewati parameter langsung ke metode berarti dengan cara mengirim pesan ke objek dan mungkin menerima jawaban. Dengan itu, klien menginginkan objek untuk memberikan layanan untuknya.

Jadi sebagai kesimpulan, itu adalah dua cara yang sangat berbeda untuk melewatkan parameter dan Anda harus memilih apakah objek Anda harus memberikan layanan atau menyediakan beberapa fungsi secara inheren sambil mengelola beberapa informasi secara internal.

McMannus
sumber
+1. Kohesi adalah cara saya memutuskan apakah itu termasuk pada metode atau konstruktor.
jhewlett
4

Penggunaannya sederhana dalam kedua kasus, tetapi apa artinya mengaktifkan input parameter ke Parser1, dibandingkan dengan yang lain?

Ini adalah perubahan desain mendasar. Dan desain harus menyampaikan maksud dan makna. Apakah Anda perlu memiliki objek terpisah untuk setiap string yang ingin Anda parsing? Dengan kata lain, mengapa kita membutuhkan instance parser dengan stringX dan contoh lain dengan stringY? Ada apa dengan parse (ing) dan string yang diberikan bahwa keduanya harus hidup dan mati bersama? Dengan asumsi bahwa "implementasi [parsing] yang mendasarinya" (seperti yang dikatakan Robert Harvey) tidak berubah, tampaknya tidak ada gunanya. Dan itupun IMHO dipertanyakan.

Bagaimana konsep perubahan kelas ketika meneruskan data ke konstruktor alih-alih parameter metode?

Parameter konstruktor memberi tahu saya hal-hal ini diperlukan untuk suatu objek. Status yang tepat tidak dijamin tanpa mereka. Juga, saya tahu bagaimana / mengapa satu parser secara fundamental berbeda dari yang lain.

Parameter konstruktor membuat saya tidak perlu tahu terlalu banyak tentang cara menggunakan kelas. Jika sebaliknya saya seharusnya menetapkan properti tertentu - bagaimana saya tahu itu? Segelas cacing terbuka. Sifat apa? Dalam urutan apa? Sebelum saya menggunakan metode apa? dan seterusnya.

Pertanyaan lain muncul ketika saya menyadari bahwa antarmuka akan sangat tidak berarti dalam implementasi kedua:

Antarmuka, seperti dalam API, adalah metode dan properti yang diekspos ke kode klien. Jangan terlibat secara public interface { ... }eksklusif. Jadi arti dari antarmuka adalah dilema parameter baik-atau konstruktor vs metode, TIDAK public interface Iparservspublic sealed class Parser

The sealedkelas aneh. Jika saya berpikir tentang implementasi parser yang berbeda - Anda memang menyebutkan "Iparser" - maka warisan adalah pikiran pertama saya. Itu hanya perpanjangan konseptual yang alami dalam pemikiran saya. IE semua ParserXs pada dasarnya Parsers. Bagaimana cara mengatakannya? ... German Shepard adalah seekor anjing (warisan), tetapi saya dapat melatih burung beo saya untuk menggonggong (bertindak seperti anjing - "antarmuka"); tetapi Polly bukan seekor anjing, hanya berpura-pura, telah belajar sebagian dari dogness. Kelas, abstrak atau lainnya, berfungsi dengan baik sebagai antarmuka .

radarbob
sumber
Jika berjalan seperti pengurai dan berbicara seperti pengurai, itu adalah ... bebek!
2

Versi kedua dari kelas dapat dibuat tidak berubah.

Antarmuka masih dapat digunakan untuk memberikan kemampuan untuk menukar implementasi yang mendasarinya.

Robert Harvey
sumber
Tidak bisakah itu dibuat abadi di versi pertama juga, dengan melewatkan data di dalam kelas secara fungsional?
ciscoheat
2
Benar. Tetapi pola umum dari ketidakberdayaan adalah mengatur anggota kelas menggunakan konstruktor, dan memiliki sifat read-only. Untuk pemrograman fungsional, Anda bahkan tidak perlu kelas.
Robert Harvey
Scylla dan Charybdis: apakah saya memilih OO atau data yang tidak dapat diubah? Saya tidak pernah mendengarnya seperti itu sebelumnya.
2

Parser1

Bangunan dengan konstruktor default dan meneruskan input teks ke dalam metode menyiratkan bahwa Parser1 dapat digunakan kembali.

Parser2

Melewati teks input ke konstruktor menyiratkan bahwa Parser2 baru harus dibuat untuk setiap string input.

Mike Partridge
sumber
Tentu, jadi jika kita membawanya ke tingkat berikutnya, apa yang harus disimpulkan tentang objek yang dapat digunakan kembali dibandingkan dengan yang tidak dapat digunakan kembali?
ciscoheat
Saya tidak akan berasumsi lebih dari itu, alih-alih menunda dokumentasi.
Mike Partridge
"Objek yang dapat digunakan kembali" di mana identitasnya berubah adalah non-sequitur. Dan dengan kerangka kerja terkelola yang berarti bahwa Anda bahkan tidak perlu membuang sesuatu, cukup menimpa mereka atau membuatnya keluar dari ruang lingkup, itu tentu tidak perlu. Bangun jauh!