เฮ้เพื่อนเครื่องบดแพทเทิร์น! 😮 ยินดีต้อนรับสู่ซีรีส์ Design Patterns! เป็นชุดบทความที่ฉันเขียนเกี่ยวกับรูปแบบเพื่อกระจายความมัน🙌

ฉันขอแนะนำให้คุณอ่านบทความก่อนหน้านี้ซึ่งคุณสามารถเลือกความรู้มากมาย:

จากนั้น ตามปกติ สนับสนุน O'Reilly Media เนื่องจากหนังสือของพวกเขางดงามมาก: https://www.oreilly.com/library/view/head-first-design/9781492077992/

โครงสร้าง:

  • บทนำ
  • ปัญหา: อะแดปเตอร์
  • ภาคพิเศษที่หนึ่ง
  • ปัญหา: ด้านหน้า
  • หลักการออกแบบที่ต้องปฏิบัติตาม
  • ภาคพิเศษที่สอง
  • รหัสโซลูชันสุดท้าย: ทั้งสองรูปแบบ
  • การวาดภาพ

❗️ตอนแรกผมให้หมดเวลาไปที่ Adapter แล้วจึงเปลี่ยนไปใช้ Facade ซึ่งจะใช้เวลาน้อยลง ไปกันเลย☄️

รูปแบบอะแดปเตอร์และซุ้ม

อะแดปเตอร์

บทนำ

รูปแบบเป็นตัวเลือกที่ดีในการดำดิ่งสู่โลกแห่ง OOP อย่างน้อยสำหรับฉันมันเป็นประตูวิเศษที่เปิดออกซึ่งช่วยให้ฉันเข้าใจ OOP ไม่ใช่แค่คลาส มรดก องค์ประกอบ แต่เพื่อภายใน SOLID😱 จากพื้นฐาน ดูวิธีผสมระบบที่ซับซ้อน และอื่นๆ🧑🏼‍💻

และถึงแม้ว่าคุณจะไม่จำทุกรูปแบบในโลกนี้ (และคุณไม่ควรจำ) รูปแบบเหล่านั้นคือกระสุนเงินที่เข้าใจได้ ที่จะปูทางในใจของคุณไปสู่โครงสร้างที่ได้รับการปรับปรุงให้เหมาะสมยิ่งขึ้น ซอฟต์แวร์ที่ดีกว่า และผลที่ตามมา คุณเป็น SWE ชั้นนำ

ปัญหา: อะแดปเตอร์

ลองนึกภาพคุณมีลูกค้าที่รู้วิธีโต้ตอบกับชั้นเรียนบางชั้นเรียน

ตัวอย่างพื้นฐาน: ปลั๊ก AC -> ซ็อกเก็ต จากนั้นคุณย้ายไปประเทศอื่น🎌 และตอนนี้คุณใช้โครงสร้างต่อไปนี้: ปลั๊ก AC -› อะแดปเตอร์ไฟ AC -› ซ็อกเก็ต

อะแดปเตอร์ในการเขียนโปรแกรมทำงานในลักษณะเดียวกัน: ใช้ คลาสไคลเอนต์ รวมเข้ากับอินเทอร์เฟซที่รู้จักใน คลาสสุดท้าย (ลองจินตนาการว่าเป็น ผู้ขาย ของเรา ).

โปรดให้ความสนใจอย่างยิ่งกับโค้ดด้านล่าง ตอนแรกฉันคิดว่ารูปแบบนั้นง่ายมาก แต่ติดอยู่ในห้วงแห่งความเข้าใจผิดและเสียเวลาหลายชั่วโมง

