Penjelasan tentang bagaimana "Katakan, Jangan Tanyakan" dianggap sebagai OO yang baik

49

Blogpost ini diposting di Hacker News dengan beberapa upvotes. Berasal dari C ++, sebagian besar contoh ini tampaknya bertentangan dengan apa yang telah saya ajarkan.

Seperti contoh # 2:

Buruk:

def check_for_overheating(system_monitor)
  if system_monitor.temperature > 100
    system_monitor.sound_alarms
  end
end

versus bagus:

system_monitor.check_for_overheating

class SystemMonitor
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

Saran dalam C ++ adalah Anda harus lebih memilih fungsi bebas daripada fungsi anggota karena meningkatkan enkapsulasi. Keduanya identik secara semantik, jadi mengapa lebih suka pilihan yang memiliki akses ke lebih banyak negara?

Contoh 4:

Buruk:

def street_name(user)
  if user.address
    user.address.street_name
  else
    'No street name on file'
  end
end

versus bagus:

def street_name(user)
  user.address.street_name
end

class User
  def address
    @address || NullAddress.new
  end
end

class NullAddress
  def street_name
    'No street name on file'
  end
end

Mengapa tanggung jawab Useruntuk memformat string kesalahan yang tidak terkait? Bagaimana jika saya ingin melakukan sesuatu selain mencetak 'No street name on file'jika tidak ada jalan? Bagaimana jika jalan itu dinamai hal yang sama?


Bisakah seseorang mencerahkan saya pada keuntungan dan alasan "Katakan, Jangan Tanyakan"? Saya tidak mencari yang lebih baik, tetapi mencoba memahami sudut pandang penulis.

Pubby
sumber
Contoh kode mungkin Ruby dan bukan Python, saya tidak tahu.
Pubby
2
Saya selalu bertanya-tanya apakah sesuatu seperti contoh pertama bukan pelanggaran SRP?
stijn
1
Anda dapat membaca bahwa: pragprog.com/articles/tell-dont-ask
Mik378
Rubi. @ adalah singkatan misalnya dan Python mengakhiri bloknya secara implisit dengan spasi putih.
Erik Reppen
3
"Saran dalam C ++ adalah Anda harus lebih memilih fungsi bebas daripada fungsi anggota karena mereka meningkatkan enkapsulasi." Aku tidak tahu siapa yang memberitahumu, tapi itu tidak benar. Fungsi bebas dapat digunakan untuk meningkatkan enkapsulasi, tetapi tidak selalu meningkatkan enkapsulasi.
Rob K

Jawaban:

81

Menanyakan objek tentang kondisinya, dan kemudian memanggil metode pada objek tersebut berdasarkan keputusan yang dibuat di luar objek, berarti bahwa objek tersebut sekarang merupakan abstraksi yang bocor; beberapa perilakunya terletak di luar objek, dan keadaan internal terpapar (mungkin tidak perlu) ke dunia luar.

Anda harus berusaha memberi tahu objek apa yang Anda ingin mereka lakukan; jangan bertanya kepada mereka tentang keadaan mereka, membuat keputusan, dan kemudian memberi tahu mereka apa yang harus dilakukan.

Masalahnya adalah bahwa, sebagai penelepon, Anda seharusnya tidak membuat keputusan berdasarkan keadaan objek yang dipanggil yang mengakibatkan Anda kemudian mengubah keadaan objek. Logika yang Anda laksanakan mungkin tanggung jawab objek yang disebut, bukan milik Anda. Bagi Anda untuk membuat keputusan di luar objek melanggar enkapsulasi.

Tentu, Anda bisa mengatakan, itu sudah jelas. Saya tidak akan pernah menulis kode seperti itu. Tetap saja, sangat mudah untuk membuai memeriksa beberapa objek referensi dan kemudian memanggil metode yang berbeda berdasarkan hasil. Tapi itu mungkin bukan cara terbaik untuk melakukannya. Katakan objek apa yang Anda inginkan. Biarkan ia mengetahui bagaimana melakukannya. Berpikir secara deklaratif alih-alih secara prosedural!

Lebih mudah untuk keluar dari jebakan ini jika Anda mulai dengan merancang kelas berdasarkan tanggung jawab mereka; Anda kemudian dapat berkembang secara alami untuk menentukan perintah yang dapat dieksekusi kelas, sebagai lawan dari kueri yang memberi tahu Anda tentang keadaan objek.

http://pragprog.com/articles/tell-dont-ask

