Modules และ Mixins คือ Resources ยอดเยี่ยมที่ทำให้ Ruby น่าดึงดูด พวกเขาให้ Application ความสามารถในการ Share Code ที่สามารถใช้ได้ง่ายในที่อื่น มันยังช่วยจัดระเบียบโค้ดของเราโดย Grouping Functionalities และ Concerns ซึ่งปรับปรุง Readability และ Maintainability ของโค้ด
ในบทความนี้ เราจะผ่าน Concepts ข้างหลัง Modules และ Mixins เราจะเรียนรู้วิธีสร้างและ Mix Modules เข้า Classes อื่นๆ และ Discuss Benefits ของการใช้พวกเขาใน Ruby on Rails Application
หวังว่าคุณจะ Enjoy Journey นี้!
Modules คืออะไร
Modules เป็นหนึ่งใน Shiniest Resources ของ Ruby เพราะพวกเขา Provide 2 Great Benefits: เราสามารถสร้าง Namespaces เพื่อ Prevent Name Clashes และเราสามารถใช้พวกเขาเป็น Mixins เพื่อ Share Code ทั่ว Application
ในแง่ Structural Module ค่อนข้างคล้ายกับ Ruby Class ใดๆ จริงๆ แล้วสำหรับ Ruby Class เป็น Module ตามที่เราเห็นด้านล่างใน irb Console:
> Class.is_a?(Module) => true
คล้ายกับ Classes ด้วย Modules เราก็ Group Methods และ Constants และ Share Code อย่างไรก็ตาม มีความแตกต่างเล็กน้อยระหว่าง Module และ Plain Ruby Class:
- เราเริ่ม Definition ด้วย Keyword
moduleแทนclass; - เราไม่สามารถ Instantiate Modules ได้ ดังนั้นไม่มี Objects ที่สร้างจากมัน;
- เราไม่สามารถ Inherit จาก Modules ได้ ดังนั้นเราใช้พวกเขาเป็น Mixins แทน;
- Modules เป็น Standalone Code ดังนั้นไม่มี Inheritance Hierarchy ของ Modules;
Modules เป็นที่ยอดเยี่ยมสำหรับ Services, Concerns, Constants และ Code อื่นๆ ที่ด้วยการมี Responsibility เดียวกัน พวกเขาควรอยู่ด้วยกัน
Module ควรมีลักษณะแบบนี้:
# lib/modules/invoice_creator.rb module InvoiceCreator TAX_FEE = 0.5 def self.generate puts "Don't worry! I'll generate the invoice for you at #{TAX_FEE}%" end def invoice_total puts "I'll return the invoice total" 1000 end end
ในตัวอย่างนี้ เราสังเกตได้ว่า Module สามารถ Provide 2 ประเภทของ Methods: Module Methods และ Instance Methods
self.generate เป็น Module Method หมายความว่าเราสามารถใช้มันได้โดยไม่ต้อง Include (หรือ Extend) Module ใน Object ใดๆ นี่ Common มากเมื่อเราสร้าง Service Objects ตัวอย่าง เราสามารถเรียก Module Method ของเราแบบนี้:
2.5.3 :006 > InvoiceCreator.generate Don't worry! I'll generate the invoice for you => nil
invoice_total เป็น Instance Method และเพื่อให้ใช้ได้ เราต้อง Include Module เข้า Class เหมือนนี้:
# app/models/invoice.rb class Invoice < ApplicationRecord include InvoiceCreator # This includes our module in the Invoice class def calculate_tax total = invoice_total # included method from our module tax = total * InvoiceCreator::TAX_FEE puts "This is the invoice tax: #{tax}" end end
Instance Methods ทั้งหมดจาก InvoiceCreator กลายเป็น Available ให้ Invoice Instances ดังนั้นเราสามารถเรียก calculate_tax Method ได้ง่าย:
2.5.3 :008 > Invoice.new.calculate_tax "I'll return the invoice total" "This is the invoice tax: 500.0" => nil 2.5.3 :009 >
นอกจากนี้ ภายใน calculate_tax คุณสังเกตว่าเราใช้ Constant ที่ Defined ภายใน Module ของเรา ตามที่กล่าวไป Modules เป็น Great Constant Keepers!
จินตนาการ Scenario ตอนนี้ที่เราต้องมี 2 ชนิดของ InvoiceCreator เพื่อ Generate Invoices ที่ต่างกันหมดสำหรับ Suppliers และ Customers เราจะจบลงด้วย Name Clashing และเพื่อหลีกเลี่ยงนี้ เราใช้ Benefit อีกอย่างของ Modules: Namespaces มาดูใน Section ถัดไป
Namespaces Everywhere (Namespaces ไปทุกที่)
Namespaces สามารถ Define ได้ว่าเป็นวิธีจัดระเบียบ Code เมื่อเราต้องการสร้าง Local Context สำหรับ Functionality ที่กำหนด ซึ่งเป็นสิ่งที่เราต้องการสำหรับ Scenario ที่ฉันอธิบาย: Context ต่างสำหรับ Customer's Invoice Creator และอีกอันสำหรับ Supplier's Invoice Creator
มา回到โค้ดของเรา เราจะต้องสร้าง 2 Modules ต่างกัน:
# lib/modules/customer/invoice_creator.rb module Customer module InvoiceCreator def self.generate puts "Don't worry! I'll generate the customer invoice for you" end end end # lib/modules/supplier/invoice_creator.rb module Supplier module InvoiceCreator def self.generate puts "Don't worry! I'll generate the supplier invoice for you" end end end
จากนั้นเราสามารถใช้ Customer::InvoiceCreator หรือ Supplier::InvoiceCreator ที่เราต้องการ:
2.5.3 :014 > Customer::InvoiceCreator.generate Don't worry! I'll generate the customer invoice for you => nil 2.5.3 :015 > Supplier::InvoiceCreator.generate Don't worry! I'll generate the supplier invoice for you => nil 2.5.3 :016 >
วิธีนี้ Specific Code แต่ละตัวจะถูก Wrap ขึ้นภายใน Module ของตัวเอง Respecting Separation of Concerns Principle นอกจากนี้ Namespacing Everywhere ยังเป็นวิธียอดเยี่ยมในการเก็บ Code ของเราให้ Well Organized
มา看看ตอนนี้ว่าเราสามารถใช้ Benefit อื่นของ Ruby Modules ได้อย่างไร: Mixins
The Magic of Using Mixins (เวทมนต์ของการใช้ Mixins)
ตามที่คุณอาจรู้แล้ว Characteristic หนึ่งของ Ruby คือมัน Implement Single Inheritance Mechanism ซึ่งหมายความว่า Class สามารถ Inherit จาก Class อื่นได้เพียง Class เดียว เราบ่อยครั้งต้องการ Inherit จาก Classes มากกว่า ใน Ruby เราสามารถ Cover ความต้องการนั้นได้โดยใช้ Composition over Inheritance Pattern
สิ่งนี้ Doable โดยใช้ Mixins เมื่อเรา Mix In ชิ้นโค้ดใน Ruby Class อื่น เรากำลัง Add Behavior มากขึ้นให้ Class นี้โดยไม่ใช้ Inheritance และนี่ Amazing ดังนั้น ใน Ruby Mixins คือ Modules ที่เรา Include ใน Classes ที่พวกเขาจำเป็น ด้วยนั้นเรา Gain โดยเก็บ Code ให้ Clean และ Responsibilities Separated ตามที่ควรเป็น
ตัวอย่าง มากำหนดว่า InvoiceCreator Module ของเราเป็น Service ที่ต้องใช้ Functionalities ที่จัดเตรียมโดย Modules อื่นๆ หลายตัว เช่น InvoiceCalculator, InvoiceRenderer และ InvoiceSender พวกเขาจำเป็นสำหรับ Fulfill Invoice Creation Process
เราสามารถ Achieve ได้โดย Include Chain ของ Modules เป็น Mixins ใน Code ของเรา ดังนั้นเราสามารถใช้ Methods โดยตรง เหมือนตัวอย่างด้านล่าง:
# lib/modules/invoice_calculator.rb module InvoiceCalculator def calculate_items_total puts "imagine some math here" end end # lib/modules/invoice_renderer.rb module InvoiceRenderer def generate_invoice_pdf puts "imagine that we are using some PDF generation magic here" end end # lib/modules/invoice_sender.rb module InvoiceSender def send_invoice puts "imagine your favorite mail service being used here" end end
เนื่องจากเราไม่สามารถทำให้ InvoiceCreator Inherit จาก Modules อื่นๆ ทั้ง 3 เราจึง Include พวกเขาแทน วิธีนี้ InvoiceCreator Include Methods ทั้งหมดจาก Modules Invoice อื่นๆ และเราสามารถเรียกเหล่านี้ใน Class/Module ใดๆ ที่ InvoiceCreator Module ถูก Include เข้าไป ระวัง! หาก Modules ใดๆ มี Methods ชื่อเดียวกัน พวกเขาจะ Overwrite กัน
# lib/modules/customer/invoice_creator.rb module Customer module InvoiceCreator include InvoiceCalculator include InvoiceRenderer include InvoiceSender def generate_invoice calculate_items_total # from InvoiceCalculator generate_invoice_pdf # from InvoiceRenderer send_invoice # from InvoiceSender end end end
ตอนนี้เราสามารถเรียก Service Methods ของเราที่ไหนก็ตามที่เรา Include มัน โดยทำนี้:
# app/models/invoice.rb class Invoice < ApplicationRecord include Customer::InvoiceCreator def send_invoice_to_customer puts "Don't worry! I'll generate the customer invoice for you" generate_invoice end end
นี่จะเป็น Result การเรียก Methods จาก Included Invoice Modules ผ่าน InvoiceCreator Module:
2.5.3 :051 > Invoice.new.generate_invoice Don't worry! I'll generate the supplier invoice for you imagine some math here imagine that we are using some PDF generation magic here imagine your favorite mail service being used here => nil
สังเกตว่า นี่คือวิธีที่เราใช้ Composition over Inheritance Principle Principle นี้ Stand ว่าคุณควร Prefer ใช้ Composition เมื่อไหนก็ตามที่คุณสามารถ ใน Composition เราสร้าง Classes ที่ Responsible ให้ Specific Functionalities ให้ Others ซึ่งเป็นสิ่งที่เราทำอยู่
That's all Folks! (จบแล้วครับ!)
ในฐานะ Ruby Developers เรารักใช้ Facilities และ Syntax Sugar ของมันเมื่อ Planning, Writing, Maintaining และ Refactoring Code ของเรา ฉันหวังว่าผ่านบทความนี้ คุณจะ Appreciate ทำไม Modules จึงเป็น Resource ยอดเยี่ยมที่ช่วยปรับปรุง Readability ของโค้ด เก็บ Things ให้มี Responsibility เดียว และเก็บ Codebase ให้ Clean และ Easy to Maintain และเรายังสามารถใช้ Mixins Magic เพื่อ Include Modules ใน Code ของเราในกรณีที่เราจะต้อง Inherit จาก Multiple Sources
เราจะคุยต่อเกี่ยวกับ Greatest Resources ของ Ruby และ Rails ที่นี่ Stay tuned!
P.S. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับข่าวสาร Ruby Magic newsletter และจะไม่พลาดโพสต์เด็ดๆ ไปเลย!