OO Design in Rails: จะใส่ของที่ไหนดี


244

ฉันสนุกกับ Rails จริงๆ (ถึงแม้ว่าฉันจะไม่ค่อยสงบ) และฉันก็เพลิดเพลินกับ Ruby ที่เป็น OO มาก ยังคงแนวโน้มที่จะทำให้ subclasses ของ ActiveRecord ขนาดใหญ่และตัวควบคุมขนาดใหญ่นั้นค่อนข้างเป็นธรรมชาติ (แม้ว่าคุณจะใช้ตัวควบคุมต่อทรัพยากร) ถ้าคุณจะสร้างโลกวัตถุที่ลึกกว่านี้คุณจะใส่คลาสไหน (และโมดูลฉันคิดว่า) ฉันถามเกี่ยวกับมุมมอง (ในตัวช่วยด้วยตนเอง) ผู้ควบคุมและรุ่น

Lib ไม่เป็นไรและฉันพบวิธีแก้ไขปัญหาเพื่อให้สามารถโหลดได้ในสภาพแวดล้อมการพัฒนา แต่ฉันต้องการทราบว่ามีวิธีที่ดีกว่าในการทำสิ่งนี้หรือไม่ ฉันแค่กังวลเกี่ยวกับชั้นเรียนที่มีขนาดใหญ่เกินไป นอกจากนี้แล้วสิ่งที่เกี่ยวกับเครื่องยนต์และพวกเขาเหมาะสมอย่างไร

คำตอบ:


384

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

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

ในความเป็นจริงแล้วความคิดที่ดี แต่คอนโทรลเลอร์ที่ผอมแห้งก็เป็นข้อแนะนำที่ดี แต่สิ่งที่พิสูจน์ได้ - การวางทุกอย่างไว้ในแบบจำลองไม่ใช่แผนการที่ดีที่สุดจริงๆ

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

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

วิธีแก้ปัญหานี้สามารถทำงานได้ดีในบางกรณี - ในกรณีอื่นคุณจะต้องคิดถึงการใช้คลาสในรหัสของคุณซึ่งไม่ใช่โมเดลมุมมองหรือตัวควบคุม

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

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

config.load_paths << File.join(Rails.root, "app", "classes")

หากคุณใช้ผู้โดยสารหรือ JRuby คุณอาจต้องการเพิ่มเส้นทางของคุณไปยังเส้นทางโหลดที่กระตือรือร้น:

config.eager_load_paths << File.join(Rails.root, "app", "classes")

บรรทัดล่างคือเมื่อคุณไปถึงจุดหนึ่งใน Rails ที่คุณพบว่าตัวเองถามคำถามนี้ถึงเวลาที่จะเพิ่มพูนการสับ Ruby ของคุณและเริ่มต้นการสร้างแบบจำลองชั้นเรียนที่ไม่ได้เป็นเพียงคลาส MVC ที่ Rails ให้คุณเป็นค่าเริ่มต้น

ปรับปรุง:คำตอบนี้ใช้กับ Rails 2.x และสูงกว่า


D'โอ้ การเพิ่มไดเรกทอรีแยกต่างหากสำหรับผู้ที่ไม่ใช่รุ่นนั้นไม่ได้เกิดขึ้นกับฉัน ฉันสามารถรู้สึกเป็นระเบียบขึ้นมา ...
Mike Woodhouse

Yehuda ขอบคุณสำหรับสิ่งนั้น คำตอบที่ดี นั่นคือสิ่งที่ฉันเห็นในแอพที่ฉันสืบทอด (และสิ่งที่ฉันทำ): ทุกสิ่งในตัวควบคุมรุ่นมุมมองและผู้ช่วยเหลือที่มีให้โดยอัตโนมัติสำหรับตัวควบคุมและมุมมอง จากนั้นมาผสมจาก lib แต่ไม่เคยมีความพยายามที่จะทำแบบจำลอง OO จริง แม้ว่าคุณจะถูก: ใน "แอพ / คลาสหรือที่อื่น ๆ " แค่อยากจะตรวจสอบว่ามีบางคำตอบมาตรฐานฉันหายไป ...
แดน Rosenstark

33
ด้วยเวอร์ชันล่าสุดเพิ่มเติม config.autoload_paths จะมีค่าเริ่มต้นเป็นไดเรกทอรีทั้งหมดภายใต้แอป ดังนั้นคุณไม่จำเป็นต้องเปลี่ยน config.load_paths ตามที่อธิบายไว้ข้างต้น ฉันไม่แน่ใจเกี่ยวกับ eager_load_paths (ยัง) และต้องพิจารณา มีใครรู้บ้างไหม?
Shyam Habarakada

ก้าวร้าวก้าวร้าวต่อตัวกลาง: P
Sebastian Patten

