Sebagai contoh:
Hanya lamaran pekerjaan yang belum ditinjau atau disetujui, yang dapat diperbarui. Dengan kata lain, seseorang dapat memperbarui formulir alat kerjanya sampai HR mulai memeriksanya, atau sudah diterima.
Jadi lamaran kerja bisa di 4 negara:
DITERAPKAN (kondisi awal), IN_REVIEW, DISETUJUI, DITERBITKAN
Bagaimana saya mencapai perilaku seperti itu?
Tentunya saya dapat menulis metode pembaruan () di kelas Aplikasi, memeriksa status Aplikasi, dan tidak melakukan apa pun atau melemparkan pengecualian jika Aplikasi tidak dalam status yang diperlukan
Tetapi kode semacam ini tidak membuatnya jelas aturan seperti itu ada, itu memungkinkan siapa saja untuk memanggil metode pembaruan (), dan hanya setelah gagal klien tahu operasi seperti itu tidak diizinkan. Oleh karena itu klien perlu menyadari bahwa upaya seperti itu mungkin gagal, oleh karena itu berhati-hatilah. Klien menyadari hal-hal seperti itu juga berarti bahwa logika bocor ke luar.
Saya mencoba membuat kelas yang berbeda untuk setiap negara (ApprovedApplication dll) dan menempatkan operasi yang diizinkan hanya pada kelas yang diizinkan, tetapi pendekatan semacam ini terasa salah juga.
Apakah ada pola desain resmi, atau sepotong kode sederhana, untuk menerapkan perilaku seperti itu?
this kind of code does not make it obvious such a rule exists
- Inilah sebabnya mengapa kode memiliki dokumentasi. Penulis kode yang baik akan mengikuti saran Euphoric dan memberikan metode untuk membiarkan pihak luar menguji aturan sebelum mencobanya di perangkat keras.Jawaban:
Situasi seperti ini cukup sering muncul. Misalnya, file hanya dapat dimanipulasi saat terbuka, dan jika Anda mencoba melakukan sesuatu dengan file setelah ditutup, Anda mendapatkan pengecualian runtime.
Keinginan Anda ( dinyatakan dalam pertanyaan Anda sebelumnya ) untuk menggunakan sistem jenis bahasa untuk memastikan bahwa hal yang salah tidak dapat terjadi adalah mulia, karena kesalahan waktu kompilasi selalu lebih baik daripada kesalahan runtime. Namun, tidak ada pola desain yang saya tahu untuk situasi seperti ini, mungkin karena itu akan menyebabkan lebih banyak masalah daripada yang akan dipecahkan. (Itu tidak praktis.)
Hal yang paling dekat dengan situasi Anda yang saya ketahui adalah memodelkan berbagai status objek yang sesuai dengan kemampuan berbeda melalui antarmuka tambahan, tetapi dengan cara ini Anda hanya mengurangi jumlah tempat dalam kode tempat kesalahan runtime dapat terjadi, Anda tidak menghapus kemungkinan kesalahan runtime.
Jadi, dalam situasi Anda, Anda akan mendeklarasikan sejumlah antarmuka yang menggambarkan apa yang dapat dilakukan dengan objek Anda di berbagai statusnya, dan objek Anda akan mengembalikan referensi ke antarmuka yang tepat saat transisi keadaan.
Jadi, misalnya,
approve()
metode kelas Anda akan mengembalikanApprovedApplication
antarmuka. Antarmuka akan diimplementasikan secara pribadi, (melalui kelas bersarang,) sehingga kode yang hanya memiliki referensi ke tidakApplication
dapat memanggilApprovedApplication
metode apa pun . Kemudian, kode yang memanipulasi aplikasi yang disetujui secara eksplisit menyatakan niatnya untuk melakukannya pada waktu kompilasi dengan mengharuskanApprovedApplication
untuk bekerja dengannya. Tapi tentu saja, jika Anda menyimpan antarmuka ini di suatu tempat, dan kemudian Anda melanjutkan untuk menggunakan antarmuka ini setelahdecline()
metode ini dipanggil, Anda masih akan mendapatkan kesalahan runtime. Saya tidak berpikir ada solusi sempurna untuk masalah Anda.sumber
if( someone.hasApprovalPermission( application ) ) { application.approve(); }
prinsip Separation of Concerns menunjukkan bahwa baik aplikasi, maupun seseorang, tidak perlu dipikirkan untuk membuat keputusan terkait izin dan keamanan.Aku menganggukkan kepalaku pada bit yang berbeda dari berbagai jawaban tetapi OP tampaknya masih memiliki perhatian terhadap kontrol aliran. Ada terlalu banyak untuk mencoba menyatu dalam kata-kata. Saya hanya akan memperbaiki beberapa kode - Pola Negara.
Nama Negara sebagai Past Tense
"In_Review" mungkin bukan keadaan, melainkan transisi, atau proses. Kalau tidak, nama negara Anda harus konsisten: "Menerapkan", "Menyetujui", "Menolak", dll. ATAU memiliki "Ditinjau" juga. Atau tidak.
Negara Terapan melakukan transisi ulasan dan menetapkan negara untuk Ditinjau. Kondisi Ulasan melakukan transisi persetujuan dan menetapkan status ke Disetujui (atau Ditolak).
Edit - Penanganan Kesalahan Komentar
Komentar terbaru:
Dan dari pertanyaan awal:
Ada banyak pekerjaan desain yang harus dilakukan. Tidak ada
Unified Field Theory Pattern
. Kebingungan datang dari asumsi kerangka transisi keadaan akan melakukan fungsi aplikasi umum dan penanganan kesalahan. Itu terasa salah karena itu. Jawaban yang ditampilkan dirancang untuk mengendalikan perubahan kondisi.Ini menunjukkan ada tiga fungsi yang bekerja di sini: Negara, Memperbarui, dan interaksi keduanya. Dalam hal
Application
ini bukan kode yang saya tulis. Mungkin menggunakannya untuk menentukan kondisi saat ini.Application
bukanapplicationPaperwork
keduanya.Application
bukan interaksi keduanya, tetapi bisa menjadiStateContextEvaluator
kelas umum . SekarangApplication
akan mengatur interaksi komponen ini dan kemudian bertindak sesuai, seperti memancarkan pesan kesalahan.Akhiri Edit
sumber
Application
konstruktor di mana pengecualian dilemparkan. Mungkin meneleponAppliedState.Approve()
mungkin menghasilkan pesan pengguna "Aplikasi harus ditinjau sebelum dapat disetujui."AppliedState.apply()
akan dengan lembut mengingatkan pengguna bahwa aplikasi telah dikirimkan dan sedang menunggu peninjauan. Dan program terus berjalan.Secara umum, apa yang Anda gambarkan adalah alur kerja. Lebih khusus lagi, fungsi-fungsi bisnis yang diwujudkan oleh negara-negara seperti DIREVIEWKAN DISETUJUI atau DITANGGUHKAN berada di bawah judul "aturan bisnis" atau "logika bisnis."
Tetapi untuk menjadi jelas, aturan bisnis tidak boleh dikodekan ke dalam pengecualian. Untuk melakukannya adalah dengan menggunakan pengecualian untuk kontrol aliran program, dan ada banyak alasan bagus mengapa Anda tidak harus melakukannya. Pengecualian harus digunakan untuk kondisi luar biasa, dan status INVALID suatu aplikasi sepenuhnya tidak eksklusif dari sudut pandang bisnis.
Gunakan pengecualian dalam kasus di mana program tidak dapat pulih dari kondisi kesalahan tanpa campur tangan pengguna ("file tidak ditemukan," misalnya).
Tidak ada pola khusus untuk menulis logika bisnis, selain teknik biasa untuk mengatur sistem pemrosesan data bisnis dan kode penulisan untuk mengimplementasikan proses Anda. Jika aturan bisnis dan alur kerjanya rumit, pertimbangkan untuk menggunakan semacam server alur kerja atau mesin aturan bisnis.
Dalam kasus apa pun, status REVIEW, DISETUJUI, DITANGGUNG, dll. Dapat diwakili oleh variabel pribadi tipe enum di kelas Anda. Jika Anda menggunakan metode pengambil / penyetel, Anda dapat mengontrol apakah setter akan mengizinkan perubahan dengan terlebih dahulu memeriksa nilai variabel enum. Jika seseorang mencoba menulis ke setter ketika nilai enum dalam kondisi yang salah, maka Anda dapat melempar pengecualian.
sumber
Application
bisa berupa antarmuka, dan Anda bisa memiliki implementasi untuk masing-masing negara. Antarmuka dapat memilikimoveToNextState()
metode, dan ini akan menyembunyikan semua logika alur kerja.Untuk kebutuhan klien, mungkin ada juga metode yang mengembalikan secara langsung apa yang dapat Anda lakukan dan tidak (yaitu seperangkat booleans), bukan hanya keadaan, sehingga Anda tidak memerlukan "daftar periksa" di klien (saya berasumsi klien menjadi pengontrol MVC atau UI).
Namun, alih-alih melempar pengecualian, Anda bisa melakukan apa saja dan mencatatnya. Ini aman saat runtime, aturan diberlakukan dan klien memiliki cara untuk menyembunyikan kontrol "pembaruan".
sumber
Salah satu pendekatan untuk masalah ini yang telah sangat sukses di alam liar adalah hypermedia - representasi keadaan entitas disertai dengan kontrol hypermedia yang menggambarkan jenis transisi yang saat ini diperbolehkan. Konsumen menanyakan kontrol untuk menemukan apa yang bisa dilakukan.
Ini adalah mesin negara, dengan kueri di antarmuka yang memungkinkan Anda menemukan peristiwa apa yang diizinkan untuk diaktifkan.
Dengan kata lain: kami sedang menggambarkan web (REST).
Pendekatan lain adalah mengambil gagasan Anda tentang antarmuka yang berbeda untuk negara yang berbeda, dan memberikan kueri yang memungkinkan Anda mendeteksi antarmuka mana yang saat ini tersedia. Pikirkan IUnknown :: QueryInterface, atau down casting. Kode klien berperan sebagai Mother May I dengan status untuk mengetahui apa yang diizinkan.
Ini pada dasarnya pola yang sama - hanya menggunakan antarmuka untuk mewakili kontrol hypermedia.
sumber
Berikut adalah contoh bagaimana Anda dapat mendekati ini dari perspektif fungsional, dan bagaimana hal itu membantu menghindari potensi jebakan. Saya bekerja di Haskell, yang saya anggap Anda tidak tahu, jadi saya akan menjelaskannya secara rinci saat saya melanjutkan.
Ini mendefinisikan tipe data yang bisa di salah satu dari empat negara yang sesuai dengan negara aplikasi Anda.
ApplicationDetails
diasumsikan sebagai tipe yang ada yang berisi informasi terperinci.Jenis alias yang perlu konversi eksplisit ke dan dari
Application
. Ini berarti bahwa jika kita mendefinisikan fungsi berikut yang menerima dan membukaUpdatableApplication
dan melakukan sesuatu yang berguna dengannya,maka kita harus secara eksplisit mengonversi Aplikasi ke Aplikasi yang Dapat Diperbarui sebelum kita dapat menggunakannya. Ini dilakukan dengan menggunakan fungsi ini:
Di sini kami melakukan tiga hal menarik:
UpdatableApplication
(yang hanya melibatkan catatan jenis kompilasi dari perubahan jenis yang ditambahkan, karena Haskell memiliki fitur khusus untuk melakukan tipuan tingkat-jenis seperti ini, tidak memerlukan biaya saat runtime) , danOption
di C # atauOptional
di Jawa - itu adalah objek yang membungkus hasil yang bisa hilang).Sekarang, untuk benar-benar menyatukan ini, kita perlu memanggil fungsi ini dan, jika hasilnya berhasil, meneruskannya ke fungsi pembaruan ...
Karena
updateApplication
fungsi membutuhkan objek yang dibungkus, kita tidak bisa lupa untuk memeriksa prasyarat. Dan karena fungsi pemeriksaan prakondisi mengembalikan objek yang dibungkus di dalamMaybe
objek, kita tidak bisa lupa untuk memeriksa hasilnya dan merespons jika gagal.Sekarang ... Anda bisa melakukan ini dalam bahasa berorientasi objek. Tapi itu kurang nyaman:
Maybe
mereka biasanya tidak memiliki cara yang nyaman untuk mengekstraksi data dan memilih jalur yang akan diambil pada saat yang sama. Pencocokan pola juga sangat berguna di sini.sumber
Anda bisa menggunakan pola «perintah», dan kemudian meminta Invoker untuk memberikan daftar fungsi yang valid sesuai dengan keadaan kelas penerima.
Saya menggunakan hal yang sama untuk menyediakan fungsionalitas untuk antarmuka yang berbeda yang seharusnya memanggil kode saya, beberapa opsi tidak tersedia tergantung dari status catatan saat ini, jadi penyerbu saya memperbarui daftar dan dengan cara itu setiap GUI meminta Invoker opsi mana yang tersedia dan mereka mengecatnya sendiri.
sumber