Sesuatu yang muncul cukup banyak dalam pekerjaan saya saat ini adalah bahwa ada proses umum yang perlu terjadi, tetapi kemudian bagian aneh dari proses itu perlu terjadi sedikit berbeda tergantung pada nilai variabel tertentu, dan saya tidak cukup yakin apa cara paling elegan untuk menangani ini.
Saya akan menggunakan contoh yang biasanya kita miliki, yang melakukan hal-hal yang sedikit berbeda tergantung pada negara yang kita hadapi.
Jadi saya punya kelas, sebut saja Processor
:
public class Processor
{
public string Process(string country, string text)
{
text.Capitalise();
text.RemovePunctuation();
text.Replace("é", "e");
var split = text.Split(",");
string.Join("|", split);
}
}
Kecuali bahwa hanya beberapa tindakan yang perlu terjadi untuk negara-negara tertentu. Misalnya, hanya 6 negara yang memerlukan langkah kapitalisasi. Karakter untuk dibagi mungkin berubah tergantung pada negara. Mengganti aksen 'e'
mungkin hanya diperlukan tergantung pada negara.
Jelas Anda bisa menyelesaikannya dengan melakukan sesuatu seperti ini:
public string Process(string country, string text)
{
if (country == "USA" || country == "GBR")
{
text.Capitalise();
}
if (country == "DEU")
{
text.RemovePunctuation();
}
if (country != "FRA")
{
text.Replace("é", "e");
}
var separator = DetermineSeparator(country);
var split = text.Split(separator);
string.Join("|", split);
}
Tetapi ketika Anda berurusan dengan semua negara yang mungkin ada di dunia, itu menjadi sangat rumit. Dan terlepas dari itu, if
pernyataan membuat logika lebih sulit untuk dibaca (setidaknya, jika Anda membayangkan metode yang lebih kompleks daripada contoh), dan kompleksitas siklomatik mulai merayap cukup cepat.
Jadi saat ini saya sedang melakukan sesuatu seperti ini:
public class Processor
{
CountrySpecificHandlerFactory handlerFactory;
public Processor(CountrySpecificHandlerFactory handlerFactory)
{
this.handlerFactory = handlerFactory;
}
public string Process(string country, string text)
{
var handlers = this.handlerFactory.CreateHandlers(country);
handlers.Capitalier.Capitalise(text);
handlers.PunctuationHandler.RemovePunctuation(text);
handlers.SpecialCharacterHandler.ReplaceSpecialCharacters(text);
var separator = handlers.SeparatorHandler.DetermineSeparator();
var split = text.Split(separator);
string.Join("|", split);
}
}
Penangan:
public class CountrySpecificHandlerFactory
{
private static IDictionary<string, ICapitaliser> capitaliserDictionary
= new Dictionary<string, ICapitaliser>
{
{ "USA", new Capitaliser() },
{ "GBR", new Capitaliser() },
{ "FRA", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
{ "DEU", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
};
// Imagine the other dictionaries like this...
public CreateHandlers(string country)
{
return new CountrySpecificHandlers
{
Capitaliser = capitaliserDictionary[country],
PunctuationHanlder = punctuationDictionary[country],
// etc...
};
}
}
public class CountrySpecificHandlers
{
public ICapitaliser Capitaliser { get; private set; }
public IPunctuationHanlder PunctuationHanlder { get; private set; }
public ISpecialCharacterHandler SpecialCharacterHandler { get; private set; }
public ISeparatorHandler SeparatorHandler { get; private set; }
}
Yang sama saya tidak yakin saya suka. Logikanya masih agak dikaburkan oleh semua pembuatan pabrik dan Anda tidak bisa begitu saja melihat metode asli dan melihat apa yang terjadi ketika proses "GBR" dijalankan, misalnya. Anda juga akhirnya menciptakan banyak kelas (dalam contoh yang lebih kompleks dari ini) dalam gaya GbrPunctuationHandler
,, UsaPunctuationHandler
dll ... yang berarti Anda harus melihat beberapa kelas yang berbeda untuk mencari tahu semua tindakan yang mungkin bisa terjadi selama tanda baca penanganan. Jelas saya tidak ingin satu kelas raksasa dengan satu miliar if
pernyataan, tetapi sama-sama 20 kelas dengan logika yang sedikit berbeda juga terasa kikuk.
Pada dasarnya saya pikir saya telah mendapatkan semacam simpul OOP dan tidak tahu cara yang baik untuk menguraikannya. Saya bertanya-tanya apakah ada pola di luar sana yang akan membantu dengan jenis proses ini?
PreProcess
fungsi, yang dapat diimplementasikan secara berbeda berdasarkan pada beberapa negara,DetermineSeparator
dapat ada untuk mereka semua, dan aPostProcess
. Semuanya dapatprotected virtual void
dengan implementasi default, dan kemudian Anda dapat memiliki spesifikProcessors
per negaraif (country == "DEU")
Anda periksaif (config.ShouldRemovePunctuation)
.country
sebuah string yang bukan sebuah instance dari kelas yang model orang-orang pilihan?Jawaban:
Saya akan menyarankan merangkum semua opsi dalam satu kelas:
dan meneruskannya ke
Process
metode:sumber
CountrySpecificHandlerFactory
... o_0public class ProcessOptions
seharusnya benar-benar[Flags] enum class ProcessOptions : int { ... }
...ProcessOptions
. Sangat mudah.Ketika .NET framework ditetapkan untuk menangani masalah-masalah semacam ini, itu tidak memodelkan semuanya sebagai
string
. Jadi Anda memiliki, misalnya,CultureInfo
kelas :Sekarang, kelas ini mungkin tidak mengandung fitur spesifik yang Anda butuhkan, tetapi Anda jelas dapat membuat sesuatu yang analog. Dan kemudian Anda mengubah
Process
metode Anda :CountryInfo
Kelas Anda kemudian dapat memiliki abool RequiresCapitalization
properti, dll, yang membantuProcess
metode Anda mengarahkan pemrosesan dengan tepat.sumber
Mungkin Anda bisa memiliki satu
Processor
per negara?Dan satu kelas dasar untuk menangani bagian umum dari pemrosesan:
Selain itu, Anda harus mengolah kembali tipe pengembalian Anda karena tidak akan dikompilasi saat Anda menulisnya - terkadang
string
metode tidak mengembalikan apa pun.sumber
Anda dapat membuat antarmuka umum dengan
Process
metode ...Maka Anda menerapkannya untuk setiap negara ...
Anda kemudian dapat membuat metode umum untuk membuat instance dan mengeksekusi masing-masing kelas terkait negara ...
Maka Anda hanya perlu membuat dan menggunakan prosesor seperti ...
Berikut ini contoh biola dotnet yang berfungsi ...
Anda menempatkan semua pemrosesan khusus negara di setiap kelas negara. Buat kelas umum (dalam kelas Pemrosesan) untuk semua metode individu yang sebenarnya, sehingga setiap prosesor negara menjadi daftar panggilan umum lainnya, daripada menyalin kode di setiap kelas negara.
Catatan: Anda harus menambahkan ...
agar metode statis untuk membuat turunan dari kelas negara.
sumber
Process
, dan alih-alih menggunakannya sekali untuk mendapatkan IProcessor yang benar? Anda biasanya memproses banyak teks sesuai dengan aturan negara yang sama.Process("GBR", "text");
mengeksekusi metode statis yang membuat instance dari prosesor GBR dan mengeksekusi metode Proses itu. Itu hanya mengeksekusi pada satu contoh, untuk jenis negara tertentu.Beberapa versi yang lalu, C # swtich diberi dukungan penuh untuk pencocokan pola . Sehingga kasus "banyak negara cocok" mudah dilakukan. Meskipun masih tidak memiliki kemampuan jatuh, satu input dapat mencocokkan banyak kasus dengan pencocokan pola. Mungkin bisa membuat itu jika-spam sedikit lebih jelas.
Npw, sebuah sakelar biasanya dapat diganti dengan Koleksi. Anda harus menggunakan Delegasi dan Kamus. Proses dapat diganti dengan.
Maka Anda bisa membuat Kamus:
Saya menggunakan functionNames untuk menyerahkan Delegasi. Tapi Anda bisa menggunakan sintaks Lambda untuk menyediakan seluruh kode di sana. Dengan begitu, Anda bisa menyembunyikan seluruh Koleksi itu seperti halnya koleksi besar lainnya. Dan kode menjadi pencarian sederhana:
Itu adalah dua pilihan. Anda mungkin ingin mempertimbangkan untuk menggunakan Enumerasi alih-alih string untuk pencocokan, tetapi itu adalah detail kecil.
sumber
Saya mungkin (tergantung pada detail kasus penggunaan Anda) pergi dengan
Country
menjadi objek "nyata", bukan string. Kata kuncinya adalah "polimorfisme".Jadi pada dasarnya akan terlihat seperti ini:
Kemudian Anda dapat membuat negara khusus untuk yang Anda butuhkan. Catatan: Anda tidak harus membuat
Country
objek untuk semua negara, Anda bisaLatinlikeCountry
, atau bahkanGenericCountry
. Di sana Anda dapat mengumpulkan apa yang harus dilakukan, bahkan menggunakan kembali yang lain, seperti:Atau serupa.
Country
mungkin sebenarnyaLanguage
, saya tidak yakin tentang kasus penggunaan, tapi saya mengerti maksudnya.Juga, metode tentu saja tidak harus
Process()
itu harus menjadi hal yang benar-benar perlu Anda lakukan. SukaWords()
atau apalah.sumber
Anda ingin mendelegasikan (mengangguk ke rantai tanggung jawab) sesuatu yang tahu tentang budaya sendiri. Jadi gunakan atau buat tipe Negara atau CultureInfo, seperti yang disebutkan di atas dalam jawaban lain.
Tetapi secara umum dan mendasar masalah Anda adalah Anda mengambil konstruksi prosedural seperti 'prosesor' dan menerapkannya pada OO. OO adalah tentang mewakili konsep dunia nyata dari bisnis atau domain masalah dalam perangkat lunak. Prosesor tidak menerjemahkan apa pun di dunia nyata selain dari perangkat lunak itu sendiri. Setiap kali Anda memiliki kelas seperti Prosesor atau Manajer atau Gubernur, bel alarm akan berbunyi.
sumber
Rantai tanggung jawab adalah jenis hal yang mungkin Anda cari tetapi dalam OOP agak rumit ...
Bagaimana dengan pendekatan yang lebih fungsional dengan C #?
CATATAN: Tentu saja tidak harus statis. Jika kelas Proses memerlukan status Anda dapat menggunakan kelas instances atau fungsi yang diterapkan sebagian;).
Anda dapat membangun Proses untuk setiap negara pada permulaan, menyimpan masing-masing dalam koleksi yang diindeks dan mengambilnya saat dibutuhkan dengan biaya O (1).
sumber
Saya hanya akan menerapkan rutinitas
Capitalise
,RemovePunctuation
dll. Sebagai subproses yang dapat mengirim pesan dengan atext
dancountry
parameter, dan akan mengembalikan teks yang diproses.Gunakan kamus untuk mengelompokkan negara yang sesuai dengan atribut tertentu (jika Anda lebih suka daftar, itu akan bekerja dengan baik hanya dengan sedikit biaya kinerja). Misalnya:
CapitalisationApplicableCountries
danPunctuationRemovalApplicableCountries
.sumber
Saya merasa bahwa informasi tentang negara harus disimpan dalam data, bukan dalam kode. Jadi, alih-alih kelas CountryInfo atau CapitalisationApplicableCountries dictionary, Anda bisa memiliki database dengan catatan untuk setiap negara dan bidang untuk setiap langkah pemrosesan, dan kemudian pemrosesan bisa melalui bidang untuk negara tertentu dan memproses sesuai. Pemeliharaan kemudian terutama dalam database, dengan kode baru hanya diperlukan ketika langkah-langkah baru diperlukan, dan data dapat dibaca manusia dalam database. Ini mengasumsikan langkah-langkahnya independen dan tidak saling mengganggu; jika tidak demikian maka segala sesuatunya rumit.
sumber