Pengantar pola desain, Bagian 2: Gang-of-empat klasik ditinjau kembali

Pada Bagian 1 dari rangkaian tiga bagian ini yang memperkenalkan pola desain, saya mengacu pada Pola Desain: Elemen Desain Berorientasi Objek yang Dapat Digunakan Kembali . Buku klasik ini ditulis oleh Erich Gamma, Richard Helm, Ralph Johnson, dan John Vlissides, yang secara kolektif dikenal sebagai Gang of Four. Seperti yang diketahui sebagian besar pembaca, Design Patterns menyajikan 23 pola desain perangkat lunak yang sesuai dengan kategori yang dibahas di Bagian 1: Kreasi, struktural, dan perilaku.

Pola desain di JavaWorld

Seri pola desain Java David Geary adalah pengantar ahli untuk banyak pola Gang of Four dalam kode Java.

Design Patterns adalah bacaan kanonik untuk pengembang perangkat lunak, tetapi banyak programmer baru merasa tertantang oleh format referensi dan cakupannya. Masing-masing dari 23 pola dijelaskan secara mendetail, dalam format templat yang terdiri dari 13 bagian, yang bisa sangat banyak untuk dicerna. Tantangan lain bagi pengembang Java baru adalah bahwa pola Gang of Four muncul dari pemrograman berorientasi objek, dengan contoh berdasarkan C ++ dan Smalltalk, bukan kode Java.

Dalam tutorial ini saya akan membongkar dua pola yang umum digunakan - Strategi dan Pengunjung - dari perspektif pengembang Java. Strategi adalah pola yang cukup sederhana yang berfungsi sebagai contoh bagaimana membuat kaki Anda basah dengan pola desain GoF secara umum; Pengunjung lebih kompleks dan cakupannya menengah. Saya akan mulai dengan contoh yang seharusnya mengungkap mekanisme pengiriman ganda, yang merupakan bagian penting dari pola Pengunjung. Kemudian saya akan mendemonstrasikan pola Pengunjung dalam kasus penggunaan kompilator.

Mengikuti contoh saya di sini akan membantu Anda menjelajahi dan menggunakan pola GoF lainnya untuk diri Anda sendiri. Selain itu, saya akan menawarkan tip untuk mendapatkan hasil maksimal dari buku Gang of Four dan diakhiri dengan ringkasan kritik penggunaan pola desain dalam pengembangan perangkat lunak. Diskusi itu bisa sangat relevan bagi pengembang yang baru mengenal pemrograman.

Strategi Membongkar

The Strategi Pola memungkinkan Anda menentukan keluarga algoritma seperti yang digunakan untuk menyortir, komposisi teks, atau manajemen tata letak. Strategi juga memungkinkan Anda merangkum setiap algoritme di kelasnya sendiri dan membuatnya dapat dipertukarkan. Setiap algoritma yang dienkapsulasi dikenal sebagai strategi . Saat runtime, klien memilih algoritme yang sesuai untuk persyaratannya.

Apa itu klien?

Sebuah client adalah setiap software yang berinteraksi dengan pola desain. Meskipun biasanya sebuah objek, klien juga bisa menjadi kode dalam metode aplikasi public static void main(String[] args).

Berbeda dengan pola Dekorator, yang berfokus pada perubahan tampilan atau kulit suatu objek , Strategi berfokus pada perubahan nyali objek , yang berarti perilakunya yang dapat diubah. Strategi memungkinkan Anda menghindari penggunaan beberapa pernyataan bersyarat dengan memindahkan cabang bersyarat ke dalam kelas strateginya sendiri. Kelas-kelas ini sering kali berasal dari superclass abstrak, yang direferensikan oleh klien dan digunakan untuk berinteraksi dengan strategi tertentu.

Dari perspektif abstrak, Strategi melibatkan Strategy, dan jenis.ConcreteStrategyxContext

Strategi

Strategymenyediakan antarmuka umum untuk semua algoritme yang didukung. Kode 1 menyajikan Strategyantarmuka.

Daftar 1. eksekusi void (int x) harus dilaksanakan dengan semua strategi konkret

public interface Strategy { public void execute(int x); }

Jika strategi konkret tidak diparameterisasi dengan data umum, Anda dapat menerapkannya melalui interfacefitur Java . Di mana mereka diparameterisasi, Anda malah akan mendeklarasikan kelas abstrak. Misalnya, strategi perataan kanan-kanan, tengah-tengah, dan rata kanan-kiri berbagi konsep lebar yang digunakan untuk melakukan perataan teks. Jadi, Anda akan mendeklarasikan lebar ini di kelas abstrak.

Strategi Beton x

Masing-masing mengimplementasikan antarmuka umum dan menyediakan implementasi algoritme. Kode 2 mengimplementasikan antarmuka Kode 1 untuk menggambarkan strategi konkret tertentu.ConcreteStrategyxStrategy

Kode 2. ConcreteStrategyA menjalankan satu algoritma

public class ConcreteStrategyA implements Strategy { @Override public void execute(int x) { System.out.println("executing strategy A: x = "+x); } }

