CodeGym /Blog Java /rawak /Corak Reka Bentuk Strategi
John Squirrels
Tahap
San Francisco

Corak Reka Bentuk Strategi

Diterbitkan dalam kumpulan
Hai! Dalam pelajaran hari ini, kita akan bercakap tentang corak Strategi. Dalam pelajaran sebelum ini, kita telah mengenali secara ringkas konsep pewarisan. Sekiranya anda terlupa, saya akan mengingatkan anda bahawa istilah ini merujuk kepada penyelesaian standard kepada tugas pengaturcaraan biasa. Di CodeGym, kami sering mengatakan bahawa anda boleh google jawapan kepada hampir semua soalan. Ini kerana tugas anda, walau apa pun, mungkin telah berjaya diselesaikan oleh orang lain. Corak ialah penyelesaian yang dicuba dan benar kepada tugas yang paling biasa, atau kaedah untuk menyelesaikan situasi bermasalah. Ini seperti "roda" yang anda tidak perlu cipta semula sendiri, tetapi anda perlu tahu cara dan masa untuk menggunakannya :) Satu lagi tujuan untuk corak adalah untuk mempromosikan seni bina seragam. Membaca kod orang lain bukanlah tugas yang mudah! Setiap orang menulis kod yang berbeza, kerana tugas yang sama boleh diselesaikan dengan pelbagai cara. Tetapi penggunaan corak membantu pengaturcara yang berbeza memahami logik pengaturcaraan tanpa menyelidiki setiap baris kod (walaupun ketika melihatnya buat kali pertama!) Hari ini kita melihat salah satu corak reka bentuk yang paling biasa dipanggil "Strategi". Corak reka bentuk: Strategi - 2Bayangkan bahawa kami sedang menulis program yang akan berfungsi secara aktif dengan objek Pengangkutan. Tidak kira apa sebenarnya program kami lakukan. Kami telah mencipta hierarki kelas dengan satu kelas induk Conveyance dan tiga kelas kanak-kanak: 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-tiga kelas anak mewarisi dua kaedah standard daripada induk: go() dan stop() . Program kami sangat mudah: kereta kami hanya boleh bergerak ke hadapan dan menggunakan brek. Meneruskan kerja kami, kami memutuskan untuk memberi kereta kaedah baharu: fill() (bermaksud, "isi tangki minyak"). 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!");
   }
}
Bolehkah masalah benar-benar timbul dalam keadaan semudah itu? Malah, mereka sudah mempunyai... Corak reka bentuk: 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 kini mempunyai kenderaan (kereta sorong bayi) yang tidak sesuai dengan konsep umum. Ia mungkin mempunyai pedal atau dikawal radio, tetapi satu perkara yang pasti - ia tidak akan mempunyai sebarang tempat untuk mencurahkan gas. Hierarki kelas kami telah menyebabkan kaedah biasa diwarisi oleh kelas yang tidak memerlukannya. Apa yang perlu kita lakukan dalam keadaan ini? Nah, kita boleh mengatasi kaedah fill() dalam kelas Stroller supaya tiada apa-apa yang berlaku apabila anda cuba mengisi minyak kereta dorong:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Tetapi ini tidak boleh dipanggil penyelesaian yang berjaya jika tidak ada sebab lain daripada kod pendua. Sebagai contoh, kebanyakan kelas akan menggunakan kaedah kelas induk, tetapi selebihnya akan dipaksa untuk mengatasinya. Jika kita mempunyai 15 kelas dan kita mesti mengatasi tingkah laku dalam 5-6 daripadanya, pertindihan kod akan menjadi agak meluas. Mungkin antara muka boleh membantu kami? Sebagai contoh, seperti ini:

public interface Fillable {
  
   public void fill();
}
Kami akan mencipta antara muka Boleh Diisi dengan kaedah satu fill() . Kemudian, kenderaan yang perlu diisi minyak akan melaksanakan antara muka ini, manakala kenderaan lain (contohnya, kereta sorong bayi kami) tidak akan melaksanakannya. Tetapi pilihan ini tidak sesuai dengan kami. Pada masa hadapan, hierarki kelas kami mungkin berkembang menjadi sangat besar (bayangkan berapa banyak jenis kenderaan yang terdapat di dunia). Kami meninggalkan versi sebelumnya yang melibatkan warisan, kerana kami tidak mahu mengatasi fill ()kaedah berkali-kali. Sekarang kita perlu melaksanakannya dalam setiap kelas! Dan bagaimana jika kita mempunyai 50? Dan jika perubahan kerap akan dibuat dalam program kami (dan ini hampir selalu benar untuk program sebenar!), kami perlu tergesa-gesa melalui semua 50 kelas dan menukar tingkah laku setiap kelas secara manual. Jadi, pada akhirnya, apa yang harus kita lakukan dalam situasi ini? Untuk menyelesaikan masalah kami, kami akan memilih cara yang berbeza. Iaitu, kita akan memisahkan tingkah laku kelas kita daripada kelas itu sendiri. Apakah maksudnya? Seperti yang anda ketahui, setiap objek mempunyai keadaan (satu set data) dan tingkah laku (satu set kaedah). Gelagat kelas penghantar kami terdiri daripada tiga kaedah: go() , stop() dan fill() . Dua kaedah pertama adalah baik sama seperti mereka. Tetapi kami akan memindahkan kaedah ketiga daripadaKelas penghantar . Ini akan memisahkan tingkah laku daripada kelas (lebih tepat, ia akan memisahkan hanya sebahagian daripada tingkah laku, kerana dua kaedah pertama akan kekal di mana mereka berada). Jadi di manakah kita harus meletakkan kaedah fill() kita ? Tiada apa yang terlintas di fikiran :/ Nampaknya ia betul-betul di tempat yang sepatutnya. Kami akan mengalihkannya ke antara muka yang berasingan: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Mengapa kita memerlukan antara muka sedemikian? Semuanya mudah. Sekarang kita boleh membuat beberapa kelas yang melaksanakan antara muka 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 mencipta tiga strategi tingkah laku: satu untuk kereta biasa, satu untuk hibrid dan satu untuk kereta lumba Formula 1. Setiap strategi melaksanakan algoritma pengisian bahan api yang berbeza. Dalam kes kami, kami hanya memaparkan rentetan pada konsol, tetapi setiap kaedah boleh mengandungi beberapa logik yang kompleks. Apa yang kita lakukan seterusnya?

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 antara muka FillStrategy kami sebagai medan dalam kelas induk Conveyance . Harap maklum bahawa kami tidak menunjukkan pelaksanaan tertentu — kami menggunakan antara muka. Kelas kereta memerlukan pelaksanaan khusus antara muka 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 lihat apa yang kami dapat!

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();
   }
}
Output konsol:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Hebat! Proses mengisi minyak berfungsi sebagaimana mestinya! Dengan cara ini, tiada apa yang menghalang kami daripada menggunakan strategi sebagai parameter dalam pembina! Sebagai contoh, 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 kaedah utama() kami (yang kekal tidak berubah). Kami mendapat keputusan yang sama! Output konsol:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Corak reka bentuk strategi mentakrifkan keluarga algoritma, merangkum setiap satu daripadanya dan memastikan bahawa ia boleh ditukar ganti. Ia membolehkan anda mengubah suai algoritma tanpa mengira cara ia digunakan oleh pelanggan (takrifan ini, yang diambil daripada buku "Corak Reka Bentuk Kepala Pertama", nampaknya sangat baik bagi saya). Corak reka bentuk: Strategi - 4Kami telah menentukan keluarga algoritma yang kami minati (cara mengisi minyak kereta) dalam antara muka berasingan dengan pelaksanaan yang berbeza. Kami memisahkan mereka dari kereta itu sendiri. Sekarang jika kita perlu membuat sebarang perubahan pada algoritma pengisian bahan api tertentu, ia tidak akan menjejaskan kelas kereta kita dalam apa jua cara. Dan untuk mencapai kebolehtukaran, kami hanya perlu menambah satu kaedah penetap pada kelas Pengangkutan kami :

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;
   }
}
Kini kita boleh menukar strategi dengan cepat:

public class Main {

   public static void main(String[] args) {

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

       stroller.fill();
   }
}
Jika kereta sorong bayi tiba-tiba mula menggunakan petrol, program kami akan bersedia untuk mengendalikan senario ini :) Dan itu sahaja! Anda telah mempelajari satu lagi corak reka bentuk yang sudah pasti akan menjadi penting dan membantu apabila bekerja pada projek sebenar :) Sehingga kali seterusnya!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION