เฮ้เพื่อนเครื่องบดแพทเทิร์น! 😮 ยินดีต้อนรับสู่ซีรีส์ Design Patterns! เป็นชุดบทความที่ฉันเขียนเกี่ยวกับรูปแบบเพื่อกระจายความมัน🙌
ฉันขอแนะนำให้คุณอ่านบทความก่อนหน้านี้ซึ่งคุณสามารถเลือกความรู้มากมาย:
- รูปแบบกลยุทธ์: https://medium.com/towardsdev/strategy-pattern-for-independent-algorithms-kotlin-70ed24c7bd8b
- รูปแบบผู้สังเกตการณ์: https://medium.com/towardsdev/observer-pattern-for-loose-coupling-kotlin-f5ab804609bb
- รูปแบบมัณฑนากร: https://medium.com/towardsdev/decorator-decorator-pattern-for-object-composition-kotlin-7cec92cbaf7b
- รูปแบบโรงงาน: https://medium.com/dev-genius/factory-patterns-to-hide-instantiation-kotlin-d5f01cf01921
- รูปแบบซิงเกิลตัน: https://medium.com/dev-genius/singleton-pattern-for-one-of-a-kind-objects-java-a63c774d9d4
- รูปแบบคำสั่ง: https://medium.com/dev-genius/command-pattern-for-encapsulated-invocation-kotlin-4338eb23d2ca
จากนั้น ตามปกติ สนับสนุน O'Reilly Media เนื่องจากหนังสือของพวกเขางดงามมาก: https://www.oreilly.com/library/view/head-first-design/9781492077992/
โครงสร้าง:
- บทนำ
- ปัญหา: อะแดปเตอร์
- ภาคพิเศษที่หนึ่ง
- ปัญหา: ด้านหน้า
- หลักการออกแบบที่ต้องปฏิบัติตาม
- ภาคพิเศษที่สอง
- รหัสโซลูชันสุดท้าย: ทั้งสองรูปแบบ
- การวาดภาพ
❗️ตอนแรกผมให้หมดเวลาไปที่ Adapter แล้วจึงเปลี่ยนไปใช้ Facade ซึ่งจะใช้เวลาน้อยลง ไปกันเลย☄️
รูปแบบอะแดปเตอร์และซุ้ม
อะแดปเตอร์
บทนำ
รูปแบบเป็นตัวเลือกที่ดีในการดำดิ่งสู่โลกแห่ง OOP อย่างน้อยสำหรับฉันมันเป็นประตูวิเศษที่เปิดออกซึ่งช่วยให้ฉันเข้าใจ OOP ไม่ใช่แค่คลาส มรดก องค์ประกอบ แต่เพื่อภายใน SOLID😱 จากพื้นฐาน ดูวิธีผสมระบบที่ซับซ้อน และอื่นๆ🧑🏼💻
และถึงแม้ว่าคุณจะไม่จำทุกรูปแบบในโลกนี้ (และคุณไม่ควรจำ) รูปแบบเหล่านั้นคือกระสุนเงินที่เข้าใจได้ ที่จะปูทางในใจของคุณไปสู่โครงสร้างที่ได้รับการปรับปรุงให้เหมาะสมยิ่งขึ้น ซอฟต์แวร์ที่ดีกว่า และผลที่ตามมา คุณเป็น SWE ชั้นนำ
ปัญหา: อะแดปเตอร์
ลองนึกภาพคุณมีลูกค้าที่รู้วิธีโต้ตอบกับชั้นเรียนบางชั้นเรียน
ตัวอย่างพื้นฐาน: ปลั๊ก AC -> ซ็อกเก็ต จากนั้นคุณย้ายไปประเทศอื่น🎌 และตอนนี้คุณใช้โครงสร้างต่อไปนี้: ปลั๊ก AC -› อะแดปเตอร์ไฟ AC -› ซ็อกเก็ต
อะแดปเตอร์ในการเขียนโปรแกรมทำงานในลักษณะเดียวกัน: ใช้ คลาสไคลเอนต์ รวมเข้ากับอินเทอร์เฟซที่รู้จักใน คลาสสุดท้าย (ลองจินตนาการว่าเป็น ผู้ขาย ของเรา ).
โปรดให้ความสนใจอย่างยิ่งกับโค้ดด้านล่าง ตอนแรกฉันคิดว่ารูปแบบนั้นง่ายมาก แต่ติดอยู่ในห้วงแห่งความเข้าใจผิดและเสียเวลาหลายชั่วโมง
ในตอนแรก ให้สังเกตตัวอย่างพื้นฐานของอะแดปเตอร์ จากนั้นฉันจะทำการสรุป
- เรามี 2 อินเทอร์เฟซ:
Duck
และTurkey
- แต่ละคลาสมีการนำไปใช้งานของตัวเอง
- ลองนึกภาพ ลูกค้า รู้วิธีโต้ตอบกับอินเทอร์เฟซประเภท Duck แต่เรามีคลาสที่ใช้อินเทอร์เฟซ Turkey (ฉันเขียน
MallardDuck
เป็นตัวอย่าง แต่ลูกค้า ไม่รู้อะไรเกี่ยวกับเรื่องนี้) - ใน Adapter เราใช้อินเทอร์เฟซที่เรา ‼️ปรับให้เข้ากับ‼️ ซึ่งในกรณีของเราคือ
Duck
- ใส่วิธีการของ Adapt-to-Interface และใส่วิธีการของคลาสจริงที่ใช้ (PS: ระบุอินสแตนซ์ของคลาสที่จะใช้ประโยชน์จากวิธีการเหล่านั้นภายใน) ซึ่งในโค้ดด้านบนคือ :
.gobble()
&turkeyFly()
ฉันเริ่มต้นด้วยตัวอย่างโค้ดโดยเฉพาะ นอกเหนือจากบทความก่อนหน้านี้ หวังว่าบันทึกนี้จะทำให้การพูดน้อยของคุณราบรื่นขึ้น🙉
เอาล่ะ ผมขอใช้คำศัพท์ที่เกี่ยวข้องกับรูปแบบนี้สักหน่อยเพื่อให้เหมือนเป็นวิชาการมากขึ้น👨🏼🎓
- ลูกค้าถูกนำไปใช้กับ อินเทอร์เฟซเป้าหมาย โดยที่ เป้าหมาย คืออินเทอร์เฟซใดที่ลูกค้าทราบวิธีโต้ตอบกับ
- อะแดปเตอร์ ใช้ อินเทอร์เฟซเป้าหมาย เพื่อกำหนดเส้นทางคำขอของไคลเอ็นต์ ในตัวอย่างด้านบน
TurkeyAdapter
ใช้ อินเทอร์เฟซเป้าหมาย ซึ่งก็คือDuck
- อินสแตนซ์ที่ใช้ภายในอะแดปเตอร์คือ อินเทอร์เฟซของอะแดปเตอร์ (เช่น
CoolTurkey
ของเราที่ให้ไว้กับอินเทอร์เฟซ อะแดปเตอร์ ใช้Turkey
อินเทอร์เฟซ) - ลูกค้าได้รับการตอบรับอย่างมีความสุข และไม่เคยรู้เลยว่ามีมนต์ดำอยู่หลังประตู!🦹♂️
ที่จริงแล้วมีข้อแม้บางประการที่ฉันจะพูดคุยเพิ่มเติมในบทความ
- อะแดปเตอร์ แยก ไคลเอ็นต์จากโค้ดสุดท้าย (อินเทอร์เฟซที่นำไปใช้) ดังนั้น รหัสไคลเอ็นต์และรหัสสุดท้ายของเราจึงไม่จำเป็นต้องเปลี่ยนหากบางส่วนอาจมีการเปลี่ยนแปลง (และในหลายๆ ครั้งเราทำไม่ได้)
- ให้ความสนใจกับวิธีที่ อะแดปเตอร์ แมปคำขอของลูกค้าไม่ใช่คลาสที่เป็นรูปธรรม แต่เป็นอินเทอร์เฟซ ซึ่งหมายความว่าเราสามารถเขียน อะแดปเตอร์ หลายรายการเพื่อแปลงชุดคลาสต่างๆ
ภาคพิเศษที่หนึ่ง
- อะแดปเตอร์มี 2 ประเภทพูดตามตรง ตัวอย่างข้างต้นคือ Object Adapter ตามรูปแบบบัญญัติที่มี Single Inheritance และ Composition และเป็นสิ่งที่ดี (แถมเราไม่มี Multiple Inheritance ใน Java & Kotlin) อย่างไรก็ตาม มีประเภทที่สอง: Class Adapter ซึ่งใช้ การสืบทอดหลายรายการ & ไม่มีองค์ประกอบ ฉันเขียนโค้ดใน Python เพื่อแสดงให้คุณเห็นว่ามันมีลักษณะอย่างไร:
ดูสิ เรามีคลาสนามธรรม 2 คลาส (ใน Python เราไม่มีอินเทอร์เฟซที่แท้จริง) จากนั้นเราก็มี 2 คลาสที่เป็นรูปธรรม สุดท้าย อะแดปเตอร์ รับคำขอจากลูกค้าที่รู้วิธีพูดกับซ็อกเก็ตประเภท US =>
โดยให้ abstract class
ของซ็อกเก็ต US เป็นออบเจ็กต์แรกใน Inheritance และนำเมธอดไปใช้ ออบเจ็กต์ที่สองในการสืบทอดคือ คลาสที่เป็นรูปธรรม ซึ่งเราจะเปลี่ยนเส้นทางไป ในกรณีของเราคือ EuSocket
อะแดปเตอร์ตัวไหนดีกว่ากัน? ไม่รู้สิ🤔 และแม้แต่ O'Reilly Media ก็ไม่ได้อ้างสิทธิ์ผู้ชนะในหนังสือ แต่ละข้อมีข้อดีและข้อเสียที่เกี่ยวข้องกับแนวทางการจัดองค์ประกอบและการสืบทอด
2. จะเกิดอะไรขึ้นถ้าเราไม่มีวิธีการบางอย่างในโค้ดที่ใช้? (ฉันหมายถึง รหัส Adaptee) ตัวอย่างเช่น ลูกค้า/โค้ดของเรารู้วิธีโต้ตอบกับ ตัววนซ้ำ แต่ในโค้ดสุดท้ายเราติดอยู่กับ ตัวแจงนับ
* หมายเหตุด้านข้าง: ตัวเก่าจะใหม่กว่า อย่างหลัง
แล้วเราจะเลี่ยงได้ไหม? น่าเสียดายที่ได้รับความช่วยเหลือจากข้อยกเว้นเท่านั้น สำหรับกรณีดังกล่าว ให้เขียนเอกสาร 🙋♂️
ตัวอย่างโค้ด O'Reilly Media:
ดูสิ วัตถุของเราคือ enumerator
แต่วิธีการเปิดเผยนั้นเป็นของ ตัววนซ้ำ
3. อะแดปเตอร์ อาจสับสนกับ มัณฑนากร แต่อย่างแรกจะล้อมอ็อบเจ็กต์ไว้บนอินเทอร์เฟซที่มีอยู่ ในขณะที่อันหลังเพิ่มความรับผิดชอบ หากต้องการเรียนรู้เพิ่มเติม โปรดอ่านบทความของฉันเกี่ยวกับ รูปแบบมัณฑนากร: https://medium.com/towardsdev/decorator-decorator-pattern-for-object-composition-kotlin-7cec92cbaf7b
ปัญหา: ด้านหน้า
จากมุมมองของฉัน รูปแบบนี้ใกล้เคียงกับสิ่งที่เราเขียนในชีวิตประจำวันมากขึ้น✍🏻
ลองนึกภาพคุณมีชั้นเรียนจำนวนมากที่คุณต้องโทรตามลำดับ ยิ่งไปกว่านั้น คุณมี อินเทอร์เฟซ และถูกนำไปใช้โดยคลาสบางคลาส เช่นเดียวกับหลายสิบสิ่ง... 👀 ทำไมไม่จัดเตรียมอินสแตนซ์ของอินเทอร์เฟซที่นำไปใช้ในคลาสเดียวที่ครอบคลุมซึ่งจะเปิดเผยวิธีการที่อินสแตนซ์เหล่านั้นเรียกวิธีการของพวกเขา?🧐
Facade class * instance_one = newA() * instance_two = newB() methodA() instance_one.callA() instance_two.callAA() methodB() instance_one.callB() instance_two.callBB() interface A interface B | | class newA implements A class newB implements B * callA() * callAA() * callB() * callBB()
ฉันหวังว่าคุณจะมีไดอะแกรมคร่าวๆ นี้ (ฉันจะทำให้โค้ดหมดลงในส่วนที่กำหนด)
- หากคุณต้องการเข้าถึงวิธีการของส่วนประกอบระดับต่ำเหล่านั้น -› คุณสามารถใช้งานได้ฟรี
- เรา แยก ลูกค้า ที่ส่งคำขอผ่าน Facade ด้วย ระบบ จะเกิดอะไรขึ้นหากอินเทอร์เฟซมีการเปลี่ยนแปลงและการใช้งานคลาสในมือ❓❓ไม่จำเป็นต้องเปลี่ยนไคลเอนต์ของเรา (และบ่อยครั้งเราทำไม่ได้) แต่ทำ Facade อีกครั้งแล้วคุณก็โฉบเฉี่ยว!💯
Facade ไม่เพียงแต่ทำให้อินเทอร์เฟซง่ายขึ้น แต่ยัง แยก ไคลเอนต์จาก Final System
หลักการออกแบบที่ต้องปฏิบัติตาม
- หลักการของความรู้น้อยที่สุด (กฎแห่ง Demeter): พูดคุยกับเพื่อนของคุณเท่านั้น🌝
โดยสรุป หมายความว่าเราต้องตระหนักดีถึงคลาสที่เราใช้ในขณะพัฒนาระบบ เช่น. อย่าสุ่มสี่สุ่มห้าเรียกวิธีการบางอย่างจากคลาสอื่นเพราะมันจะส่งผลย้อนกลับในข้อบกพร่อง🪲
ตัวอย่าง การเรียกเมธอดที่ไม่ถูกต้อง:
เราเรียกวิธีการบนอินสแตนซ์ที่ส่งคืนจากวิธีอื่น:
getThermometer()
ส่งคืนอินสแตนซ์- เราโทร
getTemperature()
ในกรณีจาก1
ข้อแนะนำในการเรียกวิธีการ:
- จากชั้นเรียนเดียวกัน
- จากองค์ประกอบของคลาสปัจจุบัน
- จากพารามิเตอร์ (อาจเป็นวัตถุ) ที่ส่งผ่านไปยังวิธีการ
- จากอ็อบเจ็กต์ที่สร้าง/สร้างอินสแตนซ์ในเมธอด
‼️อันตราย:‼ ️ อย่าเรียกใช้เมธอดบนอ็อบเจ็กต์ที่ส่งคืนจากวิธีอื่นให้เรียกภายในเมธอด มันเพิ่มจำนวนเพื่อนที่เกี่ยวข้องกับวิธีการ/คลาสซึ่งไม่ดีในกรณีของเรา (ดูตัวอย่างด้านบนสำหรับกรณีดังกล่าว)
ฉันจะแสดงให้คุณเห็นว่าทุกอย่างทำงานอย่างไร:
- คลาสหลักคือ
Car
โดยที่เราทริกเกอร์เมธอดstart()
- เรามาดูรายละเอียดวิธีการเรียกเมธอดในเมธอด:
* บรรทัด 30: ใช้เมธอดบน พารามิเตอร์ ที่ระบุให้กับเมธอด
* บรรทัด 32: ใช้เมธอดบนส่วนประกอบของคลาส , เครื่องยนต์
* บรรทัด 33: ใช้วิธีการของคลาสปัจจุบันCar
* บรรทัด 34: ใช้วิธีการของคลาสที่สร้าง/สร้างอินสแตนซ์ในวิธีการ
สุดท้าย ฉันจะแสดงให้คุณเห็นว่าคุณสามารถแก้ไขการเรียกเมธอดจากด้านบนได้อย่างไร (ตัวอย่างที่ไม่ดี)🎉:
- เราเรียก
giveHouseTemperature()
แต่เราไม่สามารถเรียกวิธีการสุ่มสี่สุ่มห้าบนcurrentStation
แล้วจึงเรียกทันทีบนthermometer
ดังนั้นเราจึง แยก การร้องขอ - ขั้นแรก ให้ใช้เทอร์โมมิเตอร์จาก
currentStation
: ใช้วิธีการกับ ส่วนประกอบของคลาส ปัจจุบัน - จากนั้นเรียก วิธีการภายในคลาสของเรา โดยที่เราจัดเตรียมอินสแตนซ์ เทอร์โมมิเตอร์
giveTemperature()
ถูกเรียกใช้บนพารามิเตอร์ที่ให้มากับเมธอด- ในที่สุดผลลัพธ์ก็กลับคืนสู่วิธีก่อนหน้าและมาหาเรา🙌🏼
ส่วนเพิ่มเติม: สอง
- หลักการของความรู้น้อยที่สุด ถูกใช้ภายใน รูปแบบซุ้ม:
* ลูกค้าโต้ตอบกับซุ้มเท่านั้น: สิ่งที่ดี (เพื่อนคนหนึ่ง) - รหัสระบบสามารถอัปเกรดได้โดยไม่กระทบต่อ ไคลเอนต์ (หากเราไม่เปลี่ยนส่วน อินเทอร์เฟซ และหากเป็นเช่นนั้น เฉพาะ Facade เท่านั้นที่จะถูกเปลี่ยน)
- หาก ส่วนหน้า ปะปนกันและสูญเสีย หลักการข้างต้น : เพิ่มรูปแบบ ส่วนหน้า เพิ่มเติมภายใน ส่วนหน้า หลักของเรา
รหัสโซลูชันสุดท้าย: ทั้งสองรูปแบบ
มาทัวร์ชมโค้ดกันดีกว่า 💪🏼
- ในตอนแรก เรามีรูปแบบ อะแดปเตอร์:
main.kt
สรุปคำขอของลูกค้าของเรา ที่นี่เราสร้างออบเจ็กต์สุดท้ายของเราและรวมไว้ใน อะแดปเตอร์ ซึ่งใช้ อินเทอร์เฟซเป้าหมาย- ดังนั้น
TargetInterface.kt
คืออินเทอร์เฟซที่เริ่มแรกTarget.kt
คือคลาสที่เรานำไปใช้งาน AdapteeInterface.kt
คืออินเทอร์เฟซโค้ดใหม่ของเรา &Adaptee.kt
คือคลาสที่นำมาใช้ใหม่ของเราAdapter.kt
คือ อะแดปเตอร์ ของเรา ซึ่งใช้อินเทอร์เฟซ OLD อีกครั้ง และด้วยเหตุนี้จึงเปิดเผยวิธีการของมัน แต่เราจัดเตรียมอินสแตนซ์ ใหม่ ของเราให้กับ อะแดปเตอร์ เพื่อให้เราเรียกวิธีการเหล่านั้น ในอินสแตนซ์ จากวิธีการที่เปิดเผย
ฉันหวังว่าคุณจะได้โครงสร้าง หากมีอะไรไม่คลิก -› เขียนความคิดเห็น✌🏼
- ตอนนี้เราย้ายไปที่รูปแบบ Facade:
ProjectorInterface.kt
&AmplifierInterface.kt
เป็นอินเทอร์เฟซจากระบบProjector.kt
&Amplifier.kt
เป็นคลาสที่เป็นรูปธรรมที่ใช้งานอินเทอร์เฟซจากด้านบนOurFacade.kt
คือ Facade ซึ่ง แยก ระบบจากคำขอของลูกค้าmain.kt
คือคำขอของลูกค้าของเรา เราจัดเตรียมอินสแตนซ์ของคลาสให้กับ Facade มีวิธีการที่ลูกค้าดึงมาในขณะนี้ ภายในวิธีการเหล่านั้น เรามีอินสแตนซ์ที่เรียกว่าเป็นวิธีการ
ขอย้ำอีกครั้ง หาก อินเทอร์เฟซ ของระบบมีการเปลี่ยนแปลง (และ การใช้งาน คลาสด้วย) เราไม่จำเป็นต้องทำการปรับปรุง ระบบไคลเอนต์ครั้งใหญ่ ต่างๆ แต่เพียงปรับแต่งส่วนหน้า
การวาดภาพ
ในภาพคุณสามารถดู:
- ด้านซ้ายคือ รูปแบบอะแดปเตอร์
- ด้านขวาคือรูปแบบซุ้ม
มันยุ่งนิดหน่อย แต่หวังว่าคุณจะเข้าใจสาระสำคัญนะ
เอาโทร✌🏼
คำจำกัดความทางวิชาการของ รูปแบบอะแดปเตอร์: อะแดปเตอร์ทำหน้าที่เป็นคนกลางที่แปลงอินเทอร์เฟซที่เรามีอยู่ในระบบของเราให้เป็นอินเทอร์เฟซที่ลูกค้าคาดหวัง (เช่น เคยอยู่ในระบบของเรามาก่อน) อะแดปเตอร์ช่วยให้คลาสทำงานร่วมกันซึ่งไม่สามารถทำได้หากไม่มี (ไคลเอนต์และโค้ดสุดท้ายของเรา) เนื่องจากความไม่เข้ากัน
คำจำกัดความทางวิชาการของ รูปแบบซุ้ม: ให้อินเทอร์เฟซแบบรวม/จุดเข้าถึงชุดอินเทอร์เฟซในระบบ (ซึ่งมีการใช้งาน) Facade เป็นอินเทอร์เฟซระดับสูง (จริงๆ แล้ว ไม่ใช่อินเทอร์เฟซ แต่เป็นคลาส แต่ในคำศัพท์คลาสสิกพูดอย่างนั้น) ที่ทำให้การโต้ตอบกับระบบระดับต่ำง่ายขึ้น
ว้าว ข้อมูลเยอะมาก!🤯 อ่านซ้ำอย่างละเอียดและถามคำถามในความคิดเห็นหากมีอะไรพร่ามัว
คุณสามารถหาฉันได้:
- LinkedIn: www.linkedin.com/in/sleeplesschallenger
- GitHub: https://github.com/SleeplessChallenger
- Leetcode: https://leetcode.com/SleeplessChallenger/
- โทรเลข: @SleeplessChallenger