ในตอนแรก ให้สังเกตตัวอย่างพื้นฐานของอะแดปเตอร์ จากนั้นฉันจะทำการสรุป

  1. เรามี 2 อินเทอร์เฟซ: Duck และ Turkey
  2. แต่ละคลาสมีการนำไปใช้งานของตัวเอง
  3. ลองนึกภาพ ลูกค้า รู้วิธีโต้ตอบกับอินเทอร์เฟซประเภท Duck แต่เรามีคลาสที่ใช้อินเทอร์เฟซ Turkey (ฉันเขียน MallardDuck เป็นตัวอย่าง แต่ลูกค้า ไม่รู้อะไรเกี่ยวกับเรื่องนี้)
  4. ใน Adapter เราใช้อินเทอร์เฟซที่เรา ‼️ปรับให้เข้ากับ‼️ ซึ่งในกรณีของเราคือ Duck
  5. ใส่วิธีการของ Adapt-to-Interface และใส่วิธีการของคลาสจริงที่ใช้ (PS: ระบุอินสแตนซ์ของคลาสที่จะใช้ประโยชน์จากวิธีการเหล่านั้นภายใน) ซึ่งในโค้ดด้านบนคือ : .gobble() & turkeyFly()

ฉันเริ่มต้นด้วยตัวอย่างโค้ดโดยเฉพาะ นอกเหนือจากบทความก่อนหน้านี้ หวังว่าบันทึกนี้จะทำให้การพูดน้อยของคุณราบรื่นขึ้น🙉

เอาล่ะ ผมขอใช้คำศัพท์ที่เกี่ยวข้องกับรูปแบบนี้สักหน่อยเพื่อให้เหมือนเป็นวิชาการมากขึ้น👨🏼‍🎓

  1. ลูกค้าถูกนำไปใช้กับ อินเทอร์เฟซเป้าหมาย โดยที่ เป้าหมาย คืออินเทอร์เฟซใดที่ลูกค้าทราบวิธีโต้ตอบกับ
  2. อะแดปเตอร์ ใช้ อินเทอร์เฟซเป้าหมาย เพื่อกำหนดเส้นทางคำขอของไคลเอ็นต์ ในตัวอย่างด้านบน TurkeyAdapter ใช้ อินเทอร์เฟซเป้าหมาย ซึ่งก็คือ Duck
  3. อินสแตนซ์ที่ใช้ภายในอะแดปเตอร์คือ อินเทอร์เฟซของอะแดปเตอร์ (เช่น CoolTurkey ของเราที่ให้ไว้กับอินเทอร์เฟซ อะแดปเตอร์ ใช้ Turkey อินเทอร์เฟซ)
  4. ลูกค้าได้รับการตอบรับอย่างมีความสุข และไม่เคยรู้เลยว่ามีมนต์ดำอยู่หลังประตู!🦹‍♂️

ที่จริงแล้วมีข้อแม้บางประการที่ฉันจะพูดคุยเพิ่มเติมในบทความ

  • อะแดปเตอร์ แยก ไคลเอ็นต์จากโค้ดสุดท้าย (อินเทอร์เฟซที่นำไปใช้) ดังนั้น รหัสไคลเอ็นต์และรหัสสุดท้ายของเราจึงไม่จำเป็นต้องเปลี่ยนหากบางส่วนอาจมีการเปลี่ยนแปลง (และในหลายๆ ครั้งเราทำไม่ได้)
  • ให้ความสนใจกับวิธีที่ อะแดปเตอร์ แมปคำขอของลูกค้าไม่ใช่คลาสที่เป็นรูปธรรม แต่เป็นอินเทอร์เฟซ ซึ่งหมายความว่าเราสามารถเขียน อะแดปเตอร์ หลายรายการเพื่อแปลงชุดคลาสต่างๆ

ภาคพิเศษที่หนึ่ง

  1. อะแดปเตอร์มี 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

หลักการออกแบบที่ต้องปฏิบัติตาม

  1. หลักการของความรู้น้อยที่สุด (กฎแห่ง Demeter): พูดคุยกับเพื่อนของคุณเท่านั้น🌝

โดยสรุป หมายความว่าเราต้องตระหนักดีถึงคลาสที่เราใช้ในขณะพัฒนาระบบ เช่น. อย่าสุ่มสี่สุ่มห้าเรียกวิธีการบางอย่างจากคลาสอื่นเพราะมันจะส่งผลย้อนกลับในข้อบกพร่อง🪲

ตัวอย่าง การเรียกเมธอดที่ไม่ถูกต้อง:

เราเรียกวิธีการบนอินสแตนซ์ที่ส่งคืนจากวิธีอื่น:

  1. getThermometer() ส่งคืนอินสแตนซ์
  2. เราโทร getTemperature() ในกรณีจาก 1

