루비 상속과 믹스 인


127

Ruby에서는 여러 믹스 인을 포함 할 수 있지만 하나의 클래스 만 확장하므로 믹스 인이 상속보다 선호되는 것 같습니다.

내 질문 : 유용하도록 확장 / 포함 해야하는 코드를 작성하는 경우 왜 클래스로 만들 것입니까? 아니면 달리 말하면 왜 항상 모듈로 만들지 않겠습니까?

클래스를 원하는 한 가지 이유 만 생각할 수 있으며 클래스를 인스턴스화 해야하는 경우입니다. 그러나 ActiveRecord :: Base의 경우 직접 인스턴스화하지 마십시오. 대신 모듈이 아니어야합니까?

답변:


176

나는 단지 이 주제에 대해 읽어 잘 접지 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의 자손으로 트럭에 전달되는 특성입니다. 또는 차량의 특수한 형태.


1
이 예는 Truck IS A Vehicle-차량이 아닌 트럭이 없다는 것을 깔끔하게 보여줍니다.
PL J

1
예는 깔끔하게 보여줍니다 Truck-IS A- Vehicle없습니다Truck 그렇지 않은 을Vehicle . 그러나 나는 아마도 모듈 SelfPropelable(:?) 흠 SelfPropeled소리를 낼 것입니다. 그러나 그것은 거의 같습니다 : D. 어쨌든 나는 그것을 포함하지 않을 Vehicle것이지만 Truck그 안에는없는 차량이 SelfPropeled있습니다. 또한 좋은 표시는 다른 차량이 아닌 다른 것들이 SelfPropeled있습니까? -아마도,하지만 찾기가 더 어려웠습니다. 따라서 VehicleSelfPropelling 클래스에서 상속받을 수 있습니다 (클래스로서 적합하지 않을 것입니다 SelfPropeled)
PL J

39

믹스 인은 좋은 아이디어라고 생각하지만 아무도 언급하지 않은 또 다른 문제가 있습니다 : 네임 스페이스 충돌. 치다:

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 Amodule B상수와 메서드가 네임 스페이스에 없을 가능성이 있는지 확인하십시오 . 문제는 충돌이 발생할 때 컴파일러가 전혀 경고하지 않는다는 것입니다.

나는이 행동이 대규모 프로그래머 팀으로 확장되지는 않는다고 주장한다. 구현하는 사람 class C이 범위 내의 모든 이름을 알고 있다고 가정해서는 안된다 . 루비는 심지어 다른 타입 의 상수 나 메소드를 재정의 할 수도 있습니다 . 나는 수있는 확실하지 않다 어느 올바른 행동으로 간주 될 수있다.


2
이것은 현명한주의 단어입니다. C ++의 다중 상속 함정을 연상시킵니다.
Chris Tonkinson

1
이것에 대한 좋은 완화가 있습니까? 이것은 파이썬 다중 상속이 탁월한 솔루션 인 이유처럼 보입니다 (언어 p * ssing match를 시작하지 않고이 특정 기능을 비교).
Marcin

1
@bazz 위대하고 훌륭하지만 대부분의 언어의 구성은 번거 롭습니다. 오리 형식 언어와도 관련이 있습니다. 또한 이상한 상태를 얻지 못할 수도 있습니다.
Marcin

오래된 게시물은 알고 있지만 여전히 검색에서 밝혀졌습니다. 대답은 부분적으로 잘못 - C#sayhi출력 B::HELLO때문이 아니라 루비 상수까지 믹스,하지만 가까이 먼에에서 루비 결의 상수 때문에 - 그래서 HELLO에서 참조는 B항상에 해결하는 것이다 B::HELLO. 클래스 C가 자신을 정의한 경우에도 C::HELLO마찬가지입니다.
Laas

13

내 수업 : 모듈은 동작을 공유하기위한 것이고 클래스는 객체 간의 관계를 모델링하기위한 것입니다. 기술적으로 모든 것을 Object의 인스턴스로 만들고 원하는 동작 집합을 얻으려는 모듈에 혼합 할 수는 있지만 열악하고 우연하며 읽을 수없는 디자인 일 것입니다.


2
이것은 직접적인 방식으로 질문에 대답합니다. 상속은 프로젝트를 더 읽기 쉽게 만들 수있는 특정 조직 구조를 시행합니다.
emery

10

귀하의 질문에 대한 답변은 상황에 따라 다릅니다. pubb의 관찰을 증류 할 때, 선택은 주로 고려중인 영역에 의해 결정됩니다.

그렇습니다. ActiveRecord는 서브 클래스에 의해 확장되는 것이 아니라 포함되어야합니다. 또 다른 ORM ( datamapper)은 그것을 정확하게 달성합니다!


4

Andy Gaskell의 답변을 매우 좋아합니다. ActiveRecord는 상속을 사용하지 말고 모델 / 클래스에 동작 (대부분 지속성)을 추가하는 모듈을 포함시켜야한다고 덧붙였습니다. ActiveRecord는 단순히 잘못된 패러다임을 사용하고 있습니다.

같은 이유로, MongoMapper보다 MongoId를 매우 좋아합니다. 개발자가 문제 영역에서 의미있는 것을 모델링하는 방법으로 상속을 사용할 수있는 기회를 제공하기 때문입니다.

Rails 커뮤니티의 어느 누구도 행동을 추가하는 것이 아니라 클래스 계층 구조를 정의하는 데 사용되는 방식으로 "루비 상속"을 사용하지 않는 것이 유감입니다.


1

믹스 인을 이해하는 가장 좋은 방법은 가상 클래스입니다. 믹스 인은 클래스 또는 모듈의 조상 체인에 주입 된 "가상 클래스"입니다.

"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>, ...

루비는 모듈이 클래스 / 모듈로 혼합 될 때 몇 가지 후크를 제공합니다. includedRuby에서 제공하는 후크 메소드로 일부 모듈이나 클래스에 모듈을 포함시킬 때마다 호출됩니다. 포함 된 것처럼 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

-1

지금은 template디자인 패턴 에 대해 생각하고 있습니다. 모듈에 만족스럽지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.