2016-10-19 2 views
1

나는 "Ruby : 다른 클래스의 클래스를 완전히 투명하게 래핑 할 수 있습니까?

class IArray 
    attr_reader :array 

    # array is a Ruby Array 
    def initialize(array) 
    @array = array 
    end 

    def method_missing(...) 
    # forwards every call to @array 
    end 
end 

지금 내 코드에 내가 말하고,

a1 = [1, 2, 3] 
a2 = IArray.new([4, 5]) 
a1.concat(a2) 

는 마지막 문이 작동하지 않습니다 싶은 (더 복잡한 진짜 문제) 다음과 같은 클래스를 작성해야 a2를 배열로 암시 적으로 변환하지 않습니다. " a1a2이 배열이 아니라는 것을 어떻게 알 수 있습니까? 나는 is_a?kind_of?a2에 구현 했으므로 배열인지 묻는 메시지가 표시되면 tru e를 반환합니다. 내가 원하는 것은 a1a2이 배열이라고 생각하고 a2에있는 모든 메서드를 호출하여 병합을 수행해야한다는 것입니다.

나는 다른 어떤 클래스와도 똑같이하고 싶습니다. 즉, 클래스 안에 랩핑하기를 원하지만 래핑되지 않은 것처럼 작동하게하십시오.

+0

그냥'Array'에서'IArray'를 상속받을 수 있습니까? –

+2

_ 이상의 변환이 필요하지 않습니다. ""구현 한 is_a?와'kind_of?'는'a2' "_에 해당합니다. 게다가, 당신의 코드는 나를 위해 잘 작동합니다. 'concat'은'method_missing'에 의해 처리/전달되는'to_ary'를 호출합니다. – Stefan

+0

Stefan 저는 IArray 클래스에서 _is_a? _ 및 kind_of? _를 구현했으며 절대 호출되지 않습니다. concat이 to_ary를 호출하지만 to_ary의 반환 값은 배열이어야하며 제 경우에는 @array를 반환 할 수 없습니다. 호출이 method_missing을 통과하게하고 싶습니다. –

답변

2

아니요, 불행히도 한 객체가 다른 객체를 완전히 시뮬레이트 할 수 없습니다. 다른 것을 시뮬레이트하는 하나의 객체가 객체 지향의 토대 중 하나이고 Ruby가 객체 지향 언어이므로이 작업이 실제로 가능해야하기 때문에 이것은 불행한 제한입니다.