ข้อแนะนำในการเรียกวิธีการ:

  1. จากชั้นเรียนเดียวกัน
  2. จากองค์ประกอบของคลาสปัจจุบัน
  3. จากพารามิเตอร์ (อาจเป็นวัตถุ) ที่ส่งผ่านไปยังวิธีการ
  4. จากอ็อบเจ็กต์ที่สร้าง/สร้างอินสแตนซ์ในเมธอด

‼️อันตราย:‼ ️ อย่าเรียกใช้เมธอดบนอ็อบเจ็กต์ที่ส่งคืนจากวิธีอื่นให้เรียกภายในเมธอด มันเพิ่มจำนวนเพื่อนที่เกี่ยวข้องกับวิธีการ/คลาสซึ่งไม่ดีในกรณีของเรา (ดูตัวอย่างด้านบนสำหรับกรณีดังกล่าว)

ฉันจะแสดงให้คุณเห็นว่าทุกอย่างทำงานอย่างไร:

  1. คลาสหลักคือ Car โดยที่เราทริกเกอร์เมธอด start()
  2. เรามาดูรายละเอียดวิธีการเรียกเมธอดในเมธอด:
    * บรรทัด 30: ใช้เมธอดบน พารามิเตอร์ ที่ระบุให้กับเมธอด
    * บรรทัด 32: ใช้เมธอดบนส่วนประกอบของคลาส , เครื่องยนต์
    * บรรทัด 33: ใช้วิธีการของคลาสปัจจุบัน Car
    * บรรทัด 34: ใช้วิธีการของคลาสที่สร้าง/สร้างอินสแตนซ์ในวิธีการ

สุดท้าย ฉันจะแสดงให้คุณเห็นว่าคุณสามารถแก้ไขการเรียกเมธอดจากด้านบนได้อย่างไร (ตัวอย่างที่ไม่ดี)🎉:

  1. เราเรียก giveHouseTemperature() แต่เราไม่สามารถเรียกวิธีการสุ่มสี่สุ่มห้าบน currentStation แล้วจึงเรียกทันทีบน thermometer ดังนั้นเราจึง แยก การร้องขอ
  2. ขั้นแรก ให้ใช้เทอร์โมมิเตอร์จาก currentStation : ใช้วิธีการกับ ส่วนประกอบของคลาส ปัจจุบัน
  3. จากนั้นเรียก วิธีการภายในคลาสของเรา โดยที่เราจัดเตรียมอินสแตนซ์ เทอร์โมมิเตอร์
  4. giveTemperature() ถูกเรียกใช้บนพารามิเตอร์ที่ให้มากับเมธอด
  5. ในที่สุดผลลัพธ์ก็กลับคืนสู่วิธีก่อนหน้าและมาหาเรา🙌🏼

ส่วนเพิ่มเติม: สอง

  1. หลักการของความรู้น้อยที่สุด ถูกใช้ภายใน รูปแบบซุ้ม:
    * ลูกค้าโต้ตอบกับซุ้มเท่านั้น: สิ่งที่ดี (เพื่อนคนหนึ่ง)
  2. รหัสระบบสามารถอัปเกรดได้โดยไม่กระทบต่อ ไคลเอนต์ (หากเราไม่เปลี่ยนส่วน อินเทอร์เฟซ และหากเป็นเช่นนั้น เฉพาะ Facade เท่านั้นที่จะถูกเปลี่ยน)
  3. หาก ส่วนหน้า ปะปนกันและสูญเสีย หลักการข้างต้น : เพิ่มรูปแบบ ส่วนหน้า เพิ่มเติมภายใน ส่วนหน้า หลักของเรา

รหัสโซลูชันสุดท้าย: ทั้งสองรูปแบบ

