JavaRush /จาวาบล็อก /Random-TH /รูปแบบการออกแบบ “กลยุทธ์”

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

เผยแพร่ในกลุ่ม
สวัสดี! ในการบรรยายครั้งก่อนๆ เราได้พบแนวคิดของ “รูปแบบการออกแบบ” ไปแล้ว ในกรณีที่คุณลืม ให้เราเตือนคุณ: คำนี้หมายถึงวิธีแก้ปัญหามาตรฐานสำหรับปัญหาทั่วไปในการเขียนโปรแกรม รูปแบบการออกแบบ “กลยุทธ์” - 1ที่ JavaRush เรามักจะพูดว่าคำตอบสำหรับคำถามเกือบทุกข้อสามารถค้นหาใน Google ได้ ดังนั้นอาจมีบางคนแก้ไขปัญหาที่คล้ายกับของคุณสำเร็จแล้ว ดังนั้น รูปแบบจึงเป็นวิธีแก้ปัญหาที่ผ่านการทดสอบตามเวลาและแบบทดสอบแล้วสำหรับปัญหาที่พบบ่อยที่สุดหรือวิธีการในการแก้ไขสถานการณ์ปัญหา เหล่านี้คือ "จักรยาน" ที่แท้จริงซึ่งไม่ว่าในกรณีใดคุณจำเป็นต้องประดิษฐ์ตัวเองขึ้นมา แต่คุณต้องรู้วิธีและเวลาในการนำไปใช้ :) งานของรูปแบบอีกประการหนึ่งคือการนำสถาปัตยกรรมไปสู่มาตรฐานเดียว การอ่านโค้ดของคนอื่นไม่ใช่เรื่องง่าย! แต่ละคนเขียนต่างกัน เพราะปัญหาเดียวกันสามารถแก้ไขได้หลายวิธี แต่การใช้รูปแบบช่วยให้โปรแกรมเมอร์หลายๆ คนเข้าใจตรรกะของโปรแกรมโดยไม่ต้องเจาะลึกโค้ดทุกบรรทัด (แม้ว่าพวกเขาจะเห็นมันเป็นครั้งแรกก็ตาม!) วันนี้เราจะมาดูหนึ่งในรูปแบบที่พบบ่อยที่สุดที่เรียกว่า “กลยุทธ์” รูปแบบการออกแบบ “กลยุทธ์” - 2สมมติว่าเรากำลังเขียนโปรแกรมที่ทำงานร่วมกับวัตถุ Car อยู่ ในกรณีนี้ ไม่ได้มีความสำคัญเป็นพิเศษว่าโปรแกรมของเราทำอะไรกันแน่ เมื่อต้องการทำเช่นนี้ เราได้สร้างระบบการสืบทอดโดยมีคลาสพาเรนต์หนึ่งคลาสAutoและคลาสลูกสามคลาส: SedanและTruckF1Car
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
คลาสย่อยทั้งสามคลาสสืบทอดวิธีการมาตรฐานสองวิธีจากผู้ปกครอง - gas()และstop() โปรแกรมของเรานั้นง่ายมาก: รถยนต์ทำได้เพียงขับไปข้างหน้าและเบรกเท่านั้น ในการทำงานของเราต่อไป เราตัดสินใจเพิ่มวิธีการใหม่ให้กับรถยนต์ - fill()(เติมเชื้อเพลิง) มาเพิ่มลงในคลาส parent Auto:
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
ดูเหมือนว่าปัญหาจะเกิดขึ้นในสถานการณ์ง่ายๆเช่นนี้เหรอ? ที่จริงแล้วพวกมันได้เกิดขึ้นแล้ว... รูปแบบการออกแบบ “กลยุทธ์” - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
ในโปรแกรมของเรามีรถยนต์ที่ไม่เข้ากับแนวคิดทั่วไปนั่นคือรถบักกี้สำหรับเด็ก อาจใช้คันเหยียบหรือควบคุมด้วยวิทยุ แต่สิ่งหนึ่งที่แน่นอนคือไม่มีที่สำหรับใส่น้ำมันเบนซินลงไป แผนการสืบทอดของเราส่งผลให้เราแจกวิธีการทั่วไปแม้กระทั่งคลาสที่ไม่ต้องการมันก็ตาม เราควรทำอย่างไรในสถานการณ์เช่นนี้? ตัวอย่างเช่น คุณสามารถแทนที่วิธีการfill()ในชั้นเรียน ได้ ChildrenBuggiesเพื่อว่าเมื่อคุณพยายามเติมน้ำมันรถบักกี้ จะไม่มีอะไรเกิดขึ้น:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
แต่โซลูชันนี้แทบจะเรียกได้ว่าประสบความสำเร็จไม่ได้ อย่างน้อยก็เพราะความซ้ำซ้อนของโค้ด ตัวอย่างเช่น คลาสส่วนใหญ่จะใช้วิธีการจากคลาสแม่ แต่คลาสอื่นจะถูกบังคับให้แทนที่มัน หากเรามี 15 คลาส และใน 5-6 เราถูกบังคับให้แทนที่พฤติกรรม การทำสำเนาโค้ดจะค่อนข้างกว้างขวาง บางทีอินเทอร์เฟซอาจช่วยเราได้ ตัวอย่างเช่นอันนี้:
public interface Fillable {

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