Robert Harvey
sumber
4
Contoh teks melarang banyak hal yang jelas merupakan praktik yang baik.
DeadMG
13
@DeadMG melakukan apa yang Anda katakan hanya kepada orang-orang yang dengan kasar mengikutinya, yang secara membabi buta mengabaikan "pragmatis" dalam nama situs dan pemikiran utama penulis situs yang telah secara jelas dinyatakan dalam buku utama mereka: "tidak ada yang namanya solusi terbaik ... "
agas
2
Jangan pernah baca buku. Saya juga tidak mau. Saya hanya membaca contoh teks, yang benar-benar adil.
DeadMG
3
@DeadMG jangan khawatir. Sekarang setelah Anda tahu titik kunci yang menempatkan contoh ini (dan yang lainnya dari pragprog dalam hal ini) dalam konteks yang dimaksud ("tidak ada solusi terbaik ..."), tidak apa-apa untuk tidak membaca buku
agas
1
Saya masih tidak yakin apa yang Katakan, Jangan Tanyakan seharusnya dijabarkan untuk Anda tanpa konteks tetapi ini adalah saran OOP yang sangat bagus.
Erik Reppen
16

Secara umum, artikel itu menyarankan agar Anda tidak mengekspos negara anggota untuk orang lain untuk mempertimbangkan, jika Anda bisa memikirkannya sendiri .

Namun, apa yang tidak dinyatakan dengan jelas adalah bahwa undang-undang ini jatuh ke dalam batas yang sangat jelas ketika alasannya lebih dari tanggung jawab kelas tertentu. Misalnya, setiap kelas yang tugasnya memegang nilai atau memberikan nilai - terutama nilai generik, atau di mana kelas memberikan perilaku yang harus diperluas.

Misalnya, jika sistem menyediakan temperaturesebagai kueri, maka besok, klien dapat check_for_underheatingtanpa harus berubah SystemMonitor. Ini tidak terjadi ketika SystemMonitormengimplementasikannya check_for_overheatingsendiri. Dengan demikian, SystemMonitorkelas yang tugasnya membangkitkan alarm ketika suhu terlalu tinggi mengikuti ini- tetapi SystemMonitorkelas yang tugasnya adalah memungkinkan sepotong kode lain untuk membaca suhu sehingga dapat mengontrol, katakanlah, TurboBoost atau sesuatu seperti itu , seharusnya tidak.

Perhatikan juga bahwa contoh kedua tanpa tujuan menggunakan Null Object Anti-pattern.

DeadMG
sumber
19
"Objek kosong" bukankah aku menyebutnya anti-pola, jadi aku bertanya-tanya apa alasanmu melakukannya?
Konrad Rudolph
4
Cukup yakin bahwa tidak ada yang memiliki metode yang ditentukan sebagai "Tidak melakukan apa-apa". Itu membuat memanggil mereka agak sia-sia. Itu berarti bahwa setiap objek yang mengimplementasikan Objek Null merusak LSP, paling tidak, dan menggambarkan dirinya sebagai operasi pelaksana yang, pada kenyataannya, tidak. Pengguna mengharapkan nilai kembali. Kebenaran program mereka tergantung padanya. Anda hanya membawa lebih banyak masalah dengan berpura-pura bahwa itu adalah nilai ketika bukan. Pernahkah Anda mencoba men-debug metode yang gagal diam-diam? Itu tidak mungkin dan tidak ada yang harus menderita melalui itu.
DeadMG
4
Saya berpendapat bahwa ini sepenuhnya tergantung pada domain masalah.
Konrad Rudolph
5
@DeadMG Saya setuju bahwa contoh di atas adalah penggunaan yang buruk dari pola Obyek Null, tapi ada adalah manfaat untuk menggunakannya. Beberapa kali saya telah menggunakan implementasi 'no-op' dari beberapa antarmuka atau lainnya untuk menghindari pengecekan null atau memiliki 'null' yang merembes ke sistem.
Maks
6
Tidak yakin saya mengerti maksud Anda dengan "klien dapat check_for_underheatingtanpa harus mengubah SystemMonitor". Bagaimana klien berbeda dari SystemMonitorpada saat itu? Bukankah sekarang Anda menghilangkan logika pemantauan di berbagai kelas? Saya juga tidak melihat masalah dengan kelas monitor yang memberikan informasi sensor ke kelas lain sambil menyimpan fungsi alarm. Pengontrol boost harus mengendalikan boost tanpa khawatir menaikkan alarm jika suhunya terlalu tinggi.
TMN
9

