CodeGym /จาวาบล็อก /สุ่ม /รูปแบบการออกแบบกลยุทธ์
John Squirrels
ระดับ
San Francisco

รูปแบบการออกแบบกลยุทธ์

เผยแพร่ในกลุ่ม
สวัสดี! ในบทเรียนวันนี้ เราจะพูดถึงรูปแบบกลยุทธ์ ในบทที่แล้ว เราได้ทำความคุ้นเคยกับแนวคิดเรื่องการสืบทอดมาพอสังเขปแล้ว ในกรณีที่คุณลืม ฉันจะเตือนคุณว่าคำนี้หมายถึงโซลูชันมาตรฐานสำหรับงานการเขียนโปรแกรมทั่วไป ที่ CodeGym เรามักจะพูดว่าคุณสามารถใช้ Google หาคำตอบสำหรับคำถามเกือบทุกข้อได้ นี่เป็นเพราะงานของคุณ ไม่ว่าจะเป็นอะไรก็ตาม อาจได้รับการแก้ไขเรียบร้อยแล้วโดยคนอื่น รูปแบบเป็นวิธีการแก้ปัญหาที่พยายามใช้จริงสำหรับงานทั่วไปหรือวิธีการแก้ไขสถานการณ์ที่เป็นปัญหา สิ่งเหล่านี้เปรียบเสมือน "วงล้อ" ที่คุณไม่จำเป็นต้องประดิษฐ์ขึ้นใหม่ด้วยตัวเอง แต่คุณจำเป็นต้องรู้ว่าจะใช้มันอย่างไรและเมื่อใด :) จุดประสงค์อีกประการหนึ่งสำหรับรูปแบบคือการส่งเสริมสถาปัตยกรรมที่เป็นหนึ่งเดียว การอ่านโค้ดของคนอื่นไม่ใช่เรื่องง่าย! ทุกคนเขียนโค้ดต่างกัน เพราะงานเดียวกันสามารถแก้ไขได้หลายวิธี แต่การใช้รูปแบบช่วยให้โปรแกรมเมอร์ที่แตกต่างกันเข้าใจตรรกะการเขียนโปรแกรมโดยไม่ต้องเจาะลึกรหัสแต่ละบรรทัด (แม้ว่าจะเห็นเป็นครั้งแรกก็ตาม!) วันนี้เรามาดูรูปแบบการออกแบบที่พบบ่อยที่สุดรูปแบบหนึ่งที่เรียกว่า "กลยุทธ์" รูปแบบการออกแบบ: กลยุทธ์ - 2ลองนึกภาพว่าเรากำลังเขียนโปรแกรมที่จะทำงานกับวัตถุ Conveyance ไม่สำคัญว่าโปรแกรมของเราจะทำอะไรกันแน่ เราได้สร้างลำดับชั้นของคลาสโดยมี คลาส Conveyance parent หนึ่งคลาสและคลาสรอง 3 คลาส: Sedan , Truckและ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 {
}
คลาสย่อยทั้งสามคลาสสืบทอดสองวิธีมาตรฐานจากพาเรนต์: go()และstop( ) โปรแกรมของเราง่ายมาก: รถของเราสามารถเคลื่อนที่ไปข้างหน้าและใช้เบรกเท่านั้น เราตัดสินใจให้วิธีการใหม่แก่รถ: เติม () (หมายถึง "เติมน้ำมันให้เต็มถัง") เราได้เพิ่มลงใน คลาส Conveyance parent:

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!");
   }
}
ปัญหาสามารถเกิดขึ้นได้จริง ๆ ในสถานการณ์ง่าย ๆ อย่างนั้นหรือ? ความจริงแล้วพวกเขามี... รูปแบบการออกแบบ: กลยุทธ์ - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
ขณะนี้โปรแกรมของเรามียานพาหนะ (รถเข็นเด็ก) ที่ไม่เข้ากับแนวคิดทั่วไป มันอาจมีแป้นเหยียบหรือควบคุมด้วยวิทยุ แต่สิ่งหนึ่งที่แน่นอนก็คือ มันไม่มีที่ให้เติมน้ำมัน ลำดับชั้นของคลาสของเราทำให้เมธอดทั่วไปได้รับการสืบทอดโดยคลาสที่ไม่ต้องการ เราควรทำอย่างไรในสถานการณ์เช่นนี้? เราสามารถแทนที่ เมธอด fill()ใน คลาส Strollerเพื่อไม่ให้เกิดอะไรขึ้นเมื่อคุณพยายามเติมเชื้อเพลิงให้กับรถเข็นเด็ก:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
แต่สิ่งนี้แทบจะไม่สามารถเรียกได้ว่าเป็นวิธีแก้ปัญหาที่ประสบความสำเร็จหากไม่มีเหตุผลอื่นนอกจากรหัสที่ซ้ำกัน ตัวอย่างเช่น คลาสส่วนใหญ่จะใช้เมธอดของคลาสพาเรนต์ แต่ส่วนที่เหลือจะถูกบังคับให้แทนที่ หากเรามี 15 คลาสและเราต้องแทนที่พฤติกรรมใน 5-6 คลาส การทำซ้ำโค้ดจะค่อนข้างกว้างขวาง บางทีอินเทอร์เฟซอาจช่วยเราได้ ตัวอย่างเช่น:

