Prinsip paling tidak mengejutkan (POLA) dan antarmuka

17

Seperempat abad yang lalu ketika saya sedang belajar C ++, saya diajarkan bahwa antarmuka harus memaafkan dan sejauh mungkin tidak peduli tentang urutan metode yang dipanggil karena konsumen mungkin tidak memiliki akses ke sumber atau dokumentasi sebagai pengganti ini.

Namun, setiap kali saya membimbing para programmer junior dan senior devs mendengar saya, mereka telah bereaksi dengan heran yang membuat saya bertanya-tanya apakah ini benar-benar suatu hal atau apakah itu baru saja keluar dari mode.

Jelas seperti lumpur?

Pertimbangkan antarmuka dengan metode ini (untuk membuat file data):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

Sekarang Anda tentu saja bisa melalui ini secara berurutan, tetapi mengatakan Anda tidak peduli tentang nama file (berpikir a.out) atau apa header dan trailer string dimasukkan, Anda bisa menelepon AddDataLine.

Contoh yang kurang ekstrim mungkin menghilangkan header dan trailer.

Namun yang lain mungkin mengatur string header dan trailer sebelum file dibuka.

Apakah ini prinsip desain antarmuka yang diakui atau hanya jalan POLA sebelum diberi nama?

NB tidak terjebak dalam hal-hal kecil dari antarmuka ini, itu hanya contoh demi pertanyaan ini.

Robbie Dee
sumber
10
Prinsip "paling mencengangkan" jauh lebih lazim dalam desain antarmuka pengguna daripada dalam desain "Antarmuka aplikasi programmer". Alasannya adalah bahwa pengguna dari situs web atau program tidak bisa diharapkan untuk membaca setiap petunjuk sama sekali sebelum menggunakannya, sementara programmer diharapkan, setidaknya pada prinsipnya, untuk membaca dokumentasi API sebelum pemrograman dengan mereka.
Kilian Foth
7
@KilianFoth: Saya cukup yakin Wikipedia salah tentang ini - POLA tidak hanya tentang desain antarmuka pengguna, istilah "prinsip kejutan paling sedikit" (yang persis sama) juga digunakan oleh Bob Martin untuk fungsi dan desain kelas dalam bukunya. Buku "Kode Bersih".
Doc Brown
2
Seringkali, antarmuka yang tidak berubah lebih baik. Anda dapat menentukan semua data yang ingin Anda atur pada waktu konstruksi. Tidak ada ambiguitas yang tersisa dan kelas menjadi lebih mudah untuk ditulis. (Terkadang skema ini tidak mungkin, tentu saja.)
usr
4
Tidak setuju sepenuhnya tentang POLA yang tidak berlaku untuk API. Itu berlaku untuk apa pun yang diciptakan manusia untuk manusia lain. Ketika sesuatu bertindak seperti yang diharapkan, mereka lebih mudah dikonseptualisasikan dan karenanya menciptakan beban kognitif yang lebih rendah, memungkinkan orang untuk melakukan lebih banyak hal dengan sedikit usaha.
Gort the Robot

Jawaban:

25

Salah satu cara di mana Anda dapat tetap berpegang pada prinsip paling tidak heran adalah dengan mempertimbangkan prinsip-prinsip lain seperti ISP dan SRP , atau bahkan KERING .

Dalam contoh spesifik yang Anda berikan, sarannya adalah ada ketergantungan tertentu dalam pemesanan untuk memanipulasi file; tetapi API Anda mengontrol akses file dan format data, yang baunya sedikit seperti pelanggaran SRP.

Edit / Perbarui: ini juga menyarankan bahwa API itu sendiri meminta pengguna untuk melanggar KERING, karena mereka perlu mengulangi langkah yang sama setiap kali mereka menggunakan API .

Pertimbangkan API alternatif di mana operasi IO terpisah dari operasi data. dan tempat API sendiri 'memiliki' pemesanan:

ContentBuilder

SetHeader( ... )
AddLine( ... )
SetTrailer ( ... )

Penulis File

Open(filename) 
Write(content) throws InvalidContentException
Close()

Dengan pemisahan di atas, ContentBuildertidak perlu benar-benar "melakukan" apa pun selain menyimpan baris / header / trailer (Mungkin juga ContentBuilder.Serialize()metode yang mengetahui urutannya). Dengan mengikuti prinsip-prinsip SOLID lainnya, tidak penting lagi apakah Anda mengatur tajuk atau cuplikan sebelum atau setelah menambahkan baris, karena tidak ada apa pun di dalam ContentBuilderfile yang benar-benar ditulis untuk diarsipkan sampai diteruskan FileWriter.Write.

Ini juga memiliki manfaat tambahan menjadi sedikit lebih fleksibel; misalnya, mungkin bermanfaat untuk menulis konten ke logger diagnostik, atau mungkin membagikannya di jaringan daripada menulisnya langsung ke file.

Saat merancang API Anda juga harus mempertimbangkan pelaporan kesalahan, apakah itu keadaan, nilai pengembalian, pengecualian, panggilan balik, atau yang lainnya. Pengguna API mungkin berharap untuk dapat secara terprogram mendeteksi setiap pelanggaran kontraknya, atau bahkan kesalahan lain yang tidak dapat dikontrolnya seperti kesalahan I / O file.

Ben Cottrell
sumber
Persis apa yang saya cari - terima kasih! Dari artikel ISP: "(ISP) menyatakan bahwa tidak ada klien yang harus dipaksa untuk bergantung pada metode yang tidak digunakannya"
Robbie Dee
5
Ini bukan jawaban yang buruk, namun pembuat konten tetap dapat diimplementasikan dengan cara di mana urutan panggilan SetHeaderatau AddLinemasalah. Untuk menghilangkan ketergantungan ini bukan ISP atau SRP, itu hanya POLA.
Doc Brown
Ketika pesanan penting, Anda masih dapat memenuhi POLA dengan mendefinisikan operasi sehingga melakukan langkah selanjutnya membutuhkan nilai yang dikembalikan dari langkah sebelumnya, sehingga menegakkan pesanan dengan sistem tipe. FileWriterkemudian dapat membutuhkan nilai dari ContentBuilderlangkah terakhir dalam Writemetode untuk memastikan semua konten input selesai, membuat InvalidContentExceptiontidak perlu.
Dan Lyons
@DanLyons Saya merasa agak dekat dengan situasi yang penanya coba hindari; di mana pengguna API perlu tahu atau peduli tentang pesanan. Idealnya, API itu sendiri harus menegakkan pesanan, jika tidak berpotensi meminta pengguna untuk melanggar KERING. Itulah alasan untuk berpisah ContentBuilderdan memungkinkan FileWriter.Writeuntuk merangkum sedikit pengetahuan itu. Pengecualian akan diperlukan jika terjadi sesuatu yang tidak beres dengan konten, (misalnya, seperti tajuk yang hilang). Pengembalian juga bisa berfungsi, tapi saya bukan penggemar mengubah pengecualian menjadi kode pengembalian.
Ben Cottrell
Tapi jelas layak menambahkan lebih banyak catatan tentang KERING dan memesan jawaban.
Ben Cottrell
12

Ini bukan hanya tentang POLA, tetapi juga tentang mencegah keadaan tidak sah sebagai kemungkinan sumber bug.

Mari kita lihat bagaimana kami dapat memberikan beberapa kendala pada contoh Anda tanpa memberikan implementasi konkret:

Langkah pertama: Jangan biarkan apa pun dipanggil, sebelum file dibuka.

CreateDataFileInterface
  + OpenFile(filename : string) : DataFileInterface