   public void fill();
}
ทำไมเราถึงต้องการอินเทอร์เฟซนี้? มันง่ายมาก ตอนนี้เราสามารถสร้างคลาสต่างๆ ที่จะใช้อินเทอร์เฟซนี้:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
เราได้สร้างกลยุทธ์พฤติกรรมสามประการ - สำหรับรถยนต์ทั่วไป สำหรับรถไฮบริด และสำหรับรถยนต์ Formula 1 แต่ละกลยุทธ์ใช้อัลกอริธึมการเติมเชื้อเพลิงแยกกัน ในกรณีของเรา นี่เป็นเพียงเอาต์พุตไปยังคอนโซล แต่อาจมีตรรกะที่ซับซ้อนอยู่ภายในเมธอด เราควรทำอย่างไรกับเรื่องนี้ต่อไป?
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
เราใช้อินเทอร์เฟซของเราเป็นFillStrategyฟิลด์ในคลาสพาเรนต์ Autoโปรดทราบ: เราไม่ได้ระบุการใช้งานเฉพาะเจาะจง แต่ใช้อินเทอร์เฟซแทน และเราต้องการการใช้งานอินเทอร์เฟซเฉพาะFillStrategyในคลาสรถเด็ก:
public class F1Car extends Auto {

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

public class HybridAuto extends Auto {

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

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
มาดูกันว่าเราได้อะไรบ้าง:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
เอาต์พุตคอนโซล:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
เยี่ยมมาก กระบวนการเติมเชื้อเพลิงทำงานได้ตามปกติ! อย่างไรก็ตาม ไม่มีอะไรขัดขวางเราไม่ให้ใช้กลยุทธ์เป็นพารามิเตอร์ในตัวสร้าง! ตัวอย่างเช่นเช่นนี้:
public class Auto {

   private FillStrategy fillStrategy;

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

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

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



public class HybridAuto extends Auto {

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

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
ลองใช้วิธีการของเราmain()(ยังคงไม่เปลี่ยนแปลง) และรับผลลัพธ์เหมือนเดิม! เอาต์พุตคอนโซล:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
รูปแบบกลยุทธ์จะกำหนดกลุ่มอัลกอริธึม ห่อหุ้มแต่ละอัลกอริธึม และทำให้มั่นใจว่าสามารถใช้แทนกันได้ ช่วยให้คุณสามารถแก้ไขอัลกอริธึมโดยไม่คำนึงถึงการใช้งานบนฝั่งไคลเอ็นต์ (คำจำกัดความนี้นำมาจากหนังสือ “Exploring Design Patterns” และดูเหมือนว่าฉันจะประสบความสำเร็จอย่างมาก) รูปแบบการออกแบบ “กลยุทธ์” - 4เราได้แยกกลุ่มอัลกอริธึมที่เราสนใจ (ประเภทรถเติมน้ำมัน) ออกเป็นอินเทอร์เฟซที่แยกจากกันพร้อมการใช้งานหลายอย่าง เราได้แยกพวกเขาออกจากแก่นแท้ของรถ ดังนั้น ในตอนนี้ หากเราจำเป็นต้องเปลี่ยนแปลงกระบวนการเติมเชื้อเพลิงนี้หรือกระบวนการเติมเชื้อเพลิงนั้น สิ่งนี้จะไม่ส่งผลกระทบต่อประเภทรถยนต์ของเราแต่อย่างใด สำหรับความสามารถในการสับเปลี่ยนกันได้ เพื่อให้บรรลุเป้าหมาย เราเพียงแค่ต้องเพิ่มเมธอด setter หนึ่งตัวในคลาสของเราAuto:
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
ตอนนี้เราสามารถเปลี่ยนกลยุทธ์ได้ทันที:
public class Main {

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

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