แม้ว่าโปรแกรมเมอร์
รูปแบบการออกแบบ: รูปแบบโครงสร้างของคลาสการออกแบบและวัตถุ
อะแดปเตอร์, มัณฑนากร, พร็อกซี, ผู้เชี่ยวชาญด้านข้อมูล, คอมโพสิต, บริดจ์, คัปปลิ้งต่ำ, ฟลายเวท, รูปแบบที่ได้รับการป้องกัน และซุ้ม
รูปแบบการออกแบบโครงสร้างเกี่ยวข้องกับวิธีการประกอบคลาสและวัตถุเพื่อสร้างโครงสร้างที่ใหญ่ขึ้น สิ่งเหล่านี้ช่วยให้คุณสร้างระบบโดยไม่ต้องเขียนใหม่หรือปรับแต่งโค้ด เนื่องจากรูปแบบเหล่านี้ทำให้ระบบสามารถนำกลับมาใช้ใหม่ได้และฟังก์ชันการทำงานที่แข็งแกร่ง
แต่ละรูปแบบอธิบายถึงปัญหาที่เกิดขึ้นซ้ำแล้วซ้ำอีกในสภาพแวดล้อมของเรา จากนั้นอธิบายแก่นแท้ของการแก้ปัญหานั้น ในลักษณะที่คุณสามารถใช้วิธีแก้ปัญหานี้ได้นับล้านครั้ง โดยไม่ต้องทำแบบเดิมซ้ำสองครั้ง . —คริสโตเฟอร์ อเล็กซานเดอร์
รูปแบบการออกแบบโครงสร้างมี 10 ประเภทดังนี้
- รูปแบบอะแดปเตอร์
- ลวดลายสะพาน
- รูปแบบคอมโพสิต
- แพทเทิร์นมัณฑนากร
- คลัปต่ำ
- รูปแบบฟลายเวท
- รูปแบบซุ้ม
- รูปแบบพร็อกซี
- ผู้เชี่ยวชาญด้านข้อมูล
- รูปแบบที่ได้รับการคุ้มครอง
ABCD (อะแดปเตอร์, บริดจ์, คอมโพสิต, มัณฑนากร)
รูปแบบอะแดปเตอร์
เจตนา
รูปแบบอะแดปเตอร์คือรูปแบบการออกแบบเชิงโครงสร้างที่ช่วยให้วัตถุที่มีอินเทอร์เฟซ เข้ากันไม่ได้ สามารถทำงานร่วมกันได้
วิธีแก้ปัญหา
ใช้อินเทอร์เฟซที่ไคลเอ็นต์รู้จักและให้สิทธิ์เข้าถึงอินสแตนซ์ของคลาสที่ไคลเอ็นต์ไม่รู้จัก
- AdapterClient: ไคลเอ็นต์โค้ด
- อะแดปเตอร์: คลาสอะแดปเตอร์ที่ส่งต่อการเรียกไปยังอะแดปเตอร์
- Adaptee: จำเป็นต้องดัดแปลงโค้ดเก่า
- เป้าหมาย: อินเทอร์เฟซใหม่ที่จะสนับสนุน
ตัวอย่างจากโลกแห่งความเป็นจริง
กำลังไฟเดิมคือ 220 แรงดันไฟฟ้า และต้องปรับเป็น 100 แรงดันไฟฟ้าจึงจะใช้งานได้
รหัสต่อไปนี้ช่วยแก้ปัญหานี้ได้ โดยกำหนด HighVoltagePlug
(อะแดปเตอร์), อินเทอร์เฟซ Plug
(เป้าหมาย), AdapterPlug
(อะแดปเตอร์)
เป้าหมาย: Plug.java
public interface Plug { public int recharge(); }
อะแดปเตอร์: HighVoltagePlug.java
public class HighVoltagePlug{ public int recharge() { //Power is 220 Voltage return 220; } }
อะแดปเตอร์: AdapterPlug.java
public class AdapterPlug implements Plug { @Override public int recharge() { HighVoltagePlug bigplug = new HighVoltagePlug(); int v = bigplug.recharge(); v = v - 120; return v; } }
AdapterClient: AdapterClient.java
public class AdapterClient { public static void main(String[] args) { HighVoltagePlug oldPlug = new HighVoltagePlug(); System.out.println(plug.recharge() + " too much voltage"); Plug newPlug = new AdapterPlug(); System.out.println("Adapter into " + plug.recharge() + " voltage"); } }
กรณีการใช้งาน
เมื่อคุณต้องการใช้คลาสที่มีอยู่และอินเทอร์เฟซไม่ตรงกับอินเทอร์เฟซที่คุณต้องการ
เมื่อคุณต้องการสร้างคลาสที่นำมาใช้ซ้ำได้ซึ่งทำงานร่วมกับ
คลาสที่ไม่เกี่ยวข้องหรือคลาสที่ไม่คาดฝันซึ่งเป็นคลาสที่ไม่จำเป็นต้องมีอินเทอร์เฟซที่เข้ากันได้
โดยที่ต้องมีการแปลอินเทอร์เฟซระหว่างหลายแหล่ง
ลวดลายสะพาน
เจตนา
โดยแบ่งองค์ประกอบที่ซับซ้อนออกเป็นสองลำดับชั้นที่แยกจากกันแต่เกี่ยวข้องกัน: นามธรรม เชิงฟังก์ชัน และ การใช้งาน ภายใน
วิธีแก้ปัญหา
แผนภาพต่อไปนี้แสดงการใช้งานบริดจ์ที่เป็นไปได้
- นามธรรม: นี่คือองค์ประกอบที่เป็นนามธรรม
- ตัวดำเนินการ: นี่คือการนำไปใช้เชิงนามธรรม
- RefinedAbstraction: นี่คือองค์ประกอบที่ได้รับการปรับปรุง
- ConcreateImplementors: สิ่งเหล่านี้คือการใช้งานที่เป็นรูปธรรม
ตัวอย่างจากโลกแห่งความเป็นจริง
ผู้คนต่างสามารถสวมใส่เสื้อผ้าที่แตกต่างกัน เช่น ผู้ชาย ผู้หญิง เด็กผู้ชาย และเด็กผู้หญิง
AbstractImpl: Person.java
public abstract class Person { protected String name; protected Clothing cloth; public Person(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Clothing getCloth() { return cloth; } public void setCloth(Clothing cloth) { this.cloth = cloth; } }
ผู้ดำเนินการ: Clothing.java
public abstract class Clothing { protected String name; public Clothing(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
ConcreateImplementor: Jacket.java
public class Jacket extends Clothing { public Jacket(String name) { super(name); } }
RefinedAbstraction: Woman.java
public class Woman extends Person { public Woman(String name) { super(name); } @Override public void dress() { System.out.println(name + " wear " + cloth.getName()); } }
ลูกค้า: BridgeClient.java
public class BridgeClient { public static void main(String[] args) { Person woman = new Woman("Woman"); Clothing jacket = new Jacket("Jacket"); // a woman wear jacket woman.setCloth(jacket); woman.dress(); } }
กรณีการใช้งาน
หลีกเลี่ยงการผูกมัดอย่างถาวรระหว่างสิ่งที่เป็นนามธรรมและการนำไปปฏิบัติ
ทั้งนามธรรมและการใช้งานควรขยายได้โดยใช้คลาสย่อย
การเปลี่ยนแปลงในการใช้งานนามธรรมไม่ควรมีผลกระทบต่อลูกค้า นั่นคือคุณไม่ควรต้องคอมไพล์โค้ดใหม่
รูปแบบคอมโพสิต
เจตนา
รูปแบบคอมโพสิตช่วยให้คุณสร้างโครงสร้างต้นไม้แบบลำดับชั้นที่มีความซับซ้อนต่างกันไป ในขณะเดียวกันก็อนุญาตให้ทุกองค์ประกอบในโครงสร้างทำงานด้วยอินเทอร์เฟซที่เหมือนกัน
วิธีแก้ปัญหา
รูปแบบคอมโพสิตจะรวมวัตถุเข้ากับโครงสร้างต้นไม้เพื่อแสดงลำดับชั้นทั้งหมดหรือเป็นส่วนหนึ่งของลำดับชั้น
- ส่วนประกอบคือสิ่งที่เป็นนามธรรมสำหรับใบไม้และวัสดุผสม มันกำหนดอินเทอร์เฟซที่วัตถุในองค์ประกอบจะต้องนำไปใช้
- ใบไม้ คือวัตถุที่ไม่มีลูก พวกเขาใช้บริการที่อธิบายโดยอินเทอร์เฟซคอมโพเนนต์
- คอมโพสิต จัดเก็บส่วนประกอบย่อยนอกเหนือจากการใช้วิธีการที่กำหนดโดยอินเทอร์เฟซส่วนประกอบ คอมโพสิตใช้วิธีการที่กำหนดไว้ในอินเทอร์เฟซคอมโพเนนต์โดยการมอบหมายให้กับคอมโพเนนต์ลูก นอกจากนี้ คอมโพสิตยังให้วิธีการเพิ่มเติมในการเพิ่ม การถอด และการได้มาซึ่งส่วนประกอบต่างๆ
- ไคลเอนต์ จัดการออบเจ็กต์ในลำดับชั้นโดยใช้อินเทอร์เฟซส่วนประกอบ
ตัวอย่างจากโลกแห่งความเป็นจริง
ในองค์กร มีผู้จัดการทั่วไป และภายใต้ผู้จัดการทั่วไปอาจมีผู้จัดการ และภายใต้ผู้จัดการก็สามารถเป็นนักพัฒนาได้ ตอนนี้คุณสามารถตั้งค่าโครงสร้างแบบต้นไม้และขอให้แต่ละโหนดดำเนินการทั่วไป เช่น printStructures()
ส่วนประกอบ: IEmployee.java
interface IEmployee { void printStructures(); int getEmployeeCount(); }
คอมโพสิต: CompositeEmployee.java
class CompositeEmployee implements IEmployee { private int employeeCount=0; private String name; private String dept; //The container for child objects private List<IEmployee> controls; public CompositeEmployee(String name, String dept){ this.name = name; this.dept = dept; controls = new ArrayList<IEmployee>(); } public void addEmployee(IEmployee e){ controls.add(e); } public void removeEmployee(IEmployee e){ controls.remove(e); } @Override public void printStructures(){ System.out.println("\t" + this.name + " works in " + this.dept); for(IEmployee e: controls){ e.printStructures(); } } @Override public int getEmployeeCount(){ employeeCount=controls.size(); for(IEmployee e: controls){ employeeCount+=e.getEmployeeCount(); } return employeeCount; } }
ใบไม้: Employee.java
class Employee implements IEmployee{ private String name; private String dept; private int employeeCount=0; public Employee(String name, String dept){ this.name = name; his.dept = dept; } @Override public void printStructures(){ System.out.println("\t\t"+this.name + " works in " + this.dept); } @Override public int getEmployeeCount(){ return employeeCount; } }
กรณีการใช้งาน
คุณต้องการแสดงลำดับชั้นทั้งหมดหรือส่วนหนึ่งของลำดับชั้นของออบเจ็กต์ โดยที่ลูกค้าสามารถละเลยความแตกต่างระหว่างองค์ประกอบของวัตถุและวัตถุแต่ละชิ้นได้
คุณสามารถใช้รูปแบบนี้กับโครงสร้างที่สามารถมีความซับซ้อนในระดับใดก็ได้
แพทเทิร์นมัณฑนากร
เจตนา
รูปแบบมัณฑนากรช่วยให้คุณสามารถเพิ่มหรือลบฟังก์ชันการทำงานของออบเจ็กต์ได้โดยไม่ต้องเปลี่ยนรูปลักษณ์ภายนอกหรือฟังก์ชันของออบเจ็กต์
วิธีแก้ปัญหา
มันเปลี่ยนการทำงานของออบเจ็กต์ในลักษณะที่โปร่งใสต่อไคลเอนต์โดยใช้อินสแตนซ์ของคลาสย่อยของคลาสดั้งเดิมที่มอบหมายการดำเนินการให้กับออบเจ็กต์ดั้งเดิม
- ส่วนประกอบคืออินเทอร์เฟซสำหรับออบเจ็กต์ที่สามารถเพิ่มความรับผิดชอบแบบไดนามิกได้
- ConcreteComponent กำหนดวัตถุที่สามารถเพิ่มความรับผิดชอบเพิ่มเติมได้
- มัณฑนากร รักษาการอ้างอิงไปยังวัตถุคอมโพเนนต์และกำหนดอินเทอร์เฟซที่สอดคล้องกับอินเทอร์เฟซของคอมโพเนนต์
- ConcreteDecorators ขยายการทำงานของส่วนประกอบโดยการเพิ่มสถานะหรือการเพิ่มลักษณะการทำงาน
ตัวอย่างจากโลกแห่งความเป็นจริง
คุณเป็นเจ้าของบ้านแล้ว ตอนนี้คุณได้ตัดสินใจสร้างพื้นเพิ่มเติมด้านบนแล้ว คุณอาจต้องการเปลี่ยนการออกแบบสถาปัตยกรรมสำหรับพื้นเพิ่มใหม่โดยไม่กระทบต่อสถาปัตยกรรมที่มีอยู่ เช่น อย่าเปลี่ยนสถาปัตยกรรมชั้นล่าง (หรือพื้นที่มีอยู่)
กรณีการใช้งาน
เมื่อเพิ่มความรับผิดชอบให้กับแต่ละวัตถุแบบไดนามิกและโปร่งใสโดยไม่ส่งผลกระทบต่อวัตถุอื่น
เมื่อคุณต้องการเพิ่มความรับผิดชอบให้กับวัตถุที่คุณอาจต้องการเปลี่ยนแปลงในอนาคต
โดยที่การขยายโดยคลาสย่อยแบบคงที่นั้นทำไม่ได้
F2P (ฟลายเวท, เฟซาด, พร็อกซี)
รูปแบบฟลายเวท
เจตนา
รูปแบบฟลายเวทช่วยลดจำนวนวัตถุที่มีรายละเอียดระดับต่ำภายในระบบด้วยการแบ่งปันวัตถุ
วิธีแก้ปัญหา
แผนภาพต่อไปนี้แสดงให้เห็นว่าวัตถุฟลายเวทถูกส่งคืนจากพูล และเพื่อให้ทำงานได้ จำเป็นต้องส่งสถานะภายนอกเป็นอาร์กิวเมนต์
- ลูกค้า: รหัสลูกค้า
- FlyweightFactory: สิ่งนี้จะสร้างฟลายเวทหากไม่มีอยู่ และส่งคืนจากพูลหากมีอยู่
- ฟลายเวท: นามธรรมฟลายเวท
- ConcreateFlyweight: รุ่นฟลายเวตที่ออกแบบมาให้มีสถานะที่ใช้ร่วมกันกับคู่แข่ง
ตัวอย่างจากโลกแห่งความเป็นจริง
ตัวอย่างคลาสสิกของการใช้งานนี้อยู่ในโปรแกรมประมวลผลคำ ในที่นี้ ตัวละครแต่ละตัวเป็นวัตถุรุ่นฟลายเวทซึ่งแชร์ข้อมูลที่จำเป็นสำหรับการเรนเดอร์ เป็นผลให้เฉพาะตำแหน่งของอักขระภายในเอกสารเท่านั้นที่ใช้หน่วยความจำเพิ่มเติม
กรณีการใช้งาน
คุณควรใช้รูปแบบฟลายเวทเมื่อเงื่อนไขทั้งหมดต่อไปนี้เป็นจริง
- แอปพลิเคชันใช้วัตถุจำนวนมาก
- ต้นทุนการจัดเก็บสูงเนื่องจากปริมาณของวัตถุ
- แอปพลิเคชันไม่ขึ้นอยู่กับเอกลักษณ์ของวัตถุ
รูปแบบซุ้ม
เจตนา
รูปแบบ Façade จัดเตรียมอินเทอร์เฟซแบบรวมให้กับกลุ่มของอินเทอร์เฟซในระบบย่อย
วิธีแก้ปัญหา
มันกำหนดอินเทอร์เฟซระดับสูงกว่าที่ทำให้ระบบย่อยใช้งานง่ายขึ้นเนื่องจากคุณมีเพียงอินเทอร์เฟซเดียวเท่านั้น
ตัวอย่างจากโลกแห่งความเป็นจริง
Facade กำหนดอินเทอร์เฟซระดับที่สูงกว่าแบบครบวงจรให้กับระบบย่อยที่ทำให้ใช้งานได้ง่ายขึ้น ผู้บริโภคจะพบกับ Facade เมื่อสั่งซื้อจากแค็ตตาล็อก ผู้บริโภคโทรไปที่หมายเลขหนึ่งและพูดคุยกับตัวแทนฝ่ายบริการลูกค้า ตัวแทนฝ่ายบริการลูกค้าทำหน้าที่เป็นส่วนหน้า ทำหน้าที่เชื่อมต่อกับแผนกปฏิบัติตามคำสั่งซื้อ แผนกเรียกเก็บเงิน และแผนกจัดส่ง
กรณีการใช้งาน
เมื่อคุณต้องการจัดเตรียมอินเทอร์เฟซที่เรียบง่ายให้กับระบบย่อยที่ซับซ้อน
ในกรณีที่มีการขึ้นต่อกันมากมายระหว่างไคลเอนต์และคลาสการใช้งานของสิ่งที่เป็นนามธรรม
เมื่อคุณต้องการเลเยอร์ระบบย่อยของคุณ
รูปแบบพร็อกซี
รูปแบบพร็อกซีมีการใช้งานหลายประเภท โดยที่พร็อกซีระยะไกลและพร็อกซีเสมือนเป็นประเภทที่พบบ่อยที่สุด
เจตนา
รูปแบบพร็อกซีจัดเตรียมวัตถุตัวแทนหรือตัวยึดเพื่อควบคุมการเข้าถึงวัตถุต้นฉบับ
วิธีแก้ปัญหา
ตัวอย่างจากโลกแห่งความเป็นจริง
ตัวอย่างในโลกแห่งความเป็นจริงอาจเป็นเช็คหรือบัตรเครดิตที่เป็นตัวแทนสำหรับสิ่งที่อยู่ในบัญชีธนาคารของเรา สามารถใช้แทนเงินสดและให้วิธีการเข้าถึงเงินสดนั้นเมื่อจำเป็น และนั่นคือสิ่งที่รูปแบบ Proxy ทำ — “ควบคุมและจัดการการเข้าถึงอ็อบเจ็กต์ที่พวกเขากำลังปกป้อง“
กรณีการใช้งาน
คุณต้องมีการอ้างอิงวัตถุที่หลากหลายหรือซับซ้อนมากกว่าตัวชี้แบบธรรมดา
รูปแบบของ GRASP
GRASP ตั้งชื่อและอธิบายหลักการพื้นฐานในการมอบหมายความรับผิดชอบ
ผู้เชี่ยวชาญด้านข้อมูล
เราดูที่รูปแบบผู้เชี่ยวชาญ (หรือรูปแบบผู้เชี่ยวชาญด้านข้อมูล) อันนี้ค่อนข้างเรียบง่ายและสำคัญมาก
เจตนา
หลักการพื้นฐานในการกำหนดความรับผิดชอบให้กับวัตถุคืออะไร?
วิธีแก้ปัญหา
มอบหมายความรับผิดชอบให้ชั้นเรียนซึ่งมีข้อมูลที่จำเป็นต่อการตอบสนอง
ตัวอย่างจากโลกแห่งความเป็นจริง
พิจารณาเกมผูกขาด สมมติว่าวัตถุต้องการอ้างอิง Square โดยใช้ชื่อของมัน ใครเป็นผู้รับผิดชอบในการรู้จักจัตุรัสตามชื่อของมัน?
ผู้สมัครที่มีแนวโน้มมากที่สุดคือคณะกรรมการเนื่องจากประกอบด้วยสี่เหลี่ยมจัตุรัส
เนื่องจากกระดานประกอบด้วยสี่เหลี่ยมจัตุรัส จึงเป็นวัตถุที่เหมาะสมที่สุดในการสร้างสี่เหลี่ยมจัตุรัสโดยเฉพาะตามชื่อของจัตุรัส คณะกรรมการคือผู้เชี่ยวชาญด้านข้อมูล จึงมีข้อมูลทั้งหมดที่จำเป็นในการตอบสนองความรับผิดชอบนี้
กรณีการใช้งาน
คิดว่าออบเจ็กต์ในแบบจำลองการออกแบบของคุณเป็นผู้ปฏิบัติงานที่คุณจัดการ หากคุณมีงานที่ต้องมอบหมาย คุณจะมอบหมายงานให้ใคร?
- คุณมอบให้กับบุคคลที่มีความรู้ดีที่สุดในการทำงาน
- ในบางครั้งความรู้ในการทำงานจะกระจายไปยังวัตถุต่างๆ
โต้ตอบผ่านข้อความต่างๆ ในการทำงาน แต่โดยปกติแล้วจะมีวัตถุเดียวที่รับผิดชอบในการทำงานให้เสร็จสิ้น
รูปแบบที่ได้รับการคุ้มครอง
เจตนา
จะออกแบบออบเจ็กต์ ระบบย่อย และระบบอย่างไรเพื่อให้ความแปรผันหรือความไม่เสถียรในองค์ประกอบเหล่านี้ไม่ส่งผลกระทบที่ไม่พึงประสงค์ต่อองค์ประกอบอื่นๆ
วิธีแก้ปัญหา
ระบุจุดของการแปรผันหรือความไม่แน่นอนที่คาดการณ์ไว้ มอบหมายความรับผิดชอบเพื่อสร้างส่วนต่อประสานที่มีความเสถียรรอบตัว
หลักการ “อย่าพูดคุยกับคนแปลกหน้า” ซึ่งระบุว่าวิธีการของวัตถุควรส่งข้อความ (เช่น การใช้วิธีการ) ของวัตถุที่คุ้นเคยโดยตรงเท่านั้น
ตัวอย่างจากโลกแห่งความเป็นจริง
การห่อหุ้มข้อมูล อินเทอร์เฟซ ความหลากหลาย ทางอ้อม และมาตรฐานได้รับแรงบันดาลใจจากความแปรผันที่ได้รับการคุ้มครอง
กรณีการใช้งาน
Protected Variations เป็นหลักการพื้นฐานที่กระตุ้นให้เกิดกลไกและรูปแบบส่วนใหญ่ในการเขียนโปรแกรมและการออกแบบ เพื่อให้มีความยืดหยุ่นและการป้องกันจากการเปลี่ยนแปลงของข้อมูล พฤติกรรม ฮาร์ดแวร์ ส่วนประกอบซอฟต์แวร์ ระบบปฏิบัติการ และอื่นๆ
บทสรุป
รูปแบบโครงสร้างส่งผลต่อแอปพลิเคชันในหลากหลายวิธี เช่น รูปแบบอะแดปเตอร์ทำให้ระบบที่เข้ากันไม่ได้สองระบบสามารถสื่อสารได้ ในขณะที่รูปแบบ Facade ช่วยให้คุณสามารถนำเสนออินเทอร์เฟซที่เรียบง่ายให้กับผู้ใช้โดยไม่ต้องลบตัวเลือกทั้งหมดที่มีอยู่ในระบบ
ง่ายใช่มั้ย?