The void execute(int x)metode dalam properti 2 mengidentifikasi strategi khusus. Pikirkan metode ini sebagai abstraksi untuk sesuatu yang lebih berguna, seperti jenis algoritma pengurutan tertentu (misalnya, Bubble Sort, Insertion Sort, atau Quick Sort), atau jenis manajer tata letak tertentu (misalnya, Tata Letak Alur, Tata Letak Perbatasan, atau Tata Letak Kisi).

Kode 3 menyajikan Strategyimplementasi kedua .

Kode 3. ConcreteStrategyB menjalankan algoritma lain

public class ConcreteStrategyB implements Strategy { @Override public void execute(int x) { System.out.println("executing strategy B: x = "+x); } }

Konteks

Contextmemberikan konteks di mana strategi konkret digunakan. Cantuman 2 dan 3 menunjukkan data yang diteruskan dari konteks ke strategi melalui parameter metode. Karena antarmuka strategi umum digunakan oleh semua strategi konkret, beberapa di antaranya mungkin tidak memerlukan semua parameter. Untuk menghindari parameter yang terbuang percuma (terutama saat meneruskan berbagai jenis argumen hanya ke beberapa strategi konkret), Anda dapat meneruskan referensi ke konteks sebagai gantinya.

Alih-alih meneruskan referensi konteks ke metode, Anda bisa menyimpannya di kelas abstrak, membuat metode Anda memanggil tanpa parameter. Namun, konteks tersebut perlu menentukan antarmuka yang lebih luas yang akan mencakup kontrak untuk mengakses data konteks dengan cara yang seragam. Hasilnya, seperti yang ditunjukkan pada Daftar 4, adalah hubungan yang lebih erat antara strategi dan konteksnya.

Kode 4. Konteks dikonfigurasi dengan contoh ConcreteStrategyx

class Context { private Strategy strategy; public Context(Strategy strategy) { setStrategy(strategy); } public void executeStrategy(int x) { strategy.execute(x); } public void setStrategy(Strategy strategy) { this.strategy = strategy; } }

The Contextkelas dalam properti 4 toko strategi ketika dibuat, menyediakan metode untuk kemudian mengubah strategi, dan menyediakan metode lain untuk menjalankan strategi saat ini. Kecuali untuk melewati strategi untuk konstruktor, pola ini dapat dilihat di java.awt .container kelas, yang void setLayout(LayoutManager mgr)dan void doLayout()metode menentukan dan melaksanakan strategi manajer layout.

StrategyDemo

Kami membutuhkan klien untuk mendemonstrasikan tipe sebelumnya. Kode 5 menyajikan StrategyDemokelas klien.

Daftar 5. StrategyDemo

public class StrategyDemo { public static void main(String[] args) { Context context = new Context(new ConcreteStrategyA()); context.executeStrategy(1); context.setStrategy(new ConcreteStrategyB()); context.executeStrategy(2); } }

Strategi konkret dikaitkan dengan Contextcontoh ketika konteks dibuat. Strategi selanjutnya dapat diubah melalui panggilan metode konteks.

Jika Anda mengompilasi kelas-kelas ini dan menjalankannya StrategyDemo, Anda harus mengamati keluaran berikut:

executing strategy A: x = 1 executing strategy B: x = 2

Meninjau kembali pola Pengunjung

Pengunjung adalah pola desain perangkat lunak terakhir yang muncul dalam Pola Desain . Meskipun pola perilaku ini disajikan terakhir dalam buku karena alasan alfabetis, beberapa orang percaya bahwa itu harus dilakukan terakhir karena kompleksitasnya. Pengunjung baru di Pengunjung sering kesulitan dengan pola desain perangkat lunak ini.

Seperti yang dijelaskan dalam Pola Desain , pengunjung memungkinkan Anda menambahkan operasi ke kelas tanpa mengubahnya, sedikit keajaiban yang difasilitasi oleh apa yang disebut teknik pengiriman ganda. Untuk memahami pola Pengunjung, pertama-tama kita perlu mencerna pengiriman ganda.

Apa itu pengiriman ganda?

Java dan banyak bahasa lain mendukung polimorfisme (banyak bentuk) melalui teknik yang dikenal sebagai pengiriman dinamis , di mana pesan dipetakan ke urutan kode tertentu pada waktu proses. Pengiriman dinamis diklasifikasikan sebagai pengiriman tunggal atau beberapa pengiriman:

  • Pengiriman tunggal : Diberikan hierarki kelas di mana setiap kelas mengimplementasikan metode yang sama (yaitu, setiap subkelas menimpa versi metode kelas sebelumnya), dan diberikan variabel yang menetapkan instance dari salah satu kelas ini, jenisnya dapat diketahui hanya saat runtime. Misalnya, setiap kelas mengimplementasikan metode print(). Anggap juga bahwa salah satu kelas ini dibuat instance-nya pada waktu proses dan variabelnya ditetapkan ke variabel a. Ketika kompilator Java bertemu a.print();, ia hanya dapat memverifikasi bahwa atipe itu berisi sebuah print()metode. Ia tidak tahu metode mana yang harus dipanggil. Saat runtime, mesin virtual memeriksa referensi dalam variabeladan mencari tahu tipe sebenarnya untuk memanggil metode yang benar. Situasi ini, di mana implementasi didasarkan pada satu jenis (jenis instance), dikenal sebagai pengiriman tunggal .
  • Pengiriman ganda : Tidak seperti dalam pengiriman tunggal, di mana satu argumen menentukan metode mana dari nama itu yang akan dipanggil, beberapa pengiriman menggunakan semua argumennya. Dengan kata lain, ini menggeneralisasi pengiriman dinamis untuk bekerja dengan dua objek atau lebih. (Perhatikan bahwa argumen dalam pengiriman tunggal biasanya ditentukan dengan pemisah titik di sebelah kiri nama metode yang dipanggil, seperti a in a.print().)