public interface Fillable {
  
   public void fill();
}
เราจะสร้าง ส่วนต่อประสาน ที่เติมได้ด้วย วิธี เติม () หนึ่ง วิธี จากนั้น ยานพาหนะที่ต้องเติมเชื้อเพลิงจะใช้อินเทอร์เฟซนี้ ในขณะที่ยานพาหนะอื่นๆ (เช่น รถเข็นเด็กของเรา) จะไม่ใช้ แต่ตัวเลือกนี้ไม่เหมาะกับเรา ในอนาคต ลำดับชั้นของเราอาจเติบโตจนใหญ่โตมาก (ลองนึกดูว่าในโลกนี้มีพาหนะประเภทต่างๆ กี่ประเภท) เราละทิ้งเวอร์ชันก่อนหน้าที่เกี่ยวข้องกับการสืบทอด เนื่องจากเราไม่ต้องการแทนที่การเติม ()วิธีการหลายครั้งหลายหน ตอนนี้เราต้องนำไปใช้ในทุกชั้นเรียน! แล้วถ้าเรามี 50 ล่ะ? และหากมีการเปลี่ยนแปลงบ่อยครั้งในโปรแกรมของเรา (และนี่มักจะเป็นจริงสำหรับโปรแกรมจริง!) เราจะต้องรีบผ่านทั้ง 50 ชั้นเรียนและเปลี่ยนพฤติกรรมของแต่ละชั้นเรียนด้วยตนเอง สุดท้ายแล้วเราควรทำอย่างไรในสถานการณ์นี้? เพื่อแก้ปัญหาของเรา เราจะเลือกวิธีอื่น กล่าวคือ เราจะแยกพฤติกรรมของชั้นเรียนออกจากตัวชั้นเรียนเอง นั่นหมายความว่าอย่างไร? อย่างที่คุณทราบ วัตถุทุกชิ้นมีสถานะ (ชุดของข้อมูล) และพฤติกรรม (ชุดของวิธีการ) ลักษณะการทำงานของคลาสพาหนะ ของเราประกอบด้วยสามวิธี: go() , stop()และfill() สองวิธีแรกใช้ได้ดีเหมือนเดิม แต่เราจะย้ายวิธีที่สามออกจากชั้น พาหนะ . สิ่งนี้จะแยกพฤติกรรมออกจากคลาส (ให้ถูกต้องยิ่งขึ้น มันจะแยกพฤติกรรมเพียงบางส่วน เนื่องจากสองวิธีแรกจะยังคงอยู่ที่เดิม) แล้วเราจะใส่ เมธอด fill() ไว้ที่ไหน ? ไม่มีอะไรอยู่ในใจ :/ ดูเหมือนว่ามันควรจะเป็นตรงไหน เราจะย้ายไปยังอินเทอร์เฟซแยกต่างหาก: FillStrategy !

public interface FillStrategy {