8
คงจะดีถ้า Rails ส่งมาพร้อมกับโฟลเดอร์ "คลาส" นี้เพื่อสนับสนุน "หลักการความรับผิดชอบเดี่ยว" และเพื่อให้นักพัฒนาสามารถสร้างวัตถุที่ไม่ได้รับการสำรองฐานข้อมูล การใช้งาน "ข้อกังวล" ใน Rails 4 (ดูคำตอบของ Simone) ดูเหมือนว่าจะได้รับการดูแลในการนำโมดูลไปใช้เพื่อแบ่งปันตรรกะในแบบจำลองต่างๆ อย่างไรก็ตามไม่มีการสร้างเครื่องมือดังกล่าวสำหรับคลาส Ruby ธรรมดาที่ไม่มีการสำรองฐานข้อมูล เนื่องจาก Rails นั้นให้ความเห็นเป็นอย่างมากฉันอยากรู้ว่ากระบวนการคิดเบื้องหลังไม่รวมโฟลเดอร์เช่นนี้หรือไม่?
Ryan Francis

62

ปรับปรุง : การใช้ความกังวลได้รับการยืนยันว่าเป็นค่าเริ่มต้นใหม่ใน Rails 4

มันขึ้นอยู่กับลักษณะของโมดูลเอง ฉันมักจะวางตัวควบคุม / ส่วนขยายโมเดลไว้ในโฟลเดอร์ / problems ภายในแอพ

# concerns/authentication.rb
module Authentication
  ...
end    

# controllers/application_controller.rb
class ApplicationController
  include Authentication
end



# concerns/configurable.rb
module Configurable
  ...
end    

class Model 
  include Indexable
end 

# controllers/foo_controller.rb
class FooController < ApplicationController
  include Indexable
end

# controllers/bar_controller.rb
class BarController < ApplicationController
  include Indexable
end

/ lib เป็นตัวเลือกที่ฉันต้องการสำหรับไลบรารีวัตถุประสงค์ทั่วไป ฉันมักจะมีเนมสเปซโครงการใน lib ที่ฉันวางไลบรารีเฉพาะแอปพลิเคชันทั้งหมด

/lib/myapp.rb
module MyApp
  VERSION = ...
end

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb

ส่วนขยายหลักของ Ruby / Rails มักจะเกิดขึ้นในการกำหนดค่าเริ่มต้นเพื่อให้โหลดไลบรารีได้เพียงครั้งเดียวบน Rails boostrap

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb

สำหรับชิ้นส่วนรหัสที่นำกลับมาใช้ใหม่ได้ฉันมักจะสร้างปลั๊กอิน (ไมโคร) เพื่อให้ฉันสามารถนำกลับมาใช้ใหม่ในโครงการอื่น ๆ

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

นี่เป็นภาพรวมทั่วไปจริงๆ โปรดให้รายละเอียดเพิ่มเติมเกี่ยวกับตัวอย่างเฉพาะหากคุณต้องการรับคำแนะนำที่กำหนดเองเพิ่มเติม :)


สิ่งที่แปลกประหลาด ฉันไม่สามารถรับ require_dependency นี้ RAILS_ROOT + "/ lib / my_module" เพื่อทำงานกับบางสิ่งนอกไดเรกทอรี lib มันจะดำเนินการและบ่นหากไม่พบไฟล์ แต่ไม่โหลดซ้ำ
Dan Rosenstark

Ruby's ต้องการเพียงแค่โหลดสิ่งของครั้งเดียวเท่านั้น หากคุณต้องการโหลดสิ่งที่ไม่มีเงื่อนไขให้ใช้โหลด
34499 Chuck

นอกจากนี้ดูเหมือนว่าฉันค่อนข้างผิดปกติที่คุณต้องการโหลดไฟล์สองครั้งในช่วงอายุการใช้งานของแอป คุณกำลังสร้างรหัสในขณะที่คุณไป?
34499 Chuck

ทำไมคุณถึงต้องใช้ require_dependency แทนที่จะต้องใช้ โปรดทราบว่าหากคุณทำตามข้อกำหนดในการตั้งชื่อคุณไม่จำเป็นต้องใช้ข้อกำหนดเลย หากคุณสร้าง MyModule ใน lib / my_module คุณสามารถเรียกใช้ MyModule ได้โดยไม่จำเป็นต้องใช้ก่อนหน้านี้ (แม้ว่าการใช้ Need ควรจะเร็วกว่าและบางครั้งก็สามารถอ่านได้มากขึ้น) นอกจากนี้โปรดทราบว่าไฟล์ใน / lib จะถูกโหลดเพียงครั้งเดียวใน bootstrap
Simone Carletti

1
การใช้ข้อกังวลเกี่ยวข้องกับ
bbozo

10

... แนวโน้มที่จะสร้างคลาสย่อย ActiveRecord ขนาดใหญ่และคอนโทรลเลอร์ขนาดใหญ่นั้นค่อนข้างเป็นธรรมชาติ ...

"ใหญ่" เป็นคำที่น่าเป็นห่วง ... ;-)

คอนโทรลเลอร์ของคุณมีขนาดใหญ่อย่างไร? นั่นคือสิ่งที่คุณควรดู: นึกคิดตัวควบคุมควรผอม การเลือกกฎของหัวแม่มือจากอากาศบางฉันขอแนะนำว่าถ้าคุณมีมากกว่าปกติพูด 5 หรือ 6 บรรทัดของรหัสต่อวิธีการควบคุม (การกระทำ) แล้วควบคุมของคุณอาจจะอ้วนเกินไป มีการทำสำเนาที่สามารถย้ายไปยังฟังก์ชันตัวช่วยหรือตัวกรองได้หรือไม่? มีตรรกะทางธุรกิจที่สามารถผลักดันลงในโมเดลหรือไม่

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

แก้ไข: พยายามขยายเล็กน้อยหวังว่าจะไม่บิดเบือนสิ่งใดที่เลวร้ายเกินไป ...

ผู้ช่วยเหลือ: อาศัยอยู่app/helpersและส่วนใหญ่จะใช้เพื่อทำให้การดูง่ายขึ้น พวกเขาเป็นตัวควบคุมเฉพาะ (ใช้ได้กับทุกมุมมองสำหรับตัวควบคุมนั้น) หรือโดยทั่วไป ( module ApplicationHelperใน application_helper.rb)

ตัวกรอง: สมมติว่าคุณมีรหัสบรรทัดเดียวกันในหลายการกระทำ (ค่อนข้างบ่อยการเรียกคืนวัตถุที่ใช้params[:id]หรือคล้ายกัน) before_filter :get_objectการทำสำเนาที่สามารถแยกแรกที่จะเป็นวิธีการที่แยกต่างหากและแล้วออกจากการกระทำทั้งหมดด้วยการประกาศตัวกรองในความละเอียดระดับเช่น ดูส่วนที่ 6 ในคู่มือราง ActionControllerให้เพื่อนของคุณตั้งโปรแกรม

แบบจำลองการสร้างใหม่นั้นเป็นเรื่องทางศาสนามากกว่า สาวกของลุงบ๊อบจะแนะนำตัวอย่างเช่นว่าคุณทำตามห้าประการของมูลฝอย โจเอล & เจฟฟ์อาจแนะนำวิธีการเพิ่มเติม, เอ่อ, "จริงจัง", แม้ว่าพวกเขาดูเหมือนจะคืนดีกันเล็กน้อยในภายหลัง การค้นหาหนึ่งหรือหลายวิธีภายในคลาสที่ทำงานบนเซ็ตย่อยที่กำหนดไว้อย่างชัดเจนของแอ็ตทริบิวต์เป็นวิธีหนึ่งในการลองระบุคลาสที่อาจถูกปรับโครงสร้างใหม่จากโมเดลที่ได้รับจาก ActiveRecord ของคุณ

Rails model ไม่จำเป็นต้องเป็น subclasses ของ ActiveRecord :: Base แต่อย่างใด หรือจะกล่าวอีกนัยหนึ่งแบบจำลองไม่จำเป็นต้องเป็นแบบอะนาล็อกของตารางหรือแม้แต่เกี่ยวข้องกับสิ่งที่เก็บไว้เลย ยิ่งไปกว่านั้นตราบใดที่คุณตั้งชื่อไฟล์ของคุณapp/modelsตามแบบแผนของ Rails (โทร #underscore บนชื่อคลาสเพื่อค้นหาว่า Rails จะค้นหาอะไร) Rails จะค้นหาไฟล์โดยไม่requireจำเป็น


จริงในทุกเรื่องไมค์และขอบคุณสำหรับความกังวลของคุณ ... ฉันได้รับโครงการที่มีวิธีการบางอย่างในตัวควบคุมที่มีขนาดใหญ่ ฉันแบ่งมันออกเป็นวิธีเล็ก ๆ แต่ตัวควบคุมยังคง "อ้วน" ดังนั้นสิ่งที่ฉันกำลังมองหาคือตัวเลือกทั้งหมดของฉันในการถ่ายโอนข้อมูล คำตอบของคุณคือ "ฟังก์ชั่นผู้ช่วย" "ตัวกรอง" "รุ่น" "มิกซ์อิน" และ "คลาสตัวช่วย" จากนั้นฉันจะใส่สิ่งเหล่านี้ได้ที่ไหน ฉันสามารถจัดลำดับชั้นเรียนที่มีการโหลดอัตโนมัติใน dev env ได้หรือไม่?
Dan Rosenstark

1

นี่คือบล็อกโพสต์ที่ยอดเยี่ยมเกี่ยวกับการเปลี่ยนรูปแบบไขมันที่ดูเหมือนจะเกิดขึ้นจากความปราชัย "thin controller":

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

ข้อความพื้นฐานคือ "Don't Extract Mixins จาก Fat Models" ใช้คลาสบริการแทนผู้เขียนให้ 7 รูปแบบในการทำเช่นนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.