Akhirnya, pengiriman ganda adalah kasus khusus pengiriman ganda di mana jenis runtime dari dua objek terlibat dalam panggilan. Meskipun Java mendukung pengiriman tunggal, Java tidak mendukung pengiriman ganda secara langsung. Tapi kita bisa mensimulasikannya.

Apakah kita terlalu mengandalkan pengiriman ganda?

Blogger Derek Greer percaya bahwa menggunakan pengiriman ganda dapat menunjukkan masalah desain, yang dapat memengaruhi pemeliharaan aplikasi. Baca entri blog Greer "Pengiriman ganda adalah bau kode" dan komentar terkait untuk detailnya.

Mensimulasikan pengiriman ganda dalam kode Java

Entri Wikipedia tentang pengiriman ganda memberikan contoh berbasis C ++ yang menunjukkan bahwa itu lebih dari sekadar fungsi overloading. Dalam Daftar 6, saya menyajikan padanan Java.

Kode 6. Pengiriman ganda dalam kode Java

public class DDDemo { public static void main(String[] args) { Asteroid theAsteroid = new Asteroid(); SpaceShip theSpaceShip = new SpaceShip(); ApolloSpacecraft theApolloSpacecraft = new ApolloSpacecraft(); theAsteroid.collideWith(theSpaceShip); theAsteroid.collideWith(theApolloSpacecraft); System.out.println(); ExplodingAsteroid theExplodingAsteroid = new ExplodingAsteroid(); theExplodingAsteroid.collideWith(theSpaceShip); theExplodingAsteroid.collideWith(theApolloSpacecraft); System.out.println(); Asteroid theAsteroidReference = theExplodingAsteroid; theAsteroidReference.collideWith(theSpaceShip); theAsteroidReference.collideWith(theApolloSpacecraft); System.out.println(); SpaceShip theSpaceShipReference = theApolloSpacecraft; theAsteroid.collideWith(theSpaceShipReference); theAsteroidReference.collideWith(theSpaceShipReference); System.out.println(); theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith(theAsteroid); theSpaceShipReference.collideWith(theAsteroidReference); } } class SpaceShip { void collideWith(Asteroid inAsteroid) { inAsteroid.collideWith(this); } } class ApolloSpacecraft extends SpaceShip { void collideWith(Asteroid inAsteroid) { inAsteroid.collideWith(this); } } class Asteroid { void collideWith(SpaceShip s) { System.out.println("Asteroid hit a SpaceShip"); } void collideWith(ApolloSpacecraft as) { System.out.println("Asteroid hit an ApolloSpacecraft"); } } class ExplodingAsteroid extends Asteroid { void collideWith(SpaceShip s) { System.out.println("ExplodingAsteroid hit a SpaceShip"); } void collideWith(ApolloSpacecraft as) { System.out.println("ExplodingAsteroid hit an ApolloSpacecraft"); } }

Listing 6 follows its C++ counterpart as closely as possible. The final four lines in the main() method along with the void collideWith(Asteroid inAsteroid) methods in SpaceShip and ApolloSpacecraft demonstrate and simulate double dispatch.

Consider the following excerpt from the end of main():

theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith(theAsteroid); theSpaceShipReference.collideWith(theAsteroidReference);

The third and fourth lines use single dispatch to figure out the correct collideWith() method (in SpaceShip or ApolloSpacecraft) to invoke. This decision is made by the virtual machine based on the type of the reference stored in theSpaceShipReference.

Dari dalam collideWith(), inAsteroid.collideWith(this);gunakan pengiriman tunggal untuk menemukan kelas yang benar ( Asteroidatau ExplodingAsteroid) berisi collideWith()metode yang diinginkan . Karena Asteroiddan ExplodingAsteroidkelebihan beban collideWith(), jenis argumen this( SpaceShipatau ApolloSpacecraft) digunakan untuk membedakan collideWith()metode yang benar untuk dipanggil.

Dan dengan itu, kami telah menyelesaikan pengiriman ganda. Untuk rekap, kita pertama kali disebut collideWith()dalam SpaceShipatau ApolloSpacecraft, dan kemudian digunakan argumen dan thisuntuk memanggil salah satu collideWith()metode dalam Asteroidatau ExplodingAsteroid.

Saat Anda menjalankan DDDemo, Anda harus mengamati output berikut: