Pola Desain Strategi di Jawa 8

1. Perkenalan

Pada artikel ini, kita akan melihat bagaimana kita dapat mengimplementasikan pola desain strategi di Java 8.

Pertama, kami akan memberikan gambaran umum tentang pola tersebut, dan menjelaskan bagaimana pola tersebut diterapkan secara tradisional di versi Java yang lebih lama.

Selanjutnya, kita akan mencoba polanya lagi, kali ini dengan Java 8 lambda, mengurangi verbositas kode kita.

2. Pola Strategi

Pada dasarnya, pola strategi memungkinkan kita untuk mengubah perilaku algoritma pada saat runtime.

Biasanya, kita akan mulai dengan antarmuka yang digunakan untuk menerapkan algoritme, lalu mengimplementasikannya beberapa kali untuk setiap algoritme yang memungkinkan.

Katakanlah kita memiliki persyaratan untuk menerapkan berbagai jenis diskon untuk pembelian, berdasarkan apakah itu Natal, Paskah atau Tahun Baru. Pertama, mari kita membuat Penekan antarmuka yang akan dilaksanakan oleh masing-masing strategi kami:

public interface Discounter { BigDecimal applyDiscount(BigDecimal amount); } 

Kemudian katakanlah kita ingin menerapkan diskon 50% saat Paskah dan diskon 10% saat Natal. Mari terapkan antarmuka kita untuk masing-masing strategi ini:

public static class EasterDiscounter implements Discounter { @Override public BigDecimal applyDiscount(final BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.5)); } } public static class ChristmasDiscounter implements Discounter { @Override public BigDecimal applyDiscount(final BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.9)); } } 

Terakhir, mari kita coba strategi dalam pengujian:

Discounter easterDiscounter = new EasterDiscounter(); BigDecimal discountedValue = easterDiscounter .applyDiscount(BigDecimal.valueOf(100)); assertThat(discountedValue) .isEqualByComparingTo(BigDecimal.valueOf(50));

Ini bekerja dengan cukup baik, tetapi masalahnya adalah mungkin agak merepotkan untuk harus membuat kelas konkret untuk setiap strategi. Alternatifnya adalah menggunakan tipe dalam anonim, tetapi itu masih cukup bertele-tele dan tidak lebih praktis daripada solusi sebelumnya:

Discounter easterDiscounter = new Discounter() { @Override public BigDecimal applyDiscount(final BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.5)); } }; 

3. Memanfaatkan Java 8

Sejak Java 8 dirilis, pengenalan lambda telah membuat tipe dalam anonim menjadi lebih atau kurang mubazir. Itu berarti membuat strategi yang sejalan sekarang jauh lebih bersih dan mudah.

Selain itu, gaya pemrograman fungsional deklaratif memungkinkan kita mengimplementasikan pola yang sebelumnya tidak mungkin dilakukan.

3.1. Mengurangi Kode Verbositas

Mari kita coba membuat EasterDiscounter sebaris , hanya kali ini menggunakan ekspresi lambda:

Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5)); 

Seperti yang bisa kita lihat, kode kita sekarang jauh lebih bersih dan lebih mudah dipelihara, mencapai yang sama seperti sebelumnya tetapi dalam satu baris. Pada dasarnya, lambda dapat dilihat sebagai pengganti tipe dalam anonim .

Keuntungan ini menjadi lebih jelas ketika kami ingin mengumumkan lebih banyak Pendiskon sesuai:

List discounters = newArrayList( amount -> amount.multiply(BigDecimal.valueOf(0.9)), amount -> amount.multiply(BigDecimal.valueOf(0.8)), amount -> amount.multiply(BigDecimal.valueOf(0.5)) );

Saat kita ingin mendefinisikan banyak Pendiskon, kita dapat mendeklarasikannya secara statis semua di satu tempat. Java 8 bahkan memungkinkan kita mendefinisikan metode statis dalam antarmuka jika kita mau.

Jadi, alih-alih memilih antara kelas konkret atau tipe dalam anonim, mari coba buat lambda semua dalam satu kelas:

public interface Discounter { BigDecimal applyDiscount(BigDecimal amount); static Discounter christmasDiscounter() { return amount -> amount.multiply(BigDecimal.valueOf(0.9)); } static Discounter newYearDiscounter() { return amount -> amount.multiply(BigDecimal.valueOf(0.8)); } static Discounter easterDiscounter() { return amount -> amount.multiply(BigDecimal.valueOf(0.5)); } } 

Seperti yang bisa kita lihat, kita mencapai banyak hal dalam kode yang tidak terlalu banyak.

3.2. Memanfaatkan Komposisi Fungsi

Mari kita memodifikasi kami Penekan antarmuka sehingga memperluas UnaryOperator antarmuka, dan kemudian menambahkan menggabungkan () metode:

public interface Discounter extends UnaryOperator { default Discounter combine(Discounter after) { return value -> after.apply(this.apply(value)); } }

Pada dasarnya, kita refactoring kami Penekan dan memanfaatkan fakta bahwa menerapkan diskon adalah fungsi yang mengubah BigDecimal contoh ke lain BigDecimal misalnya , memungkinkan kita untuk akses telah ditetapkan metode . Karena UnaryOperator dilengkapi dengan metode apply () , kita cukup mengganti applyDiscount dengannya.

Metode gabung () hanyalah abstraksi di sekitar menerapkan satu Pendiskon ke hasil ini. Ini menggunakan aplikasi fungsional bawaan () untuk mencapai ini.

Sekarang, Mari coba menerapkan beberapa Pendiskon secara kumulatif ke suatu jumlah. Kami akan melakukan ini dengan menggunakan fungsi reduce () dan kombinasikan () kami:

Discounter combinedDiscounter = discounters .stream() .reduce(v -> v, Discounter::combine); combinedDiscounter.apply(...);

Beri perhatian khusus pada argumen pengurangan pertama . Jika tidak ada diskon yang diberikan, kami perlu mengembalikan nilai yang tidak berubah. Ini dapat dicapai dengan memberikan fungsi identitas sebagai pendiskon default.

Ini adalah alternatif yang berguna dan tidak terlalu bertele-tele untuk melakukan iterasi standar. Jika kami mempertimbangkan metode yang kami keluarkan dari kotak untuk komposisi fungsional, ini juga memberi kami lebih banyak fungsi secara gratis.

4. Kesimpulan

Di artikel ini, kami telah menjelaskan pola strategi, dan juga mendemonstrasikan bagaimana kami dapat menggunakan ekspresi lambda untuk mengimplementasikannya dengan cara yang tidak terlalu bertele-tele.

Penerapan contoh-contoh ini dapat ditemukan di GitHub. Ini adalah proyek berbasis Maven, jadi harus mudah dijalankan apa adanya.