OO Design in Rails: Nơi đặt đồ đạc


244

Tôi thực sự thích Rails (mặc dù tôi nói chung là RESTless) và tôi thích Ruby rất OO. Tuy nhiên, xu hướng tạo ra các lớp con ActiveRecord và bộ điều khiển khổng lồ là khá tự nhiên (ngay cả khi bạn sử dụng bộ điều khiển cho mỗi tài nguyên). Nếu bạn tạo thế giới đối tượng sâu hơn, bạn sẽ đặt các lớp (và mô-đun ở đâu, tôi cho là vậy)? Tôi đang hỏi về quan điểm (trong chính Người trợ giúp?), Bộ điều khiển và mô hình.

Lib không sao, và tôi đã tìm thấy một số giải pháp để tải lại nó trong môi trường dev , nhưng tôi muốn biết liệu có cách nào tốt hơn để làm công cụ này không. Tôi thực sự chỉ quan tâm đến các lớp học phát triển quá lớn. Ngoài ra, những gì về động cơ và làm thế nào để phù hợp với họ?

Câu trả lời:


384

Vì Rails cung cấp cấu trúc theo thuật ngữ MVC, nên cuối cùng chỉ sử dụng các thùng chứa mô hình, khung nhìn và bộ điều khiển được cung cấp cho bạn. Thành ngữ điển hình cho người mới bắt đầu (và thậm chí một số lập trình viên trung gian) là nhồi nhét tất cả logic trong ứng dụng vào mô hình (lớp cơ sở dữ liệu), trình điều khiển hoặc khung nhìn.

Tại một số thời điểm, ai đó chỉ ra mô hình "mô hình béo, bộ điều khiển gầy" và các nhà phát triển trung gian vội vã lấy tất cả mọi thứ từ bộ điều khiển của họ và ném nó vào mô hình, bắt đầu trở thành thùng rác mới cho logic ứng dụng.

Trên thực tế, bộ điều khiển gầy là một ý tưởng tốt, nhưng hệ quả - đưa mọi thứ vào mô hình, thực sự không phải là kế hoạch tốt nhất.

Trong Ruby, bạn có một vài lựa chọn tốt để biến mọi thứ thành mô-đun hơn. Một câu trả lời khá phổ biến là chỉ sử dụng các mô-đun (thường được đặt trong lib) chứa các nhóm phương thức, sau đó đưa các mô-đun vào các lớp thích hợp. Điều này giúp ích trong trường hợp bạn có các loại chức năng mà bạn muốn sử dụng lại trong nhiều lớp, nhưng trong đó chức năng vẫn được gắn liền với các lớp.

Hãy nhớ rằng, khi bạn bao gồm một mô-đun vào một lớp, các phương thức trở thành các phương thức cá thể của lớp, vì vậy bạn vẫn kết thúc với một lớp chứa hàng tấn phương thức, chúng chỉ được tổ chức thành nhiều tệp.

Giải pháp này có thể hoạt động tốt trong một số trường hợp - trong các trường hợp khác, bạn sẽ muốn nghĩ về việc sử dụng các lớp trong mã không phải là mô hình, khung nhìn hoặc bộ điều khiển.

Một cách tốt để suy nghĩ về nó là "nguyên tắc trách nhiệm duy nhất", nói rằng một lớp nên chịu trách nhiệm cho một (hoặc một số lượng nhỏ) các sự việc. Các mô hình của bạn chịu trách nhiệm lưu trữ dữ liệu từ ứng dụng của bạn đến cơ sở dữ liệu. Bộ điều khiển của bạn có trách nhiệm nhận được yêu cầu và trả lại phản hồi khả thi.

Nếu bạn có các khái niệm không phù hợp gọn gàng trong các hộp đó (sự kiên trì, quản lý yêu cầu / phản hồi), bạn có thể muốn nghĩ về cách bạn sẽ mô hình hóa ý tưởng trong câu hỏi. Bạn có thể lưu trữ các lớp không mô hình trong ứng dụng / lớp hoặc bất kỳ nơi nào khác và thêm thư mục đó vào đường dẫn tải của bạn bằng cách thực hiện:

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

Nếu bạn đang sử dụng hành khách hoặc JRuby, có lẽ bạn cũng muốn thêm đường dẫn của mình vào đường dẫn tải háo hức:

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

Điểm mấu chốt là một khi bạn đi đến một điểm trong Rails nơi bạn thấy mình hỏi câu hỏi này, đã đến lúc tăng cường các nhóm Ruby của bạn và bắt đầu mô hình hóa các lớp không chỉ là các lớp MVC mà Rails cung cấp cho bạn theo mặc định.

Cập nhật: Câu trả lời này áp dụng cho Rails 2.x trở lên.


Cô ơi. Thêm một thư mục riêng cho những người không phải là Người mẫu đã xảy ra với tôi. Tôi có thể cảm thấy sắp xếp gọn gàng ...
Mike Woodhouse

Yehuda, cảm ơn vì điều đó. Câu trả lời chính xác. Đó chính xác là những gì tôi thấy trong các ứng dụng tôi thừa kế (và những ứng dụng tôi tạo): mọi thứ trong bộ điều khiển, mô hình, chế độ xem và trình trợ giúp tự động được cung cấp cho bộ điều khiển và chế độ xem. Sau đó đến các mixin từ lib, nhưng chưa bao giờ thử làm mô hình OO thực sự. Mặc dù vậy, bạn đã đúng: trong "ứng dụng / lớp học hoặc bất kỳ nơi nào khác." Chỉ muốn kiểm tra xem có câu trả lời chuẩn nào tôi đang thiếu không ...
Dan Rosenstark

33
Với các phiên bản gần đây hơn, config.autoload_paths mặc định cho tất cả các thư mục trong ứng dụng. Vì vậy, bạn không cần thay đổi config.load_paths như được mô tả ở trên. Mặc dù vậy, tôi không chắc chắn về eager_load_paths và cần phải xem xét điều đó. Có ai biết chưa?
Shyam Habarakada

Bị động thụ động đối với người trung gian: P
Sebastian Patten

8
Sẽ thật tuyệt nếu Rails được vận chuyển với thư mục "lớp" này để khuyến khích "nguyên tắc trách nhiệm duy nhất" và cho phép các nhà phát triển tạo các đối tượng không được hỗ trợ cơ sở dữ liệu. Việc triển khai "Mối quan tâm" trong Rails 4 (xem câu trả lời của Simone) dường như đã quan tâm đến việc triển khai các mô-đun để chia sẻ logic trên các mô hình. Tuy nhiên, không có công cụ nào được tạo ra cho các lớp Ruby đơn giản không hỗ trợ cơ sở dữ liệu. Cho rằng Rails rất quan tâm, tôi tò mò quá trình suy nghĩ đằng sau KHÔNG bao gồm một thư mục như thế này?
Ryan Francis

62

Cập nhật : Việc sử dụng Mối quan tâm đã được xác nhận là mặc định mới trong Rails 4 .

Nó thực sự phụ thuộc vào bản chất của mô-đun. Tôi thường đặt các phần mở rộng của bộ điều khiển / mô hình trong thư mục / mối quan tâm trong ứng dụng.

# 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 là lựa chọn ưa thích của tôi cho các thư viện mục đích chung. Tôi luôn có một không gian tên dự án trong lib nơi tôi đặt tất cả các thư viện dành riêng cho ứng dụng.

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

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

Các phần mở rộng lõi của Ruby / Rails thường diễn ra trong các bộ khởi tạo cấu hình để các thư viện chỉ được tải một lần trên Rails boostrap.

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

Đối với các đoạn mã có thể tái sử dụng, tôi thường tạo các plugin (micro) để tôi có thể sử dụng lại chúng trong các dự án khác.

Các tệp trình trợ giúp thường giữ các phương thức của trình trợ giúp và đôi khi các lớp khi đối tượng được sử dụng bởi người trợ giúp (ví dụ Trình tạo biểu mẫu).

Đây là một tổng quan thực sự chung. Vui lòng cung cấp thêm chi tiết về các ví dụ cụ thể nếu bạn muốn nhận được các đề xuất tùy chỉnh hơn. :)


Điều kỳ quái. Tôi không thể yêu cầu điều này yêu cầu RAILS_ROOT + "/ lib / my_module" để làm việc với một cái gì đó từ thư mục lib. Nó chắc chắn thực thi và phàn nàn nếu không tìm thấy tệp, nhưng nó không tải lại.
Dan Rosenstark

Ruby chỉ yêu cầu tải mọi thứ một lần. Nếu bạn muốn tải một cái gì đó vô điều kiện, hãy sử dụng tải.
Chuck

Ngoài ra, có vẻ khá bất thường với tôi rằng bạn muốn tải một tệp hai lần trong suốt vòng đời của một ứng dụng. Bạn đang tạo mã khi bạn đi?
Chuck

Tại sao bạn sử dụng allow_dependency thay vì yêu cầu? Cũng lưu ý rằng nếu bạn tuân theo các quy ước đặt tên, bạn hoàn toàn không cần sử dụng. Nếu bạn tạo MyModule trong lib / my_module, bạn có thể gọi MyModule mà không cần yêu cầu trước đó (ngay cả khi sử dụng yêu cầu phải nhanh hơn và đôi khi dễ đọc hơn). Cũng lưu ý rằng tệp trong / lib chỉ được tải một lần trên bootstrap.
Simone Carletti

1
Sử dụng các mối quan tâm có liên quan
bbozo

10

... xu hướng tạo ra các lớp con ActiveRecord khổng lồ và bộ điều khiển khổng lồ là khá tự nhiên ...

"khổng lồ" là một từ đáng lo ngại ... ;-)

Làm thế nào là bộ điều khiển của bạn trở nên lớn? Đó là điều bạn nên xem xét: lý tưởng nhất, bộ điều khiển nên mỏng. Chọn một quy tắc ngón tay cái trong không khí mỏng, tôi đề nghị rằng nếu bạn thường xuyên có nhiều hơn, giả sử, 5 hoặc 6 dòng mã cho mỗi phương thức điều khiển (hành động), thì bộ điều khiển của bạn có thể quá béo. Có sự trùng lặp có thể chuyển sang chức năng của trình trợ giúp hoặc bộ lọc không? Có logic kinh doanh có thể được đẩy xuống trong các mô hình?

Làm thế nào để mô hình của bạn trở nên khổng lồ? Bạn có nên xem xét các cách để giảm số lượng trách nhiệm trong mỗi lớp? Có bất kỳ hành vi phổ biến nào bạn có thể trích xuất thành mixin không? Hoặc các lĩnh vực chức năng bạn có thể ủy thác cho các lớp trợ giúp?

EDIT: Cố gắng mở rộng một chút, hy vọng không làm biến dạng bất cứ điều gì quá tệ ...

Người trợ giúp: sống app/helpersvà chủ yếu được sử dụng để làm cho chế độ xem đơn giản hơn. Chúng là dành riêng cho bộ điều khiển (cũng có sẵn cho tất cả các chế độ xem cho bộ điều khiển đó) hoặc thường có sẵn ( module ApplicationHelpertrong application_helper.rb).

Bộ lọc: Giả sử bạn có cùng một dòng mã trong một số hành động (khá thường xuyên, truy xuất một đối tượng bằng cách sử dụng params[:id]hoặc tương tự). Sự trùng lặp đó có thể được trừu tượng hóa trước tiên thành một phương thức riêng biệt và sau đó hoàn toàn thoát khỏi các hành động bằng cách khai báo một bộ lọc trong định nghĩa lớp, chẳng hạn như before_filter :get_object. Xem Phần 6 trong Hướng dẫn Rails ActionContoder Hãy lập trình khai báo làm bạn.

Mô hình tái cấu trúc là một chút của một điều tôn giáo. Các môn đệ của chú Bob sẽ đề nghị, ví dụ, bạn tuân theo Năm điều răn của RẮN . Joel & Jeff có thể đề xuất một cách tiếp cận "thực dụng" hơn, mặc dù sau đó họ dường như được hòa giải hơn một chút . Tìm một hoặc nhiều phương thức trong một lớp hoạt động trên một tập hợp con các thuộc tính được xác định rõ ràng là một cách để thử xác định các lớp có thể được cấu trúc lại từ mô hình có nguồn gốc ActiveRecord của bạn.

Nhân tiện, các mô hình Rails không phải là lớp con của ActiveRecord :: Base. Hay nói cách khác, một mô hình không nhất thiết phải là một dạng tương tự của bảng hoặc thậm chí liên quan đến bất kỳ thứ gì được lưu trữ. Thậm chí tốt hơn, miễn là bạn đặt tên tệp của mình app/modelstheo quy ước của Rails (gọi #underscore trên tên lớp để tìm hiểu Rails sẽ tìm kiếm gì), Rails sẽ tìm thấy nó mà không requirecần thiết.


Đúng như vậy, Mike và cảm ơn vì sự quan tâm của bạn ... Tôi đã kế thừa một dự án trong đó có một số phương pháp trên các bộ điều khiển rất lớn. Tôi đã chia chúng thành các phương thức nhỏ hơn nhưng bản thân bộ điều khiển vẫn "mập". Vì vậy, những gì tôi đang tìm kiếm là tất cả các tùy chọn của tôi để giảm tải công cụ. Câu trả lời của bạn là "các hàm trợ giúp", "bộ lọc", "mô hình", "mixins" và "các lớp trợ giúp". Vì vậy, sau đó, tôi có thể đặt những thứ này ở đâu? Tôi có thể tổ chức một hệ thống phân cấp lớp được tự động tải trong dev env không?
Dan Rosenstark

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.