   public void fill();
}
เหตุใดเราจึงต้องการอินเทอร์เฟซดังกล่าว มันตรงไปตรงมาทั้งหมด ตอนนี้เราสามารถสร้างหลายคลาสที่ใช้อินเทอร์เฟซนี้:

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!");
   }
}
เราสร้างกลยุทธ์เชิงพฤติกรรมสามแบบ: กลยุทธ์หนึ่งสำหรับรถยนต์ธรรมดา หนึ่งกลยุทธ์สำหรับรถไฮบริด และอีกกลยุทธ์หนึ่งสำหรับรถแข่งฟอร์มูล่าวัน แต่ละกลยุทธ์ใช้อัลกอริธึมการเติมเชื้อเพลิงที่แตกต่างกัน ในกรณีของเรา เราเพียงแค่แสดงสตริงบนคอนโซล แต่แต่ละวิธีอาจมีตรรกะที่ซับซ้อน เราจะทำอย่างไรต่อไป?

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!");
   }
  
}
เราใช้ อินเทอร์ เฟซ FillStrategyเป็นฟิลด์ใน คลาส Conveyance parent โปรดทราบว่าเราไม่ได้ระบุการใช้งานที่เฉพาะเจาะจง — เรากำลังใช้อินเทอร์เฟซ คลาสรถจะต้องมีการใช้งานเฉพาะของ อินเทอร์ เฟซ 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();
   }
}

มาดูกันดีกว่าว่ามีอะไรบ้าง!

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();
   }
}
เอาต์พุตคอนโซล:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
ยอดเยี่ยม! กระบวนการเติมเชื้อเพลิงทำงานได้ตามปกติ! อย่างไรก็ตาม ไม่มีอะไรขัดขวางเราไม่ให้ใช้กลยุทธ์เป็นพารามิเตอร์ในตัวสร้าง! ตัวอย่างเช่น:

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());
   }
}
เรียกใช้ เมธอด main() ของเรา (ซึ่งยังคงไม่เปลี่ยนแปลง) เราได้ผลลัพธ์เดียวกัน! เอาต์พุตคอนโซล:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
รูปแบบการออกแบบกลยุทธ์จะกำหนดกลุ่มของอัลกอริทึม ห่อหุ้มแต่ละอัลกอริทึม และทำให้มั่นใจว่าสามารถใช้แทนกันได้ มันให้คุณแก้ไขอัลกอริทึมโดยไม่คำนึงว่าลูกค้าจะใช้มันอย่างไร (คำจำกัดความนี้นำมาจากหนังสือ "Head First Design Patterns" ดูเหมือนจะดีมากสำหรับฉัน) รูปแบบการออกแบบ: กลยุทธ์ - 4เราได้ระบุกลุ่มของอัลกอริทึมที่เราสนใจแล้ว (วิธีเติมน้ำมันรถยนต์) ในอินเทอร์เฟซที่แยกจากกันพร้อมการใช้งานที่แตกต่างกัน เราแยกออกจากตัวรถเอง ตอนนี้ หากเราจำเป็นต้องเปลี่ยนแปลงอัลกอริธึมการเติมน้ำมันใดๆ มันจะไม่ส่งผลกระทบต่อคลาสรถของเราแต่อย่างใด และเพื่อให้สามารถใช้แทนกันได้ เราเพียงแค่เพิ่มเมธอด setter เดียวให้กับคลาส Conveyance ของเรา:

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;
   }
}
ตอนนี้เราสามารถเปลี่ยนกลยุทธ์ได้ทันที:

public class Main {

   public static void main(String[] args) {

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

       stroller.fill();
   }
}
หากจู่ๆ รถเข็นเด็กเริ่มใช้น้ำมัน โปรแกรมของเราจะพร้อมรับมือกับสถานการณ์นี้ :) แค่นั้น! คุณได้เรียนรู้อีกหนึ่งรูปแบบการออกแบบที่จะมีความสำคัญและเป็นประโยชน์อย่างไม่ต้องสงสัยเมื่อทำงานในโครงการจริง :) จนกว่าจะถึงครั้งต่อไป!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION