สวัสดี! ในการบรรยายครั้งก่อนๆ เราได้พบแนวคิดของ “รูปแบบการออกแบบ” ไปแล้ว ในกรณีที่คุณลืม ให้เราเตือนคุณ: คำนี้หมายถึงวิธีแก้ปัญหามาตรฐานสำหรับปัญหาทั่วไปในการเขียนโปรแกรม ที่ JavaRush เรามักจะพูดว่าคำตอบสำหรับคำถามเกือบทุกข้อสามารถค้นหาใน Google ได้ ดังนั้นอาจมีบางคนแก้ไขปัญหาที่คล้ายกับของคุณสำเร็จแล้ว ดังนั้น รูปแบบจึงเป็นวิธีแก้ปัญหาที่ผ่านการทดสอบตามเวลาและแบบทดสอบแล้วสำหรับปัญหาที่พบบ่อยที่สุดหรือวิธีการในการแก้ไขสถานการณ์ปัญหา เหล่านี้คือ "จักรยาน" ที่แท้จริงซึ่งไม่ว่าในกรณีใดคุณจำเป็นต้องประดิษฐ์ตัวเองขึ้นมา แต่คุณต้องรู้วิธีและเวลาในการนำไปใช้ :) งานของรูปแบบอีกประการหนึ่งคือการนำสถาปัตยกรรมไปสู่มาตรฐานเดียว การอ่านโค้ดของคนอื่นไม่ใช่เรื่องง่าย! แต่ละคนเขียนต่างกัน เพราะปัญหาเดียวกันสามารถแก้ไขได้หลายวิธี แต่การใช้รูปแบบช่วยให้โปรแกรมเมอร์หลายๆ คนเข้าใจตรรกะของโปรแกรมโดยไม่ต้องเจาะลึกโค้ดทุกบรรทัด (แม้ว่าพวกเขาจะเห็นมันเป็นครั้งแรกก็ตาม!) วันนี้เราจะมาดูหนึ่งในรูปแบบที่พบบ่อยที่สุดที่เรียกว่า “กลยุทธ์” สมมติว่าเรากำลังเขียนโปรแกรมที่ทำงานร่วมกับวัตถุ Car อยู่ ในกรณีนี้ ไม่ได้มีความสำคัญเป็นพิเศษว่าโปรแกรมของเราทำอะไรกันแน่ เมื่อต้องการทำเช่นนี้ เราได้สร้างระบบการสืบทอดโดยมีคลาสพาเรนต์หนึ่งคลาส
Auto
และคลาสลูกสามคลาส: Sedan
และTruck
F1Car
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("Заправить бензин!");
}
}
ดูเหมือนว่าปัญหาจะเกิดขึ้นในสถานการณ์ง่ายๆเช่นนี้เหรอ? ที่จริงแล้วพวกมันได้เกิดขึ้นแล้ว...
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()
Auto
fill()
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” และดูเหมือนว่าฉันจะประสบความสำเร็จอย่างมาก) เราได้แยกกลุ่มอัลกอริธึมที่เราสนใจ (ประเภทรถเติมน้ำมัน) ออกเป็นอินเทอร์เฟซที่แยกจากกันพร้อมการใช้งานหลายอย่าง เราได้แยกพวกเขาออกจากแก่นแท้ของรถ ดังนั้น ในตอนนี้ หากเราจำเป็นต้องเปลี่ยนแปลงกระบวนการเติมเชื้อเพลิงนี้หรือกระบวนการเติมเชื้อเพลิงนั้น สิ่งนี้จะไม่ส่งผลกระทบต่อประเภทรถยนต์ของเราแต่อย่างใด สำหรับความสามารถในการสับเปลี่ยนกันได้ เพื่อให้บรรลุเป้าหมาย เราเพียงแค่ต้องเพิ่มเมธอด 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();
}
}
หากทันใดนั้นรถบักกี้สำหรับเด็กเริ่มเติมน้ำมัน โปรแกรมของเราจะพร้อมสำหรับสถานการณ์เช่นนี้ :) แค่นั้นเอง! คุณได้เรียนรู้รูปแบบการออกแบบอื่นซึ่งคุณจะต้องใช้อย่างไม่ต้องสงสัยและจะช่วยคุณมากกว่าหนึ่งครั้งเมื่อทำงานในโครงการจริง :) เจอกันใหม่!
GO TO FULL VERSION