Khi nào nên sử dụng các lớp lồng nhau và các lớp được lồng trong các mô-đun?


144

Tôi khá quen thuộc khi sử dụng các lớp con và mô-đun, nhưng gần đây tôi đã thấy các lớp lồng nhau như thế này:

class Foo
  class Bar
    # do some useful things
  end
end

Cũng như các lớp được lồng trong các mô-đun như vậy:

module Baz
  class Quux
    # more code
  end
end

Tài liệu và bài viết đều thưa thớt hoặc tôi không được giáo dục về chủ đề này đủ để tìm kiếm các cụm từ tìm kiếm phù hợp, nhưng dường như tôi không thể tìm thấy nhiều thông tin về chủ đề này.

Ai đó có thể cung cấp các ví dụ hoặc liên kết đến bài viết về lý do tại sao / khi những kỹ thuật đó sẽ được sử dụng?

Câu trả lời:


140

Các ngôn ngữ OOP khác có các lớp bên trong không thể khởi tạo mà không bị ràng buộc với lớp cấp trên. Chẳng hạn, trong Java,

class Car {
    class Wheel { }
}

chỉ các phương thức trong Carlớp có thể tạo Wheels.

Ruby không có hành vi đó.

Trong Ruby,

class Car
  class Wheel
  end
end

khác với

class Car
end

class Wheel
end

chỉ trong tên của lớp Wheelso với Car::Wheel. Sự khác biệt về tên này có thể làm cho các lập trình viên rõ ràng rằng Car::Wheellớp chỉ có thể đại diện cho một bánh xe ô tô, trái ngược với một bánh xe chung. Các định nghĩa lớp lồng nhau trong Ruby là một vấn đề ưu tiên, nhưng nó phục vụ một mục đích theo nghĩa là nó thực thi mạnh mẽ hơn một hợp đồng giữa hai lớp và làm như vậy truyền tải thêm thông tin về chúng và việc sử dụng chúng.

Nhưng với trình thông dịch Ruby, đó chỉ là một sự khác biệt về tên.

Đối với quan sát thứ hai của bạn, các lớp được lồng bên trong các mô-đun thường được sử dụng để không gian tên các lớp. Ví dụ:

module ActiveRecord
  class Base
  end
end

khác với

module ActionMailer
  class Base
  end
end

Mặc dù đây không phải là việc sử dụng duy nhất các lớp được lồng bên trong các mô-đun, nhưng nhìn chung nó là phổ biến nhất.


5
@rubyprince, tôi không chắc ý của bạn là gì khi thiết lập mối quan hệ giữa Car.newCar::Wheel.new. Bạn chắc chắn không cần phải khởi tạo một Carđối tượng để khởi tạo một Car::Wheelđối tượng trong Ruby, nhưng Carlớp phải được tải và thực thi Car::Wheelđể có thể sử dụng được.
Pan Thomakos

30
@Pan, bạn đang nhầm lẫn các lớp bên trong Java và các lớp Ruby được đặt tên. Một lớp lồng nhau Java không tĩnh được gọi là một lớp bên trong và nó chỉ tồn tại trong một thể hiện của lớp bên ngoài. Có một trường ẩn cho phép tham chiếu ra bên ngoài. Lớp bên trong Ruby chỉ đơn giản là được đặt tên và không bị "ràng buộc" với lớp kèm theo theo bất kỳ cách nào. Nó tương đương với một lớp tĩnh Java (lồng nhau) . Vâng, câu trả lời có rất nhiều phiếu nhưng nó không hoàn toàn chính xác.
DigitalRoss

7
Tôi không biết làm thế nào câu trả lời này có 60 upvote, chứ chưa nói đến việc được OP chấp nhận. Có nghĩa đen không phải là một tuyên bố đúng ở đây. Ruby không có các lớp lồng nhau như Beta hay Drameak. Hoàn toàn không có mối quan hệ nào giữa CarCar::Wheel. Các mô-đun (và do đó các lớp) chỉ đơn giản là không gian tên cho các hằng số, không có thứ gọi là lớp lồng nhau hoặc mô-đun lồng nhau trong Ruby.
Jörg W Mittag

4
Sự khác biệt duy nhất giữa hai là độ phân giải không đổi (là từ vựng, và do đó rõ ràng là khác nhau vì hai đoạn mã khác nhau về mặt từ vựng). Tuy nhiên, hoàn toàn không có sự khác biệt giữa hai lớp liên quan. Đơn giản là có hai lớp hoàn toàn không liên quan. Giai đoạn = Stage. Ruby không có các lớp lồng nhau / bên trong. Định nghĩa của bạn về các lớp lồng nhau là chính xác, nhưng đơn giản là nó không áp dụng cho Ruby, vì bạn có thể kiểm tra một cách tầm thường : Car::Wheel.new. Bùng nổ. Tôi vừa mới xây dựng một Wheelđối tượng không được lồng bên trong một Carđối tượng.
Jörg W Mittag

10
Bài đăng này rất dễ gây hiểu lầm mà không đọc toàn bộ chủ đề bình luận.
Nathan

50

Trong Ruby, định nghĩa một lớp lồng nhau tương tự như định nghĩa một lớp trong một mô-đun. Nó không thực sự buộc một liên kết giữa các lớp, nó chỉ tạo một không gian tên cho các hằng số. (Tên lớp và mô-đun là hằng số.)

Câu trả lời được chấp nhận là không chính xác về bất cứ điều gì. 1 Trong ví dụ dưới đây, tôi tạo một thể hiện của lớp được bao bọc từ vựng mà không có một thể hiện của lớp kèm theo từng tồn tại.

class A; class B; end; end
A::B.new

Các ưu điểm giống như các ưu điểm cho các mô-đun: đóng gói, nhóm mã được sử dụng chỉ ở một nơi và đặt mã gần hơn với nơi được sử dụng. Một dự án lớn có thể có một mô-đun bên ngoài xảy ra lặp đi lặp lại trong mỗi tệp nguồn và chứa rất nhiều định nghĩa lớp. Khi tất cả các khung và mã thư viện khác nhau thực hiện điều này, thì chúng chỉ đóng góp một tên cho mỗi cấp độ cao nhất, giảm nguy cơ xung đột. Prosaic, để chắc chắn, nhưng đó là lý do tại sao chúng được sử dụng.

Sử dụng một lớp thay vì một mô-đun để xác định không gian tên bên ngoài có thể có ý nghĩa trong một chương trình hoặc tập lệnh một tệp hoặc nếu bạn đã sử dụng lớp cấp cao nhất cho một cái gì đó, hoặc nếu bạn thực sự sẽ thêm mã để liên kết các lớp với nhau theo phong cách nội tâm thực sự . Ruby không có các lớp bên trong nhưng không có gì ngăn bạn tạo ra cùng một hành vi trong mã. Tham chiếu các đối tượng bên ngoài từ các đối tượng bên trong vẫn sẽ yêu cầu chấm vào từ thể hiện của đối tượng bên ngoài nhưng việc lồng các lớp sẽ gợi ý rằng đây là điều bạn có thể đang làm. Một chương trình được mô đun hóa cẩn thận có thể luôn tạo ra các lớp kèm theo trước và chúng có thể được phân tách hợp lý với các lớp lồng nhau hoặc bên trong. Bạn không thể gọi newmột mô-đun.

Bạn có thể sử dụng mẫu chung ngay cả cho các tập lệnh, trong đó không gian tên không cần thiết lắm, chỉ để giải trí và thực hành ...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end

  self
end.new.run

15
Xin vui lòng, xin vui lòng, đừng gọi đây là một lớp học bên trong. Nó không thể. Lớp Bkhông lớp bên trong A. Các hằng số B được namespaced lớp bên trong A, nhưng hoàn toàn không có mối quan hệ giữa các đối tượng được tham chiếu bởi B(mà trong trường hợp này chỉ xảy ra được một lớp học) và lớp được tham chiếu bởi A.
Jörg W Mittag

2
Ok, thuật ngữ "bên trong" được loại bỏ. Điểm tốt. Đối với những người không tuân theo lập luận trên, lý do của tranh chấp là khi bạn thực hiện một cái gì đó như thế này, giả sử, các đối tượng của lớp bên trong (và ở đây tôi đang sử dụng thuật ngữ chính tắc) có chứa một tham chiếu đến lớp bên ngoài và các biến thể hiện bên ngoài có thể được tham chiếu bởi các phương thức lớp bên trong. Không có điều gì xảy ra trong Ruby trừ khi bạn liên kết chúng với mã. Và bạn biết đấy, nếu mã đó đã có mặt trong lớp ahem, kèm theo, thì tôi cá là bạn có thể gọi Bar một cách hợp lý .
DigitalRoss

1
Đối với tôi, chính câu lệnh này giúp tôi nhiều nhất khi quyết định giữa một mô-đun và một lớp: You can't call new on a module.- vì vậy về mặt cơ bản nếu tôi chỉ muốn không gian tên một số lớp và không bao giờ thực sự cần phải tạo một thể hiện của "lớp" bên ngoài, thì Tôi sẽ sử dụng một mô-đun bên ngoài. Nhưng nếu tôi muốn khởi tạo một thể hiện của "lớp" bao bọc / bên ngoài thì tôi sẽ biến nó thành một Class thay vì một Module. Ít nhất điều này có ý nghĩa với tôi.
FireDragon

@FireDragon Hoặc trường hợp sử dụng khác có thể là bạn muốn một lớp là Factory, mà các lớp con kế thừa từ đó và nhà máy của bạn chịu trách nhiệm tạo các thể hiện của các lớp con. Trong tình huống đó, Nhà máy của bạn không thể là một mô-đun vì bạn không thể kế thừa từ một mô-đun, vì vậy đó là lớp cha mẹ mà bạn không khởi tạo (và có thể 'không gian tên' đó là trẻ em nếu bạn muốn, giống như một mô-đun)
rmcsharry

15

Bạn có thể muốn sử dụng điều này để nhóm các lớp của bạn thành một mô-đun. Sắp xếp một thứ không gian tên.

ví dụ: đá quý Twitter sử dụng không gian tên để đạt được điều này:

Twitter::Client.new

Twitter::Search.new

Vì vậy, cả hai ClientSearchcác lớp sống theo Twittermô-đun.

Nếu bạn muốn kiểm tra các nguồn, mã cho cả hai lớp có thể được tìm thấy ở đâyđây .

Hi vọng điêu nay co ich!


3
Liên kết cập nhật Twitter gem: github.com/sferik/twitter/tree/master/lib/twitter
kode

6

Vẫn còn một sự khác biệt khác giữa các lớp lồng nhau và các mô đun lồng nhau trong Ruby trước 2.5 mà các câu trả lời khác không thể đưa ra mà tôi cảm thấy phải được đề cập ở đây. Đây là quá trình tra cứu.

Tóm lại: do việc tra cứu liên tục ở cấp cao nhất trong Ruby trước 2.5, Ruby có thể sẽ tìm kiếm lớp lồng nhau của bạn ở sai vị trí ( Objectđặc biệt) nếu bạn sử dụng các lớp lồng nhau.

Trong Ruby trước 2.5:
Cấu trúc lớp lồng nhau: Giả sử bạn có một lớp X, với lớp lồng nhau Y, hoặc X::Y. Và sau đó bạn có một lớp cấp cao nhất cũng được đặt tên Y. Nếu X::Ykhông được nạp, sau đó sau sẽ xảy ra khi bạn gọi X::Y:

Có không tìm thấy Ytrong X, Ruby sẽ cố gắng tìm kiếm nó trong tổ tiên của X. TừXlà một lớp và không phải là một mô-đun, nó có tổ tiên, trong số đó là [Object, Kernel, BasicObject]. Vì vậy, nó cố gắng tìm kiếm Ytrong đó Object, nơi nó tìm thấy nó thành công.

Tuy nhiên, nó là cấp cao nhất Yvà không X::Y. Bạn sẽ nhận được cảnh báo này:

warning: toplevel constant Y referenced by X::Y


Cấu trúc mô đun lồng nhau: Giả sử trong ví dụ trước Xlà mô đun chứ không phải lớp.

Một mô-đun chỉ có chính nó như tổ tiên: X.ancestorssẽ sản xuất [X].

Trong trường hợp này, Ruby sẽ không thể tìm kiếm Ymột trong những tổ tiên của Xvà sẽ ném một NameError. Rails (hoặc bất kỳ khung nào khác có tự động tải) sẽ cố gắng tải X::Ysau đó.

Xem bài viết này để biết thêm thông tin: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-group-as-a-namespace-in-rails-appluggest/

Trong Ruby 2.5: Loại
bỏ tra cứu liên tục cấp cao nhất.
Bạn có thể sử dụng các lớp lồng nhau mà không sợ gặp phải lỗi này.


3

Ngoài các câu trả lời trước: Module trong Ruby là một lớp

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object

11
Bạn sẽ chính xác hơn để nói rằng 'lớp trong Ruby là một mô-đun'!
Tim Diggins

2
Mọi thứ trong Ruby có thể là một đối tượng, nhưng gọi mô-đun một lớp có vẻ không đúng: irb (chính): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Chad M

và ở đây chúng ta đi đến định nghĩa của "là"
ahnbizcad

Lưu ý rằng các mô-đun không thể được khởi tạo. Tức là không thể tạo các đối tượng từ một mô-đun. Vì vậy, các mô-đun, không giống như các lớp, không có một phương thức new. Vì vậy, mặc dù bạn có thể nói Mô-đun là một lớp (chữ thường), chúng không giống với Lớp (chữ hoa) :)
rmcsharry
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.