Masalah sebenarnya dengan contoh overheating Anda adalah bahwa aturan untuk apa yang memenuhi syarat sebagai overheating tidak mudah bervariasi untuk sistem yang berbeda. Misalkan Sistem A adalah seperti yang Anda miliki (suhu> 100 terlalu panas) tetapi Sistem B lebih halus (suhu> 93 terlalu panas). Apakah Anda mengubah fungsi kontrol untuk memeriksa jenis sistem, dan kemudian menerapkan nilai yang benar?

if (system is a System_A and system_monitor.temp >100)
  system_monitor.sound_alarms
else if (system is a System_B and system_monitor.temp > 93)
  system_monitor.sound_alarms
end

Atau apakah Anda memiliki masing-masing jenis sistem menentukan kapasitas pemanasannya?

SUNTING:

system.check_for_overheating

class SystemA : System
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

class SystemB : System
  def check_for_overheating
    if temperature > 93
      sound_alarms
    end
  end
end

Cara sebelumnya membuat fungsi kontrol Anda menjadi jelek ketika Anda mulai berurusan dengan lebih banyak sistem. Yang terakhir memungkinkan fungsi kontrol menjadi stabil seiring berjalannya waktu.

Matthew Flynn
sumber
1
Mengapa tidak minta setiap sistem mendaftar ke monitor. Selama pendaftaran mereka dapat menunjukkan kapan terjadi overheating.
Martin York
@LokiAstari - Anda bisa, tetapi kemudian Anda bisa lari ke sistem baru yang juga sensitif terhadap kelembaban atau tekanan atmosfer. Prinsipnya adalah untuk mengabstraksi apa yang bervariasi - dalam hal ini adalah kerentanan terhadap overheating
Matthew Flynn
1
Inilah mengapa Anda harus memiliki model jitu. Anda memberi tahu sistem tentang kondisi saat ini dan memberi tahu Anda jika berada di luar kondisi kerja normal. Dengan demikian Anda tidak perlu memodifikasi SystemMoniter. Itu enkapsulasi untuk Anda.
Martin York
@LokiAstari - Saya pikir kita berbicara dengan tujuan silang di sini - Saya benar-benar ingin membuat sistem yang berbeda, bukan monitor yang berbeda. Masalahnya, sistem harus tahu kapan ia dalam keadaan yang menimbulkan alarm, berbeda dengan beberapa fungsi pengontrol luar. SystemA harus memiliki kriteria, SystemB harus memiliki sendiri. Pengontrol seharusnya hanya dapat bertanya (secara berkala) apakah sistemnya OK atau tidak.
Matthew Flynn
6

Pertama, saya merasa saya harus menganggap karakterisasi contoh Anda sebagai "buruk" dan "baik". Artikel ini menggunakan istilah "Tidak begitu baik" dan "Lebih baik", saya pikir istilah itu dipilih karena suatu alasan: ini adalah pedoman, dan tergantung pada keadaan, pendekatan "Tidak begitu baik" mungkin tepat, atau memang satu-satunya solusi.

Ketika diberi pilihan, Anda harus memberikan preferensi untuk memasukkan fungsionalitas apa pun yang hanya bergantung pada kelas di kelas daripada di luarnya - alasannya adalah karena enkapsulasi, dan fakta bahwa itu membuatnya lebih mudah untuk mengembangkan kelas dari waktu ke waktu. Kelas ini juga melakukan pekerjaan yang lebih baik dalam mengiklankan kemampuannya daripada banyak fungsi gratis.

Terkadang Anda harus memberi tahu, karena keputusannya bergantung pada sesuatu di luar kelas atau karena itu adalah sesuatu yang tidak Anda inginkan dilakukan sebagian besar pengguna kelas. Terkadang Anda ingin memberi tahu, karena perilakunya kontra intuitif untuk kelas, dan Anda tidak ingin membingungkan sebagian besar pengguna kelas.

Misalnya, Anda mengeluh tentang alamat jalan yang mengembalikan pesan kesalahan, bukan, apa yang dilakukannya adalah memberikan nilai default. Namun terkadang nilai default tidak sesuai. Jika ini Negara Bagian atau Kota, Anda mungkin ingin default saat menetapkan catatan untuk seorang salesman atau pengambil survei, sehingga semua yang tidak dikenal pergi ke orang tertentu. Di sisi lain, jika Anda mencetak amplop, Anda mungkin lebih suka pengecualian atau penjaga yang mencegah Anda membuang-buang kertas pada surat yang tidak dapat dikirim.