มาทัวร์ชมโค้ดกันดีกว่า 💪🏼

  • ในตอนแรก เรามีรูปแบบ อะแดปเตอร์:


  1. main.kt สรุปคำขอของลูกค้าของเรา ที่นี่เราสร้างออบเจ็กต์สุดท้ายของเราและรวมไว้ใน อะแดปเตอร์ ซึ่งใช้ อินเทอร์เฟซเป้าหมาย
  2. ดังนั้น TargetInterface.kt คืออินเทอร์เฟซที่เริ่มแรก Target.kt คือคลาสที่เรานำไปใช้งาน
  3. AdapteeInterface.kt คืออินเทอร์เฟซโค้ดใหม่ของเรา & Adaptee.kt คือคลาสที่นำมาใช้ใหม่ของเรา
  4. Adapter.kt คือ อะแดปเตอร์ ของเรา ซึ่งใช้อินเทอร์เฟซ OLD อีกครั้ง และด้วยเหตุนี้จึงเปิดเผยวิธีการของมัน แต่เราจัดเตรียมอินสแตนซ์ ใหม่ ของเราให้กับ อะแดปเตอร์ เพื่อให้เราเรียกวิธีการเหล่านั้น ในอินสแตนซ์ จากวิธีการที่เปิดเผย

ฉันหวังว่าคุณจะได้โครงสร้าง หากมีอะไรไม่คลิก -› เขียนความคิดเห็น✌🏼

  • ตอนนี้เราย้ายไปที่รูปแบบ Facade:


  1. ProjectorInterface.kt & AmplifierInterface.kt เป็นอินเทอร์เฟซจากระบบ
  2. Projector.kt & Amplifier.kt เป็นคลาสที่เป็นรูปธรรมที่ใช้งานอินเทอร์เฟซจากด้านบน
  3. OurFacade.kt คือ Facade ซึ่ง แยก ระบบจากคำขอของลูกค้า
  4. main.kt คือคำขอของลูกค้าของเรา เราจัดเตรียมอินสแตนซ์ของคลาสให้กับ Facade มีวิธีการที่ลูกค้าดึงมาในขณะนี้ ภายในวิธีการเหล่านั้น เรามีอินสแตนซ์ที่เรียกว่าเป็นวิธีการ

ขอย้ำอีกครั้ง หาก อินเทอร์เฟซ ของระบบมีการเปลี่ยนแปลง (และ การใช้งาน คลาสด้วย) เราไม่จำเป็นต้องทำการปรับปรุง ระบบไคลเอนต์ครั้งใหญ่ ต่างๆ แต่เพียงปรับแต่งส่วนหน้า

การวาดภาพ

ในภาพคุณสามารถดู:

  • ด้านซ้ายคือ รูปแบบอะแดปเตอร์
  • ด้านขวาคือรูปแบบซุ้ม

มันยุ่งนิดหน่อย แต่หวังว่าคุณจะเข้าใจสาระสำคัญนะ

เอาโทร✌🏼

คำจำกัดความทางวิชาการของ รูปแบบอะแดปเตอร์: อะแดปเตอร์ทำหน้าที่เป็นคนกลางที่แปลงอินเทอร์เฟซที่เรามีอยู่ในระบบของเราให้เป็นอินเทอร์เฟซที่ลูกค้าคาดหวัง (เช่น เคยอยู่ในระบบของเรามาก่อน) อะแดปเตอร์ช่วยให้คลาสทำงานร่วมกันซึ่งไม่สามารถทำได้หากไม่มี (ไคลเอนต์และโค้ดสุดท้ายของเรา) เนื่องจากความไม่เข้ากัน

คำจำกัดความทางวิชาการของ รูปแบบซุ้ม: ให้อินเทอร์เฟซแบบรวม/จุดเข้าถึงชุดอินเทอร์เฟซในระบบ (ซึ่งมีการใช้งาน) Facade เป็นอินเทอร์เฟซระดับสูง (จริงๆ แล้ว ไม่ใช่อินเทอร์เฟซ แต่เป็นคลาส แต่ในคำศัพท์คลาสสิกพูดอย่างนั้น) ที่ทำให้การโต้ตอบกับระบบระดับต่ำง่ายขึ้น

ว้าว ข้อมูลเยอะมาก!🤯 อ่านซ้ำอย่างละเอียดและถามคำถามในความคิดเห็นหากมีอะไรพร่ามัว

คุณสามารถหาฉันได้: