CodeGym /Java Blog /Acak /Pola Desain Strategi
John Squirrels
Level 41
San Francisco

Pola Desain Strategi

Dipublikasikan di grup Acak
Hai! Dalam pelajaran hari ini, kita akan berbicara tentang pola Strategi. Dalam pelajaran sebelumnya, kita telah secara singkat mengenal konsep pewarisan. Jika Anda lupa, saya akan mengingatkan Anda bahwa istilah ini mengacu pada solusi standar untuk tugas pemrograman umum. Di CodeGym, kami sering mengatakan bahwa Anda dapat menggunakan Google untuk menjawab hampir semua pertanyaan. Ini karena tugas Anda, apapun itu, mungkin sudah berhasil diselesaikan oleh orang lain. Pola adalah solusi yang dicoba dan benar untuk tugas yang paling umum, atau metode untuk memecahkan situasi bermasalah. Ini seperti "roda" yang tidak perlu Anda temukan sendiri, tetapi Anda perlu tahu bagaimana dan kapan menggunakannya :) Tujuan lain dari pola adalah untuk mempromosikan arsitektur yang seragam. Membaca kode orang lain bukanlah tugas yang mudah! Setiap orang menulis kode yang berbeda, karena tugas yang sama dapat diselesaikan dengan banyak cara. Tetapi penggunaan pola membantu pemrogram yang berbeda memahami logika pemrograman tanpa mempelajari setiap baris kode (bahkan saat melihatnya untuk pertama kali!) Hari ini kita melihat salah satu pola desain paling umum yang disebut "Strategi". Pola desain: Strategi - 2Bayangkan kita sedang menulis sebuah program yang secara aktif akan bekerja dengan objek Conveyance. Tidak masalah apa sebenarnya program kami. Kami telah membuat hierarki kelas dengan satu kelas induk Conveyance dan tiga kelas anak: Sedan , Truck dan F1Car .

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
Ketiga kelas anak mewarisi dua metode standar dari induknya: go() dan stop() . Program kami sangat sederhana: mobil kami hanya dapat bergerak maju dan menginjak rem. Melanjutkan pekerjaan kami, kami memutuskan untuk memberi mobil metode baru: fill() (artinya, "isi tangki bensin"). Kami menambahkannya ke kelas induk Conveyance :

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
  
   public void fill() {
       System.out.println("Refueling!");
   }
}
Bisakah masalah benar-benar muncul dalam situasi yang begitu sederhana? Bahkan, mereka sudah memiliki... Pola desain: Strategi - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
Program kami sekarang memiliki alat angkut (kereta dorong bayi) yang tidak sesuai dengan konsep umum. Itu bisa memiliki pedal atau dikendalikan oleh radio, tetapi satu hal yang pasti - tidak akan ada tempat untuk menuangkan bensin. Hirarki kelas kami telah menyebabkan metode umum diwarisi oleh kelas yang tidak membutuhkannya. Apa yang harus kita lakukan dalam situasi ini? Nah, kita bisa mengganti metode fill() di kelas Stroller sehingga tidak terjadi apa-apa saat Anda mencoba mengisi bahan bakar kereta dorong:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Tapi ini hampir tidak bisa disebut sebagai solusi yang berhasil jika tidak ada alasan lain selain kode duplikat. Misalnya, sebagian besar kelas akan menggunakan metode kelas induk, tetapi sisanya akan dipaksa untuk menimpanya. Jika kita memiliki 15 kelas dan kita harus mengesampingkan perilaku di 5-6 kelas, duplikasi kode akan menjadi sangat luas. Mungkin antarmuka dapat membantu kita? Misalnya, seperti ini:

public interface Fillable {
  
