Singkatnya, jangan merekayasa perangkat lunak Anda untuk dapat digunakan kembali karena tidak ada pengguna akhir yang peduli jika fungsi Anda dapat digunakan kembali. Sebaliknya, insinyur untuk kelengkapan desain - apakah kode saya mudah bagi orang lain atau masa depan saya yang pelupa untuk mengerti? - dan fleksibilitas desain- ketika saya mau tidak mau harus memperbaiki bug, menambah fitur, atau memodifikasi fungsionalitas, seberapa banyak kode saya akan menolak perubahan? Satu-satunya hal yang diperhatikan pelanggan Anda adalah seberapa cepat Anda dapat merespons ketika dia melaporkan bug atau meminta perubahan. Mengajukan pertanyaan ini tentang desain Anda secara tidak sengaja cenderung menghasilkan kode yang dapat digunakan kembali, tetapi pendekatan ini membuat Anda tetap fokus pada menghindari masalah nyata yang akan Anda hadapi sepanjang umur kode tersebut sehingga Anda dapat melayani pengguna akhir dengan lebih baik daripada mengejar yang tinggi, tidak praktis. "rekayasa" cita-cita untuk menyenangkan jenggot leher.
Untuk sesuatu yang sederhana seperti contoh yang Anda berikan, implementasi awal Anda baik-baik saja karena betapa kecilnya, tetapi desain langsung ini akan menjadi sulit untuk dipahami dan rapuh jika Anda mencoba memasukkan terlalu banyak fleksibilitas fungsional (sebagai lawan dari fleksibilitas desain) ke dalam satu prosedur. Di bawah ini adalah penjelasan saya tentang pendekatan yang saya sukai untuk merancang sistem yang kompleks untuk kelengkapan dan fleksibilitas yang saya harap akan menunjukkan apa yang saya maksud dengan mereka. Saya tidak akan menggunakan strategi ini untuk sesuatu yang dapat ditulis dalam kurang dari 20 baris dalam satu prosedur karena sesuatu yang sangat kecil sudah memenuhi kriteria saya untuk kelengkapan dan fleksibilitas.
Objek, bukan Prosedur
Daripada menggunakan kelas-kelas seperti modul old-school dengan banyak rutinitas yang Anda panggil untuk melakukan hal-hal yang seharusnya dilakukan perangkat lunak Anda, pertimbangkan untuk memodelkan domain sebagai objek yang berinteraksi dan bekerja sama untuk menyelesaikan tugas yang ada. Metode-metode dalam paradigma Berorientasi Objek pada awalnya diciptakan untuk menjadi sinyal di antara objek-objek sehingga Object1
dapat Object2
menentukan untuk melakukan hal tersebut, apa pun itu, dan mungkin menerima sinyal balik. Ini karena paradigma Berorientasi Objek secara inheren tentang pemodelan objek domain Anda dan interaksinya daripada cara mewah untuk mengatur fungsi dan prosedur lama yang sama dari paradigma Imperatif. Dalam halvoid destroyBaghdad
misalnya, alih-alih mencoba menulis metode yang kurang umum konteks untuk menangani penghancuran Baghdad atau hal lain (yang dapat dengan cepat tumbuh kompleks, sulit dipahami, dan rapuh), setiap hal yang dapat dihancurkan harus bertanggung jawab untuk memahami bagaimana untuk menghancurkan dirinya sendiri. Misalnya, Anda memiliki antarmuka yang menggambarkan perilaku hal-hal yang dapat dihancurkan:
interface Destroyable {
void destroy();
}
Maka Anda memiliki kota yang mengimplementasikan antarmuka ini:
class City implements Destroyable {
@Override
public void destroy() {
...code that destroys the city
}
}
Tidak ada yang menyerukan penghancuran instance City
akan pernah peduli bagaimana itu terjadi, jadi tidak ada alasan untuk kode itu ada di mana pun di luar City::destroy
, dan memang, pengetahuan intim tentang cara kerja bagian dalam City
dari dirinya sendiri akan menjadi kopling ketat yang mengurangi karena Anda harus mempertimbangkan elemen-elemen luar itu jika Anda perlu memodifikasi perilaku City
. Ini adalah tujuan sebenarnya di balik enkapsulasi. Anggap saja seperti setiap objek memiliki API sendiri yang memungkinkan Anda untuk melakukan apa pun yang Anda butuhkan sehingga Anda dapat membiarkannya khawatir tentang memenuhi permintaan Anda.
Delegasi, bukan "Kontrol"
Sekarang, apakah kelas implementasi Anda City
atau Baghdad
tergantung pada seberapa umum proses menghancurkan kota itu ternyata. Dalam semua kemungkinan, a City
akan terdiri dari potongan-potongan kecil yang perlu dihancurkan secara individual untuk mencapai kehancuran total kota, jadi dalam hal itu, masing-masing potongan juga akan diterapkan Destroyable
, dan mereka masing-masing akan diperintahkan oleh City
untuk menghancurkan sendiri dengan cara yang sama seseorang dari luar meminta City
untuk menghancurkan dirinya sendiri.
interface Part extends Destroyable {
...part-specific methods
}
class Building implements Part {
...part-specific methods
@Override
public void destroy() {
...code to destroy a building
}
}
class Street implements Part {
...part-specific methods
@Override
public void destroy() {
...code to destroy a building
}
}
class City implements Destroyable {
public List<Part> parts() {...}
@Override
public void destroy() {
parts().forEach(Destroyable::destroy);
}
}
Jika Anda ingin menjadi benar-benar gila dan menerapkan gagasan Bomb
yang dijatuhkan di lokasi dan menghancurkan segala sesuatu dalam radius tertentu, itu mungkin terlihat seperti ini:
class Bomb {
private final Integer radius;
public Bomb(final Integer radius) {
this.radius = radius;
}
public void drop(final Grid grid, final Coordinate target) {
new ObjectsByRadius(
grid,
target,
this.radius
).forEach(Destroyable::destroy);
}
}
ObjectsByRadius
mewakili seperangkat objek yang dihitung untuk Bomb
dari input karena Bomb
tidak peduli bagaimana perhitungan itu dibuat selama ia dapat bekerja dengan objek. Ini dapat digunakan kembali secara tidak disengaja, tetapi tujuan utamanya adalah untuk mengisolasi perhitungan dari proses menjatuhkan Bomb
dan menghancurkan objek sehingga Anda dapat memahami setiap bagian dan bagaimana mereka cocok bersama-sama dan mengubah perilaku masing-masing bagian tanpa harus membentuk kembali seluruh algoritma .
Interaksi, bukan Algoritma
Alih-alih mencoba menebak pada jumlah parameter yang tepat untuk algoritma yang kompleks, lebih masuk akal untuk memodelkan proses sebagai satu set objek yang berinteraksi, masing-masing dengan peran yang sangat sempit, karena itu akan memberi Anda kemampuan untuk memodelkan kompleksitas kompleksitas Anda. proses melalui interaksi antara objek yang terdefinisi dengan baik, mudah dipahami, dan hampir tidak berubah ini. Ketika dilakukan dengan benar, ini membuat bahkan beberapa modifikasi paling kompleks sepele seperti mengimplementasikan satu atau dua antarmuka dan pengerjaan ulang objek mana yang dipakai dalam main()
metode Anda .
Saya akan memberi Anda sesuatu untuk contoh asli Anda, tetapi saya jujur tidak tahu apa artinya "mencetak ... Penghematan Day Light." Apa yang bisa saya katakan tentang kategori masalah itu adalah bahwa setiap kali Anda melakukan perhitungan, yang hasilnya dapat diformat dengan beberapa cara, cara saya yang lebih disukai untuk menguraikannya adalah seperti ini:
interface Result {
String print();
}
class Caclulation {
private final Parameter paramater1;
private final Parameter parameter2;
public Calculation(final Parameter parameter1, final Parameter parameter2) {
this.parameter1 = parameter1;
this.parameter2 = parameter2;
}
public Result calculate() {
...calculate the result
}
}
class FormattedResult {
private final Result result;
public FormattedResult(final Result result) {
this.result = result;
}
@Override
public String print() {
...interact with this.result to format it and return the formatted String
}
}
Karena contoh Anda menggunakan kelas dari perpustakaan Java yang tidak mendukung desain ini, Anda bisa menggunakan API ZonedDateTime
secara langsung. Idenya di sini adalah bahwa setiap perhitungan dirangkum dalam objeknya sendiri. Itu tidak membuat asumsi tentang berapa kali harus berjalan atau bagaimana harus memformat hasilnya. Ini secara khusus berkaitan dengan melakukan bentuk perhitungan yang paling sederhana. Ini membuatnya mudah dipahami dan fleksibel untuk diubah. Demikian juga, Result
secara eksklusif berkaitan dengan merangkum hasil perhitungan, dan FormattedResult
secara eksklusif berkaitan dengan berinteraksi dengan Result
memformatnya sesuai dengan aturan yang kami tetapkan. Lewat sini,kita dapat menemukan jumlah argumen yang sempurna untuk masing-masing metode kita karena masing-masing memiliki tugas yang jelas . Ini juga jauh lebih mudah untuk memodifikasi bergerak maju selama antarmuka tidak berubah (yang tidak mungkin dilakukan jika Anda meminimalkan tanggung jawab objek Anda dengan benar). main()
Metodekamimungkin terlihat seperti ini:
class App {
public static void main(String[] args) {
final List<Set<Paramater>> parameters = ...instantiated from args
parameters.forEach(set -> {
System.out.println(
new FormattedResult(
new Calculation(
set.get(0),
set.get(1)
).calculate()
).print()
);
});
}
}
Faktanya, Pemrograman Berorientasi Objek diciptakan khusus sebagai solusi untuk masalah kompleksitas / fleksibilitas paradigma Imperatif karena tidak ada jawaban yang baik (bahwa setiap orang dapat menyetujui atau tiba secara mandiri, bagaimanapun) untuk bagaimana mengoptimalkan secara optimal tentukan fungsi dan prosedur Imperatif dalam idiom.