Jadi mungkin ada kasus di mana "Tidak begitu baik" adalah cara untuk pergi, tetapi secara umum, "Lebih baik" adalah, well, lebih baik.

jmoreno
sumber
3

Data / Objek Anti-Simetri

Seperti yang ditunjukkan orang lain, Tell-Dont-Ask khusus untuk kasus di mana Anda mengubah status objek setelah Anda bertanya (lihat misalnya teks Pragprog yang diposting di tempat lain di halaman ini). Ini tidak selalu terjadi, misalnya objek 'pengguna' tidak berubah setelah diminta untuk penggunanya. Alamat. Karena itu dapat diperdebatkan apakah ini kasus yang sesuai untuk menerapkan Tell-Dont-Ask.

Tell-Dont-Ask berkaitan dengan tanggung jawab, dengan tidak menarik logika dari kelas yang harus dibenarkan di dalamnya. Tetapi tidak semua logika yang berhubungan dengan objek adalah logika dari objek tersebut. Ini mengisyaratkan pada tingkat yang lebih dalam, bahkan di luar Tell-Dont-Ask, dan saya ingin menambahkan komentar singkat tentang itu.

Sebagai soal desain arsitektur, Anda mungkin ingin memiliki objek yang benar-benar hanya wadah untuk properti, mungkin bahkan tidak berubah, dan kemudian menjalankan berbagai fungsi di atas koleksi objek seperti itu, mengevaluasi, memfilter atau mengubah mereka daripada mengirimkannya perintah (yang merupakan lebih banyak lagi domain Tell-Dont-Ask).

Keputusan yang lebih tepat untuk masalah Anda tergantung pada apakah Anda berharap memiliki data yang stabil (objek deklaratif) tetapi dengan mengubah / menambahkan di sisi fungsi. Atau jika Anda berharap memiliki seperangkat fungsi yang stabil dan terbatas tetapi mengharapkan lebih banyak fluks pada tingkat objek, misalnya dengan menambahkan tipe baru. Dalam situasi pertama Anda lebih suka fungsi bebas, dalam metode objek kedua.

Bob Martin, dalam bukunya "Clean Code", menyebut ini "Data / Object Anti-Symmetry" (hal.95 dst), komunitas lain mungkin menyebutnya sebagai " masalah ekspresi ".

ThomasH
sumber
3

Paradigma ini kadang-kadang disebut sebagai 'Katakan, jangan tanya' , artinya beri tahu objek apa yang harus dilakukan, jangan tanya tentang keadaannya; dan terkadang sebagai 'Tanya, jangan katakan' , artinya minta objek untuk melakukan sesuatu untuk Anda, jangan katakan seperti apa keadaannya. Apa pun cara terbaik dalam praktik yang sama - cara suatu objek harus melakukan tindakan adalah perhatian objek itu, bukan objek pemanggil. Antarmuka harus menghindari mengekspos negara mereka (misalnya melalui pengakses atau properti publik) dan bukannya mengekspos metode 'melakukan' yang implementasinya buram. Yang lain telah membahas hal ini dengan tautan ke programmer pragmatis.

Aturan ini terkait dengan aturan tentang menghindari kode "titik ganda" atau "panah ganda", sering disebut sebagai 'Hanya bicara dengan teman langsung', yang menyatakan foo->getBar()->doSomething()buruk, alih-alih menggunakan foo->doSomething();yang merupakan panggilan pembungkus di sekitar fungsionalitas bilah, dan diimplementasikan sebagai sederhana return bar->doSomething();- jika foobertanggung jawab untuk mengelola bar, maka biarkan melakukannya!

Nicholas Shanks
sumber
1

Selain jawaban baik lainnya tentang "katakan, jangan tanya", beberapa komentar tentang contoh spesifik Anda yang mungkin membantu:

Saran dalam C ++ adalah Anda harus lebih memilih fungsi bebas daripada fungsi anggota karena meningkatkan enkapsulasi. Keduanya identik secara semantik, jadi mengapa lebih suka pilihan yang memiliki akses ke lebih banyak negara?

Pilihan itu tidak memiliki akses ke lebih banyak negara. Mereka berdua menggunakan jumlah negara yang sama untuk melakukan pekerjaan mereka, tetapi contoh 'buruk' mengharuskan negara kelas menjadi publik untuk melakukan pekerjaannya. Lebih lanjut, perilaku kelas itu dalam contoh 'buruk' tersebar ke fungsi bebas, membuatnya lebih sulit untuk ditemukan dan lebih sulit untuk refactor.

