Saat mendesain kode, Anda selalu memiliki dua opsi.
- selesaikan saja, dalam hal ini hampir semua solusi akan bekerja untuk Anda
- menjadi bertele-tele dan merancang solusi yang mengeksploitasi kebiasaan bahasa dan ideologinya (bahasa OO dalam hal ini - penggunaan polimorfisme sebagai sarana untuk memberikan keputusan)
Saya tidak akan fokus pada yang pertama dari keduanya, karena benar-benar tidak ada yang bisa dikatakan. Jika Anda hanya ingin membuatnya berfungsi, Anda bisa membiarkan kode apa adanya.
Tetapi apa yang akan terjadi, jika Anda memilih untuk melakukannya dengan cara yang sangat bagus dan benar-benar menyelesaikan masalah dengan pola desain, seperti yang Anda inginkan?
Anda bisa melihat proses berikut:
Saat merancang kode OO, sebagian besar if
yang ada dalam kode tidak harus ada di sana. Secara alami, jika Anda ingin membandingkan dua tipe skalar, seperti int
s atau float
s, Anda cenderung memiliki if
, tetapi jika Anda ingin mengubah prosedur berdasarkan konfigurasi, Anda dapat menggunakan polimorfisme untuk mencapai apa yang Anda inginkan, pindahkan keputusan ( if
s) dari logika bisnis Anda ke suatu tempat, di mana objek dipakai - ke pabrik .
Sampai sekarang, proses Anda dapat melewati 4 jalur terpisah:
data
tidak dienkripsi atau dikompresi (tidak memanggil apa pun, kembali data
)
data
dikompresi (panggil compress(data)
dan kembalikan)
data
dienkripsi (panggil encrypt(data)
dan kembalikan)
data
dikompresi dan dienkripsi (panggilan encrypt(compress(data))
dan kembalikan)
Hanya dengan melihat 4 jalur, Anda menemukan masalah.
Anda memiliki satu proses yang memanggil 3 (secara teoritis 4, jika Anda menghitung tidak memanggil apa pun sebagai satu) metode berbeda yang memanipulasi data dan kemudian mengembalikannya. Metode memiliki nama yang berbeda , berbeda disebut API publik (cara di mana metode mengkomunikasikan perilaku mereka).
Dengan menggunakan pola adaptor , kita dapat memecahkan masalah colision nama (kita dapat menyatukan API publik) yang telah terjadi. Sederhananya, adaptor membantu dua antarmuka yang tidak kompatibel bekerja bersama. Juga, adaptor bekerja dengan mendefinisikan antarmuka adaptor baru, yang mana kelas mencoba menyatukan implementasi API mereka.
Ini bukan bahasa konkret. Ini adalah pendekatan generik, kata kunci apa pun yang ada untuk mewakili mungkin dari jenis apa pun, dalam bahasa seperti C # Anda dapat menggantinya dengan generik ( <T>
).
Saya akan berasumsi, bahwa saat ini Anda dapat memiliki dua kelas yang bertanggung jawab untuk kompresi dan enkripsi.
class Compression
{
Compress(data : any) : any { ... }
}
class Encryption
{
Encrypt(data : any) : any { ... }
}
Dalam dunia perusahaan, bahkan kelas-kelas khusus ini sangat mungkin akan diganti oleh antarmuka, seperti class
kata kunci yang akan diganti interface
(jika Anda berurusan dengan bahasa seperti C #, Java dan / atau PHP) atau class
kata kunci akan tetap ada, tetapi Compress
dan Encrypt
metode akan didefinisikan sebagai virtual murni , jika Anda kode dalam C ++.
Untuk membuat adaptor, kami mendefinisikan antarmuka umum.
interface DataProcessing
{
Process(data : any) : any;
}
Kemudian kita harus menyediakan implementasi antarmuka agar bermanfaat.
// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
public Process(data : any) : any
{
return data;
}
}
// when only compression is enabled
class CompressionAdapter : DataProcessing
{
private compression : Compression;
public Process(data : any) : any
{
return this.compression.Compress(data);
}
}
// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(data);
}
}
// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
private compression : Compression;
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(
this.compression.Compress(data)
);
}
}
Dengan melakukan ini, Anda berakhir dengan 4 kelas, masing-masing melakukan sesuatu yang sama sekali berbeda, tetapi masing-masing dari mereka menyediakan API publik yang sama. The Process
Metode.
Dalam logika bisnis Anda, di mana Anda berurusan dengan keputusan tidak ada / enkripsi / kompresi / keduanya, Anda akan merancang objek Anda untuk membuatnya tergantung pada DataProcessing
antarmuka yang kami desain sebelumnya.
class DataService
{
private dataProcessing : DataProcessing;
public DataService(dataProcessing : DataProcessing)
{
this.dataProcessing = dataProcessing;
}
}
Prosesnya sendiri bisa sesederhana ini:
public ComplicatedProcess(data : any) : any
{
data = this.dataProcessing.Process(data);
// ... perhaps work with the data
return data;
}
Tidak ada lagi persyaratan. Kelas DataService
tidak tahu apa yang akan dilakukan dengan data ketika diberikan kepada dataProcessing
anggota, dan tidak terlalu peduli tentang itu, itu bukan tanggung jawabnya.
Idealnya, Anda akan memiliki unit test yang menguji 4 kelas adaptor yang Anda buat untuk memastikan mereka bekerja, Anda membuat test pass Anda. Dan jika mereka lulus, Anda bisa yakin mereka akan bekerja di mana pun Anda memanggilnya dalam kode Anda.
Jadi melakukannya dengan cara ini saya tidak akan pernah memiliki if
kode lagi?
Tidak. Anda cenderung memiliki persyaratan dalam logika bisnis Anda, tetapi mereka masih harus berada di suatu tempat. Tempat itu adalah pabrikmu.
Dan ini bagus. Anda memisahkan masalah penciptaan dan benar-benar menggunakan kode. Jika Anda membuat pabrik Anda dapat diandalkan (di Jawa Anda bahkan bisa menggunakan sesuatu seperti kerangka kerja Guice oleh Google), dalam logika bisnis Anda, Anda tidak khawatir memilih kelas yang tepat untuk disuntikkan. Karena Anda tahu pabrik Anda berfungsi dan akan memberikan apa yang diminta.
Apakah perlu untuk memiliki semua kelas, antarmuka, dll?
Ini membawa kita kembali ke awal.
Dalam OOP, jika Anda memilih jalur untuk menggunakan polimorfisme, benar-benar ingin menggunakan pola desain, ingin mengeksploitasi fitur-fitur bahasa dan / atau ingin mengikuti semuanya adalah ideologi objek, maka itu. Dan bahkan kemudian, contoh ini bahkan tidak menunjukkan semua pabrik yang akan Anda butuhkan dan jika Anda ingin refactor Compression
dan Encryption
kelas dan membuat mereka antarmuka, Anda harus memasukkan implementasinya juga.
Pada akhirnya Anda berakhir dengan ratusan kelas kecil dan antarmuka, fokus pada hal-hal yang sangat spesifik. Yang tidak selalu buruk, tetapi mungkin bukan solusi terbaik untuk Anda jika semua yang Anda inginkan adalah melakukan sesuatu yang sederhana seperti menambahkan dua angka.
Jika Anda ingin menyelesaikannya dengan cepat, Anda dapat mengambil solusi Ixrec , yang setidaknya berhasil menghilangkan else if
dan else
memblokir, yang, menurut pendapat saya, bahkan sedikit lebih buruk daripada dataran if
.
Mempertimbangkan ini adalah cara saya membuat desain OO yang bagus. Pengodean ke antarmuka daripada implementasi, ini adalah bagaimana saya telah melakukannya selama beberapa tahun terakhir dan ini adalah pendekatan yang paling nyaman bagi saya.
Saya pribadi lebih suka pemrograman jika-kurang dan akan lebih menghargai solusi yang lebih lama dari 5 baris kode. Ini adalah cara saya terbiasa mendesain kode dan saya sangat nyaman membacanya.
Pembaruan 2: Telah ada diskusi liar tentang versi pertama dari solusi saya. Diskusi sebagian besar disebabkan oleh saya, yang saya minta maaf.
Saya memutuskan untuk mengedit jawaban dengan cara yang merupakan salah satu cara untuk melihat solusi tetapi bukan satu-satunya. Saya juga menghapus bagian dekorator, di mana yang saya maksud adalah fasad, yang akhirnya saya putuskan untuk ditinggalkan sepenuhnya, karena adaptor adalah variasi fasad.
if
pernyataan baru ?