DataFileInterface
  + SetHeaderString(header : string) : void
  + WriteDataLine(data : string) : void
  + SetTrailerString(trailer : string) : void
  + Close() : void

Sekarang harus jelas bahwa CreateDataFileInterface.OpenFileharus dipanggil untuk mengambil DataFileInterfacecontoh, di mana data aktual dapat ditulis.

Langkah kedua: Pastikan, header dan trailer selalu diatur.

CreateDataFileInterface
  + OpenFile(filename : string, header: string, trailer : string) : DataFileInterface

DataFileInterface
  + WriteDataLine(data : string) : void
  + Close() : void

Sekarang Anda harus memberikan semua parameter yang diperlukan dimuka untuk mendapatkan DataFileInterface: nama file, header dan trailer. Jika string trailer tidak tersedia hingga semua baris ditulis, Anda juga dapat memindahkan parameter ini ke Close()(kemungkinan mengubah nama metode WriteTrailerAndClose()) sehingga file setidaknya tidak dapat diselesaikan tanpa string trailer.


Untuk membalas komentar:

Saya suka pemisahan antarmuka. Tapi saya cenderung berpikir bahwa saran Anda tentang penegakan (misalnya WriteTrailerAndClose ()) sedang melakukan pelanggaran terhadap SRP. (Ini adalah sesuatu yang telah saya perjuangkan dengan beberapa kesempatan, tetapi saran Anda tampaknya menjadi contoh yang mungkin). Bagaimana Anda merespons?

Benar. Saya tidak ingin lebih berkonsentrasi pada contoh daripada yang perlu untuk membuat poin saya, tapi itu pertanyaan yang bagus. Dalam hal ini saya pikir saya akan menyebutnya Finalize(trailer)dan berpendapat bahwa itu tidak terlalu banyak. Menulis trailer dan menutup adalah detail implementasi belaka. Tetapi jika Anda tidak setuju atau memiliki situasi yang sama di mana itu berbeda, berikut ini adalah solusi yang mungkin:

CreateDataFileInterface
  + OpenFile(filename : string, header : string) : IncompleteDataFileInterface

IncompleteDataFileInterface
  + WriteDataLine(data : string) : void
  + FinalizeWithTrailer(trailer : string) : CompleteDataFileInterface

CompleteDataFileInterface
  + Close()

Saya tidak akan benar-benar melakukannya untuk contoh ini tetapi ini menunjukkan bagaimana melakukan teknik tersebut secara konsekuen.

By the way, saya berasumsi bahwa metode sebenarnya harus dipanggil dalam urutan ini, misalnya untuk menulis banyak baris secara berurutan. Jika ini tidak diperlukan, saya selalu lebih suka pembangun, seperti yang disarankan oleh Ben Cottrel .

Fabian Schmengler
sumber
1
Anda telah jatuh ke dalam perangkap, saya secara eksplisit memperingatkan Anda untuk menghindari sejak awal. Nama file tidak diperlukan - header dan trailer juga tidak. Tapi tema umum untuk memisahkan antarmuka adalah yang bagus sehingga +1 :-)
Robbie Dee
Oh, kalau begitu saya salah paham, saya pikir ini menggambarkan maksud pengguna, bukan implementasinya.
Fabian Schmengler
Saya suka pemisahan antarmuka. Tapi saya cenderung berpikir bahwa saran Anda tentang penegakan hukum (mis. WriteTrailerAndClose()) Sedang melakukan pelanggaran terhadap SRP. (Ini adalah sesuatu yang telah saya perjuangkan dengan beberapa kesempatan, tetapi saran Anda tampaknya menjadi contoh yang mungkin). Bagaimana Anda merespons?
kmote
1
@kmote jawaban terlalu panjang untuk dikomentari, lihat pembaruan saya
Fabian Schmengler
1
Jika nama file adalah opsional, Anda dapat memberikan OpenFilekelebihan yang tidak memerlukan satu.
5gon12eder