불가능 이유를 세 가지 이유가 있습니다 :

  • 참조 평등 : 참조 평등 (BasicObject#equal?가) OO하여 시뮬레이션을 중단하고,이. Reference Equality를 비교하면 시뮬레이션 대상과 시뮬레이션 대상을 구별 할 수 있지만 가능하지 않아야합니다. 원숭이 패치 BasicObject을 사용하여 equal?을 제거하거나 바꿀 수는 있지만 그럴 경우 왼쪽 및 오른쪽 문제가 발생할 수 있습니다.
  • 부울 연산자와 조건문은 : 부울 연산자와 조건문은 두 개의 싱글 객체 nilfalsefalsy이라고 생각. falsy 개체를 작성할 방법이 없기 때문에 nil 또는 false을 시뮬레이트 할 방법이 없습니다.
  • 프로토콜 대신 클래스 : 일부 핵심 라이브러리 및 표준 라이브러리 메소드 및 일부 하드 코딩 된 내부 클래스는 객체가 특정 클래스의 인스턴스가 될 것을 기대합니다. 동일한 프로토콜을 사용하는 객체는 해당 클래스와 관계없이 동일한 유형이어야합니다.

당신은하지만, 아주 멀리 얻을 수 있습니다 :

  • 거의 방법을 (실제로, 어떤 방법, 나는 믿지) 참조 평등의 핵심 라이브러리와 표준 라이브러리 검사에서.
  • nil 또는 false을 시뮬레이션 할 필요가 거의 없습니다.
  • 특정 서브 루틴이 특정 클래스를 필요로 할 때 거의 항상 간접 해치를 이스케이프 해치로 제공하고 일부 변환 방법을 호출합니다 (예 :단항 프리픽스 앰퍼샌드 & 연산자 * "스 플랫"연산자는 단항 to_a 호출 프리픽스 별표 print 통화 to_s, Array#[]to_int 통화 등), to_proc 부른다. 또한 최소한 Numeric 초 동안 정의 된 강제 프로토콜이 있습니다.

그러나, 특정 경우에, 당신은 단순히 작동하지 않습니다 이러한 경우 중 하나에 실행 to_ary 당신이 당신의 경우에, Array을 시뮬레이션 무언가를 만드는 향해 먼 길을 얻을 수있는 동안, 당신은 필요 을 계속 추적하지만 물론 Array으로 변환하면 ID와 추가 동작이 모두 손실됩니다. 불행히도, 당신은 망했다. 더 이상 할 수있는 일이 없습니다.

동일한 프로토콜을 사용하는 두 객체는 ​​동일한 유형으로 간주되어야하며 Array#concat은 인수가 Array의 인스턴스인지 여부를 신경 쓰지 않아야합니다 (또는로 변환 될 수 있음). 하나의)하지만 그 인수가 Array과 같은 프로토콜을 말하는지 (정확하게 말하자면 : concat실제로이 필요로하는 프로토콜의 부분 집합을 말합니다).

왜 이런 경우에 루비가 OO 패러다임을 따르지 않는지 추측 할 수 있습니다. 성능. OO에서 두 객체가 같은 유형 (또는 같은 클래스)이라도 한 객체는 다른 객체의 표현을 결코 알 수 없습니다. 이 사이의 근본적인 차이이다 객체 지향 추상 데이터 유형에 따라 데이터 추상화와 데이터 추상화 : ADT 인스턴스는 동일한 유형의 다른 인스턴스의 표현을 검사 할 수 있습니다 개체 수 어떤 다른 개체의하지 표현, 동일한 유형 (또는 클래스) 인 경우에도 마찬가지입니다.

그러나 이는 두 작업을 한꺼번에 검사 할 수 없다는 것을 의미합니다. 작업은 세 번째 개체의 메서드로, 두 개체의 표현을 검사 할 수 없거나 두 객체 중 하나의 메소드인데,이 경우 자신의 객체의 표현을 검사 할 수 있지만 다른 객체의 표현은 검사 할 수 없음), 이는 OO에서 두 객체의 표현에 대한 액세스가 필요한 작업을 한 번에 작성할 수 없음을 의미합니다.

예. 첫 번째 목록의 마지막 요소의 next 포인터에 액세스 할 수 있고 두 번째 목록의 첫 번째 요소의 prev 포인터에 액세스 할 수 있지만 OO에서는 두 목록 중 하나만 액세스 할 수있는 경우 두 개의 연결된 목록을 연결하는 것은 O (1)입니다. 명시 적으로 두 포인터에 대한 액세스를 제공하는 공용 메서드를 노출합니다. 그리고 배열을 다른 배열에 연결하려면 두 배열의 내부 표현에 대한 액세스가 빠르기 때문에 Ruby는 여기에서 OO 캡슐화를 중단하기로 결정하고 두 객체 모두 내부 메모리 레이아웃을 알고있는 클래스 여야합니다.

이것은 유감 스럽지만 완전히 OO는 아니지만 Smalltalk와 같은 "하드 코어 OO"언어조차도 핵심 데이터 유형 중 일부를 만들 수 있다는 단점이 있습니다. (예 : 숫자, 문자열, 배열 및 부울)

핵심 라이브러리의 상당 부분이 구현 내부에 대한 권한있는 액세스로 구현되는 YARV, JRuby 및 기타 구현과 같은 경우에는 다음과 같은 또 다른 문제가 있습니다. 보다 편리한 구현을 위해 루비 의미를 우회하는 핵심 방법에 대해 매우 유혹적이다.전혀 상관없는 예 : Enumerable#inject의 다양한 복잡한 "오버로드"를 C의 YARV 또는 Java의 JRuby에서 구현하는 것은 쉽습니다. YARV에서 C 함수는 인터프리터 내부에 대한 권한있는 액세스 권한을 가지며 전달 된 인수를 검사 할 수 있습니다 누군가가 Ruby에서 메소드를 다시 구현하려고 시도하는 방법은 JRuby에서 실제 자바 오버로드 된 메소드처럼 오버로드 된 메소드를 구현할 수있는 접착제 마법이 없다.

마찬가지로 모든 핵심 메소드는 구현의 GC 메모리에있는 객체의 내부 표현에 대한 권한있는 액세스를 가지므로 class, is_a?을 거치지 않고 객체의 클래스를 직접 검사하여 객체의 클래스를 검사합니다. kind_of? 또는 instance_of?

+0

Jörg ... 사려 깊은 대답에 감사드립니다 ... 내가 기대했던 것보다 훨씬 더! 제 해결책을 살펴보고 중단 할 수있는 부분과 누락 된 부분에 대해 의견을 나눌 수 있습니까? 감사!! –

1

SimpleDelegator을 살펴보십시오. 나는 그것이 당신의 필요를 충족시킬 것이라고 생각합니다.

+0

내가 필요로하는 것 때문에 작동하지 않는 것 같습니다. 어쩌면 내가 필요한 것을 왜 필요로하는지 (또는 누군가 완전히 다른 생각을 내놓을 수있는 이유)를 분명히 알 수있을 것이다. 나는 객체를 "주입"할 수있는 자바 라이브러리를 사용하고있다. JRuby로 작업하고 있기 때문에, 배열 객체는 라이브러리에 삽입되는 자바 객체 일뿐입니다. 불행히도 나는 루비 객체로 접근 할 수 없다. 라이브러리의 래핑 된 자바 객체가된다. 메서드를 호출하려면 라이브러리의 API를 사용해야합니다. 따라서이 API를 사용하여 호출을 포착하고 래핑 된 배열로 전달해야합니다. 더 명확 해? –

0

여기 내 (아마도 문제의 불완전한 해결책입니다).

class RBProxyObject 

    attr_reader :ruby_obj 

    def initialize(ruby_obj) 
    @ruby_obj = ruby_obj 
    end 

    def is_a?(klass) 
    @ruby_obj.is_a?(klass) 
    end 

    def kind_of?(klass) 
    @ruby_obj.is_a?(klass) 
    end 

    def method_missing(symbol, *args, &blk) 
    begin 
     @ruby_obj.send(symbol, *args, &blk) 
    rescue TypeError 
     args[0].native(symbol, @ruby_obj, &blk) 
    end 
    end 

    def native(*args) 
    method = args.shift 
    other = args.shift 
    other.send(method, @ruby_obj, *args) 
    end 

end 

을 그리고 지금 여기에 사용되는 방법입니다 :

a1 = [1, 2, 3] 
a2 = [4, 5] 

p1 = RBProxyObject.new(a1) 
p2 = RBProxyObject.new(a2) 


> p p1[0] 
    1 
> p p2[1] 
    5 

> p p1.is_a? Array 
    true 

> p p1.concat(p2) 
    [1, 2, 3, 4, 5] 

추가되어야 어떤 다른 방법이 작업을 위해 다음과 같은 순서로, 모든 루비 개체가 RBProxyObject에 "포장"해야 이 수업은 어디에서 깰 수 있습니까? 감사합니다 ...

관련 문제