Mengapa Pengguna bertanggung jawab untuk memformat string kesalahan yang tidak terkait? Bagaimana jika saya ingin melakukan sesuatu selain mencetak 'Tidak ada nama jalan di file' jika tidak ada jalan? Bagaimana jika jalan itu dinamai hal yang sama?

Mengapa 'street_name' bertanggung jawab untuk melakukan keduanya 'mendapatkan nama jalan' dan 'memberikan pesan kesalahan'? Setidaknya dalam versi 'baik', masing-masing bagian memiliki satu tanggung jawab. Namun, itu bukan contoh yang bagus.

Telastyn
sumber
2
Itu tidak benar. Anda menganggap bahwa memeriksa overheating adalah satu-satunya hal yang waras berkaitan dengan suhu. Bagaimana jika kelas dimaksudkan untuk menjadi salah satu dari sejumlah pemantau suhu, dan suatu sistem harus mengambil tindakan berbeda tergantung pada banyak hasil mereka, misalnya? Ketika perilaku ini dapat dibatasi untuk perilaku yang telah ditentukan dari satu contoh, maka pasti. Lain, itu jelas tidak bisa diterapkan.
DeadMG
Tentu, atau jika termostat dan alarm ada di kelas yang berbeda (sebagaimana seharusnya).
Telastyn
1
@DeadMG: Saran umum adalah untuk membuat hal-hal pribadi / terlindungi sampai Anda membutuhkan akses ke sana. Meskipun contoh khusus ini adalah meh, itu tidak membantah praktik standar.
Guvante
Contoh dalam artikel tentang praktik menjadi 'meh' agak membantahnya. Jika praktik ini standar karena manfaatnya besar, lalu mengapa kesulitan mencari contoh yang cocok?
Stijn de Witt
1

Jawaban-jawaban ini sangat bagus, tetapi berikut ini contoh lain yang perlu ditekankan: perhatikan bahwa ini biasanya cara untuk menghindari duplikasi. Misalnya, katakan Anda memiliki beberapa tempat dengan kode seperti:

Product product = productMgr.get(productUuid)
if (product.userUuid != currentUser.uuid) {
    throw BlahException("This product doesn't belong to this user")
}

Itu berarti Anda sebaiknya memiliki sesuatu seperti ini:

Product product = productMgr.get(productUuid, currentUser)

Karena duplikasi itu berarti sebagian besar klien dari antarmuka Anda akan menggunakan metode baru, alih-alih mengulangi logika yang sama di sana-sini. Anda memberi delegasi pekerjaan yang ingin Anda lakukan, alih-alih meminta info yang Anda butuhkan untuk melakukannya sendiri.

antonio.fornie
sumber
0

Saya percaya ini lebih benar ketika menulis objek tingkat tinggi, tetapi kurang benar ketika turun ke tingkat yang lebih dalam misalnya perpustakaan kelas karena tidak mungkin untuk menulis setiap metode tunggal untuk memuaskan semua konsumen kelas.

Misalnya # 2, saya pikir ini terlalu disederhanakan. Jika kita benar-benar akan mengimplementasikan ini, SystemMonitor akan akhirnya memiliki kode untuk akses perangkat keras tingkat rendah dan logika untuk abstraksi tingkat tinggi yang tertanam di kelas yang sama. Sayangnya, jika kita mencoba memisahkannya menjadi dua kelas, kita akan melanggar "Katakan, Jangan tanya" itu sendiri.

Contoh # 4 kurang lebih sama - itu menanamkan logika UI ke dalam data tier. Sekarang jika kita akan memperbaiki apa yang ingin dilihat pengguna jika tidak ada alamat, kita harus memperbaiki objek di tingkat data, dan bagaimana jika dua proyek menggunakan objek yang sama tetapi perlu menggunakan teks yang berbeda untuk alamat null?

Saya setuju bahwa jika kita dapat menerapkan "Tell, Don't ask" untuk semuanya, itu akan sangat berguna - saya sendiri akan senang jika saya bisa mengatakan daripada bertanya (dan melakukannya sendiri) dalam kehidupan nyata! Namun, sama seperti dalam kehidupan nyata, kelayakan solusi sangat terbatas pada kelas tingkat tinggi.

tia
sumber