1. Pengenalan
Dalam artikel ini, kita akan melihat bagaimana kita dapat menerapkan pola desain strategi di Java 8.
Pertama, kami akan memberikan gambaran keseluruhan mengenai corak, dan menerangkan bagaimana ia dilaksanakan secara tradisional di versi Java yang lebih lama.
Seterusnya, kami akan mencuba coraknya lagi, hanya kali ini dengan Java 8 lambdas, mengurangkan ketajaman kod kami.
2. Corak Strategi
Pada dasarnya, corak strategi membolehkan kita mengubah tingkah laku algoritma semasa menjalankan.
Biasanya, kita akan memulakan dengan antara muka yang digunakan untuk menerapkan algoritma, dan kemudian menerapkannya berkali-kali untuk setiap algoritma yang mungkin.
Katakanlah kita mempunyai syarat untuk menerapkan pelbagai jenis potongan untuk pembelian, berdasarkan sama ada Hari Krismas, Paskah atau Tahun Baru. Pertama, mari kita buat antara muka Discounter yang akan dilaksanakan oleh setiap strategi kami:
public interface Discounter { BigDecimal applyDiscount(BigDecimal amount); }
Oleh itu, katakan bahawa kami ingin menerapkan diskaun 50% pada Paskah dan 10% diskaun pada Krismas. Mari laksanakan antara muka kami untuk setiap strategi berikut:
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)); } }
Akhirnya, mari kita mencuba strategi dalam ujian:
Discounter easterDiscounter = new EasterDiscounter(); BigDecimal discountedValue = easterDiscounter .applyDiscount(BigDecimal.valueOf(100)); assertThat(discountedValue) .isEqualByComparingTo(BigDecimal.valueOf(50));
Ini berfungsi dengan baik, tetapi masalahnya adalah sedikit sukar untuk membuat kelas konkrit untuk setiap strategi. Alternatifnya ialah menggunakan jenis dalaman yang tidak dikenali, tetapi itu masih cukup verbose dan tidak lebih berguna daripada penyelesaian 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 dilancarkan, pengenalan lambdas telah menjadikan jenis dalaman tanpa nama lebih kurang. Ini bermaksud membuat strategi sejajar sekarang jauh lebih bersih dan lebih mudah.
Tambahan pula, gaya deklaratif pengaturcaraan fungsional memungkinkan kita menerapkan corak yang sebelumnya tidak mungkin dilakukan.
3.1. Mengurangkan Kosa Kata Kod
Mari cuba buat EasterDiscounter sebaris , hanya kali ini menggunakan ungkapan lambda:
Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5));
Seperti yang kita lihat, kod kita sekarang jauh lebih bersih dan lebih dapat dikendalikan, mencapai yang sama seperti sebelumnya tetapi dalam satu baris. Pada dasarnya, lambda dapat dilihat sebagai pengganti jenis dalaman yang tidak dikenali .
Kelebihan ini menjadi lebih jelas apabila kami ingin menyatakan lebih banyak Pendaftar 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)) );
Apabila kita ingin menentukan banyak Pemula, kita dapat menyatakan semuanya secara statik di satu tempat. Java 8 bahkan membolehkan kita menentukan kaedah statik di antara muka jika kita mahu.
Oleh itu, daripada memilih antara kelas konkrit atau jenis dalaman tanpa nama, mari cuba buat lambda semuanya 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 kita lihat, kita mencapai banyak dalam kod yang tidak banyak.
3.2. Menyusun Komposisi Fungsi
Mari ubah suai antara muka Discounter kami sehingga memperluas antara muka UnaryOperator , dan kemudian tambahkan kaedah gabungan () :
public interface Discounter extends UnaryOperator { default Discounter combine(Discounter after) { return value -> after.apply(this.apply(value)); } }
Pada dasarnya, kami memfaktorkan semula Discounter kami dan memanfaatkan fakta bahawa menerapkan potongan adalah fungsi yang mengubah instance BigDecimal menjadi instance BigDecimal yang lain , yang memungkinkan kami mengakses kaedah yang telah ditentukan . Oleh kerana UnaryOperator dilengkapi dengan kaedah apply () , kita hanya boleh mengganti applyDiscount dengannya.
Kaedah gabungan () hanyalah penekanan penggunaan satu Discounter untuk hasil ini. Ia menggunakan fungsi berfungsi terpasang () untuk mencapainya.
Sekarang, Mari cuba gunakan beberapa Potongan Diskaun secara berjumlah. Kami akan melakukan ini dengan menggunakan pengurangan fungsi () dan gabungan kami ():
Discounter combinedDiscounter = discounters .stream() .reduce(v -> v, Discounter::combine); combinedDiscounter.apply(...);
Beri perhatian khusus pada argumen pengurangan pertama . Apabila tidak ada potongan harga, kami perlu mengembalikan nilai yang tidak berubah. Ini dapat dicapai dengan menyediakan fungsi identiti sebagai penghitung lalai.
Ini adalah alternatif yang berguna dan kurang verbose untuk melakukan lelaran standard. Sekiranya kita mempertimbangkan kaedah yang kita gunakan untuk komposisi berfungsi, ini juga memberi kita lebih banyak fungsi secara percuma.
4. Kesimpulan
Dalam artikel ini, kami telah menjelaskan pola strategi, dan juga menunjukkan bagaimana kami dapat menggunakan ungkapan lambda untuk menerapkannya dengan cara yang kurang verbose.
Pelaksanaan contoh-contoh ini boleh didapati di GitHub. Ini adalah projek berasaskan Maven, jadi seharusnya mudah dijalankan seperti sedia kala.