   public void fill();
}
Kita akan membuat antarmuka Fillable dengan satu metode fill() . Kemudian, alat angkut yang perlu diisi bahan bakar akan menerapkan antarmuka ini, sedangkan alat angkut lain (misalnya kereta dorong bayi kami) tidak akan melakukannya. Tetapi opsi ini tidak cocok untuk kita. Di masa depan, hierarki kelas kita mungkin tumbuh menjadi sangat besar (bayangkan saja berapa banyak jenis alat angkut yang ada di dunia). Kami mengabaikan versi sebelumnya yang melibatkan pewarisan, karena kami tidak ingin mengganti isi ()metode berkali-kali. Sekarang kita harus mengimplementasikannya di setiap kelas! Dan bagaimana jika kita memiliki 50? Dan jika sering terjadi perubahan dalam program kita (dan ini hampir selalu berlaku untuk program nyata!), kita harus menelusuri semua 50 kelas dan secara manual mengubah perilaku masing-masing kelas. Jadi, pada akhirnya, apa yang harus kita lakukan dalam situasi ini? Untuk mengatasi masalah kita, kita akan memilih cara yang berbeda. Yakni, kita akan memisahkan perilaku kelas kita dari kelas itu sendiri. Maksudnya itu apa? Seperti yang Anda ketahui, setiap objek memiliki status (satu set data) dan perilaku (satu set metode). Perilaku kelas alat angkut kita terdiri dari tiga metode: go() , stop() dan fill() . Dua metode pertama baik-baik saja. Tapi kami akan memindahkan metode ketiga dariKelas alat angkut . Ini akan memisahkan perilaku dari kelas (lebih tepatnya, ini hanya akan memisahkan sebagian dari perilaku, karena dua metode pertama akan tetap berada di tempatnya). Jadi di mana kita harus meletakkan metode fill() kita ? Tidak ada yang terlintas dalam pikiran: / Sepertinya memang begitu seharusnya. Kami akan memindahkannya ke antarmuka terpisah: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Mengapa kita membutuhkan antarmuka seperti itu? Semuanya mudah. Sekarang kita dapat membuat beberapa kelas yang mengimplementasikan antarmuka ini:

public class HybridFillStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
Kami menciptakan tiga strategi perilaku: satu untuk mobil biasa, satu untuk hibrida, dan satu untuk mobil balap Formula 1. Setiap strategi menerapkan algoritma pengisian bahan bakar yang berbeda. Dalam kasus kami, kami hanya menampilkan string di konsol, tetapi setiap metode dapat berisi beberapa logika kompleks. Apa yang kita lakukan selanjutnya?

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
  
}
Kami menggunakan antarmuka FillStrategy kami sebagai bidang di kelas induk Penyampaian . Perhatikan bahwa kami tidak menunjukkan implementasi tertentu — kami menggunakan antarmuka. Kelas mobil akan membutuhkan implementasi khusus dari antarmuka FillStrategy :

public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}

Mari kita lihat apa yang kita punya!

public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Keluaran konsol:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Besar! Proses pengisian bahan bakar berjalan sebagaimana mestinya! Omong-omong, tidak ada yang menghalangi kita untuk menggunakan strategi sebagai parameter dalam konstruktor! Misalnya, seperti ini:

public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Mari jalankan metode main() kita (yang tetap tidak berubah). Kami mendapatkan hasil yang sama! Keluaran konsol:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Pola desain strategi mendefinisikan keluarga algoritma, merangkum masing-masing, dan memastikan bahwa mereka dapat dipertukarkan. Ini memungkinkan Anda memodifikasi algoritme terlepas dari bagaimana algoritme tersebut digunakan oleh klien (definisi ini, diambil dari buku "Pola Desain Kepala Pertama", menurut saya sangat bagus). Pola desain: Strategi - 4Kami telah menentukan keluarga algoritme yang kami minati (cara mengisi bahan bakar mobil) di antarmuka terpisah dengan implementasi berbeda. Kami memisahkan mereka dari mobil itu sendiri. Sekarang jika kita perlu membuat perubahan pada algoritme pengisian bahan bakar tertentu, itu tidak akan memengaruhi kelas mobil kita sama sekali. Dan untuk mencapai interchangeability, kita hanya perlu menambahkan satu setter method ke kelas Conveyance kita:

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Sekarang kita dapat mengubah strategi dengan cepat:

public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
Jika kereta bayi tiba-tiba mulai menggunakan bensin, program kami akan siap menangani skenario ini :) Dan hanya itu! Anda telah mempelajari satu lagi pola desain yang tidak diragukan lagi akan sangat penting dan membantu saat mengerjakan proyek nyata :) Sampai lain kali!
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION