답변:
나는 단지 이 주제에 대해 읽어 잘 접지 Rubyist (그런데 좋은 책). 저자는 내가 설명하는 것보다 더 나은 설명을 해주므로 인용하겠습니다.
단일 규칙이나 공식이 항상 올바른 디자인을 만들지는 않습니다. 그러나 클래스 대 모듈 결정을 내릴 때 몇 가지 고려 사항을 명심하는 것이 유용합니다.
모듈에는 인스턴스가 없습니다. 엔터티 또는 사물은 일반적으로 클래스에서 가장 잘 모델링되고 엔터티 또는 사물의 특성 또는 속성은 모듈에서 가장 잘 캡슐화됩니다. 이에 따라 4.1.1 절에서 언급 한 바와 같이 클래스 이름은 명사 인 경향이 있지만 모듈 이름은 형용사 (스택 대 스택 형)입니다.
클래스는 하나의 슈퍼 클래스 만 가질 수 있지만 원하는만큼 많은 모듈을 혼합 할 수 있습니다. 상속을 사용하는 경우 합리적인 수퍼 클래스 / 서브 클래스 관계를 만드는 데 우선 순위를 둡니다. 클래스의 유일한 수퍼 클래스 관계를 사용하여 클래스에 여러 특성 세트 중 하나 일 수있는 것을 부여하십시오.
한 가지 예에서 이러한 규칙을 요약하면 다음과 같이하지 않아야합니다.
module Vehicle
...
class SelfPropelling
...
class Truck < SelfPropelling
include Vehicle
...
오히려 다음을 수행해야합니다.
module SelfPropelling
...
class Vehicle
include SelfPropelling
...
class Truck < Vehicle
...
두 번째 버전은 엔티티와 속성을 훨씬 더 깔끔하게 모델링합니다. 트럭은 Vehicle의 의미에 해당하는 반면, SelfPropelling은 차량의 특성 (적어도이 세계 모델에서 우리가 관심을 갖는 모든 것)입니다. 이는 Truck의 자손으로 트럭에 전달되는 특성입니다. 또는 차량의 특수한 형태.
Truck
-IS A- Vehicle
없습니다Truck
그렇지 않은 을Vehicle
. 그러나 나는 아마도 모듈 SelfPropelable
(:?) 흠 SelfPropeled
소리를 낼 것입니다. 그러나 그것은 거의 같습니다 : D. 어쨌든 나는 그것을 포함하지 않을 Vehicle
것이지만 Truck
그 안에는없는 차량이 SelfPropeled
있습니다. 또한 좋은 표시는 다른 차량이 아닌 다른 것들이 SelfPropeled
있습니까? -아마도,하지만 찾기가 더 어려웠습니다. 따라서 Vehicle
SelfPropelling 클래스에서 상속받을 수 있습니다 (클래스로서 적합하지 않을 것입니다 SelfPropeled
)
믹스 인은 좋은 아이디어라고 생각하지만 아무도 언급하지 않은 또 다른 문제가 있습니다 : 네임 스페이스 충돌. 치다:
module A
HELLO = "hi"
def sayhi
puts HELLO
end
end
module B
HELLO = "you stink"
def sayhi
puts HELLO
end
end
class C
include A
include B
end
c = C.new
c.sayhi
어느 쪽이 이길까요? 루비에서는 후자 module B
를 포함합니다 module A
. 이제이 문제를 피하는 것이 쉽습니다. 모든 module A
및 module B
상수와 메서드가 네임 스페이스에 없을 가능성이 있는지 확인하십시오 . 문제는 충돌이 발생할 때 컴파일러가 전혀 경고하지 않는다는 것입니다.
나는이 행동이 대규모 프로그래머 팀으로 확장되지는 않는다고 주장한다. 구현하는 사람 class C
이 범위 내의 모든 이름을 알고 있다고 가정해서는 안된다 . 루비는 심지어 다른 타입 의 상수 나 메소드를 재정의 할 수도 있습니다 . 나는 수있는 확실하지 않다 어느 올바른 행동으로 간주 될 수있다.
C#sayhi
출력 B::HELLO
때문이 아니라 루비 상수까지 믹스,하지만 가까이 먼에에서 루비 결의 상수 때문에 - 그래서 HELLO
에서 참조는 B
항상에 해결하는 것이다 B::HELLO
. 클래스 C가 자신을 정의한 경우에도 C::HELLO
마찬가지입니다.
귀하의 질문에 대한 답변은 상황에 따라 다릅니다. pubb의 관찰을 증류 할 때, 선택은 주로 고려중인 영역에 의해 결정됩니다.
그렇습니다. ActiveRecord는 서브 클래스에 의해 확장되는 것이 아니라 포함되어야합니다. 또 다른 ORM ( datamapper)은 그것을 정확하게 달성합니다!
Andy Gaskell의 답변을 매우 좋아합니다. ActiveRecord는 상속을 사용하지 말고 모델 / 클래스에 동작 (대부분 지속성)을 추가하는 모듈을 포함시켜야한다고 덧붙였습니다. ActiveRecord는 단순히 잘못된 패러다임을 사용하고 있습니다.
같은 이유로, MongoMapper보다 MongoId를 매우 좋아합니다. 개발자가 문제 영역에서 의미있는 것을 모델링하는 방법으로 상속을 사용할 수있는 기회를 제공하기 때문입니다.
Rails 커뮤니티의 어느 누구도 행동을 추가하는 것이 아니라 클래스 계층 구조를 정의하는 데 사용되는 방식으로 "루비 상속"을 사용하지 않는 것이 유감입니다.
믹스 인을 이해하는 가장 좋은 방법은 가상 클래스입니다. 믹스 인은 클래스 또는 모듈의 조상 체인에 주입 된 "가상 클래스"입니다.
"include"를 사용하여 모듈에 전달하면 상속받은 클래스 바로 앞에 모듈을 상위 체인에 추가합니다.
class Parent
end
module M
end
class Child < Parent
include M
end
Child.ancestors
=> [Child, M, Parent, Object ...
Ruby의 모든 객체에는 싱글 톤 클래스가 있습니다. 이 싱글 톤 클래스에 추가 된 메소드는 오브젝트에서 직접 호출 할 수 있으므로 "클래스"메소드로 작동합니다. 객체에 "extend"를 사용하고 객체에 모듈을 전달하면 모듈의 메소드가 객체의 싱글 톤 클래스에 추가됩니다.
module M
def m
puts 'm'
end
end
class Test
end
Test.extend M
Test.m
singleton_class 메소드를 사용하여 싱글 톤 클래스에 액세스 할 수 있습니다.
Test.singleton_class.ancestors
=> [#<Class:Test>, M, #<Class:Object>, ...
루비는 모듈이 클래스 / 모듈로 혼합 될 때 몇 가지 후크를 제공합니다. included
Ruby에서 제공하는 후크 메소드로 일부 모듈이나 클래스에 모듈을 포함시킬 때마다 호출됩니다. 포함 된 것처럼 extended
확장을위한 관련 후크가 있습니다. 다른 모듈이나 클래스에 의해 모듈이 확장 될 때 호출됩니다.
module M
def self.included(target)
puts "included into #{target}"
end
def self.extended(target)
puts "extended into #{target}"
end
end
class MyClass
include M
end
class MyClass2
extend M
end
이는 개발자가 사용할 수있는 흥미로운 패턴을 만듭니다.
module M
def self.included(target)
target.send(:include, InstanceMethods)
target.extend ClassMethods
target.class_eval do
a_class_method
end
end
module InstanceMethods
def an_instance_method
end
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end
class MyClass
include M
# a_class_method called
end
보시다시피이 단일 모듈은 인스턴스 메소드, "클래스"메소드를 추가하고 대상 클래스에서 직접 작동합니다 (이 경우 a_class_method () 호출).
ActiveSupport :: Concern은이 패턴을 캡슐화합니다. ActiveSupport :: Concern을 사용하도록 다시 작성된 동일한 모듈은 다음과 같습니다.
module M
extend ActiveSupport::Concern
included do
a_class_method
end
def an_instance_method
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end