레일 : 데메테르 혼란의 법칙


13

저는 Rails AntiPatterns라는 책을 읽고 있습니다. 그들은 Demeter of Demeter를 위반하지 않기 위해 위임을 사용하는 것에 대해 이야기합니다. 주요 예는 다음과 같습니다.

그들은 컨트롤러에서 이와 같은 것을 호출하는 것이 좋지 않다고 생각합니다 (그리고 나는 동의합니다)

@street = @invoice.customer.address.street

제안 된 해결책은 다음을 수행하는 것입니다.

class Customer

    has_one :address
    belongs_to :invoice

    def street
        address.street
    end
end

class Invoice

    has_one :customer

    def customer_street
        customer.street
    end
end

@street = @invoice.customer_street

그들은 당신이 하나의 점만을 사용하기 때문에 여기서 데메테르 법칙을 어 기지 않는다고 말합니다. 인보이스의 거리를 얻기 위해 고객이 주소를 통과하기 때문에 여전히 잘못된 것으로 생각합니다. 나는 주로 내가 읽은 블로그 게시물 에서이 아이디어를 얻었습니다.

http://www.dan-manges.com/blog/37

블로그 게시물에서 주요 예는

class Wallet
  attr_accessor :cash
end
class Customer
  has_one :wallet

  # attribute delegation
  def cash
    @wallet.cash
  end
end

class Paperboy
  def collect_money(customer, due_amount)
    if customer.cash < due_ammount
      raise InsufficientFundsError
    else
      customer.cash -= due_amount
      @collected_amount += due_amount
    end
  end
end

블로그 게시물에는 customer.cash대신 점이 하나만 있지만 customer.wallet.cash이 코드는 여전히 Demeter의 법칙을 위반한다고합니다.

이제 Paperboy collect_money 메소드에는 두 개의 점이 없으며 "customer.cash"에 하나의 점만 있습니다. 이 대표단이 우리의 문제를 해결 했습니까? 전혀. 우리가 그 행동을 살펴보면, 한 신문 소년이 여전히 현금을 얻기 위해 고객의 지갑에 직접 닿고 있습니다.

편집하다

본인은 이것이 여전히 위반이며 본인에 대한 Wallet지불을 처리하고 Customer클래스 내에서 해당 메소드를 호출해야하는 인출이라는 메소드를 작성해야한다는 것을 완전히 이해하고 동의합니다 . 내가 얻지 못하는 것은이 과정에 따르면 첫 번째 예제 Invoice는 여전히 Customer거리를 얻기 위해 직접 도달 하기 때문에 데 미터 법칙을 위반한다는 것 입니다.

누군가 혼란을 해결하도록 도와 줄 수 있습니까? 지난 2 일 동안이 주제에 대해 알아 보려고했지만 여전히 혼란 스럽습니다.


2
비슷한 질문 여기에
뮐러 토르스텐

나는 블로그의 두 번째 예 (종이 소년)가 데 미터 법칙을 위반한다고 생각하지 않습니다. 디자인이 좋지 않을 수 있지만 (고객이 현금으로 지불한다고 가정하는 경우) Demeter of Demeter 위반이 아닙니다. 이 법을 위반하여 모든 설계 오류가 발생하는 것은 아닙니다. 저자는 혼란스러운 IMO입니다.
Andres F.

답변:


24

첫 번째 예는 데메테르 법칙을 위반 하지 않습니다 . 그렇습니다. 코드 가있는 상태에서 가설 @invoice.customer_street과 같은 을 얻을 수 있다고 @invoice.customer.address.street말하지만 순회의 각 단계에서 반환되는 값은 요청 된 객체에 의해 결정됩니다 . "지갑이 고객에게 현금을 요구하면 고객 은 자신의 지갑에서 현금을 얻습니다 ".

당신이 말할 때 @invoice.customer.address.street, 당신은 고객에 대한 지식을 가지고 있다고 가정하고 내부를 언급합니다- 이것은 나쁜 것입니다. 당신이 말할 때 @invoice.customer_street, 당신은 invoice"이봐, 나는 고객의 거리 를 원한다 . 고객은 다음 "이봐, 난 당신의 길을 싶습니다, 그 주소로 말한다 당신은 당신이 그것을 얻을 방법을 결정 ".

데메테르의 추력은 없다 '혹시 알 수없는 값을 멀리 "당신의 그래프에서 개체에서를, 대신이다'당신 스스로 값을 얻기 위해 지금까지 객체 그래프를 따라 통과 할 수 없습니다 '.

나는 이것이 미묘한 차이처럼 보일 수도 있지만 이것을 고려하십시오 : Demeter 호환 코드에서 변경의 내부 표현시 얼마나 많은 코드가 address변경되어야합니까? Demeter 호환이 아닌 코드는 어떻습니까?


이것은 내가 찾던 일종의 설명입니다! 감사합니다.
user2158382

아주 좋은 설명입니다. 질문이 있습니다. 1) 송장 개체가 고객 개체를 송장 클라이언트에 반환하려는 경우 반드시 내부적으로 보유하고있는 것과 동일한 고객 개체라는 의미는 아닙니다. 여러 값을 가진 멋진 패키지 데이터 세트를 클라이언트에 반환하기 위해 즉석에서 작성된 객체 일 수 있습니다. 제시 한 논리를 사용하면 송장에 둘 이상의 데이터를 나타내는 필드를 가질 수 없습니다. 아니면 뭔가 빠졌습니까?
zumalifeguard 22. 32.

2

첫 번째 예와 두 번째 예는 실제로 동일하지 않습니다. "하나 개의 점"의 일반적인 규칙, 더 OO 디자인에서 다른 것들에 대한 두 번째 회담에 대한 첫 번째 이야기는 특히 동안 " 에게 수행은 물어 보지 "

위임은 Demeter of Demeter 위반을 피하기위한 효과적인 기술이지만 속성이 아닌 동작에만 적용됩니다. -두 번째 예에서 Dan의 블로그

다시, " 특성에 대한 것이 아니라 행동에 대한 것 "

당신이 속성에 대해 묻는다면, 당신이 해야하는 요청 . "야, 얘야, 주머니에 돈이 얼마나 있니? 보여줘, 내가 지불 할 수 있는지 평가 해 볼게." 그건 잘못된 일입니다. 어떤 점원도 이런 식으로 행동하지 않을 것입니다. 대신 "제발 지불하십시오"라고 말할 것입니다.

customer.pay(due_amount)

지불 여부와 지불 가능 여부를 평가하는 것은 고객의 의무입니다. 그리고 점원의 임무는 고객에게 지불을 요청한 후에 완료됩니다.

그렇다면 두 번째 예는 첫 번째 예가 잘못되었음을 증명합니까?

내 의견으로는. 아니오 , 다음과 같은 경우 :

1. 당신은 자기 제약으로 그것을합니다.

@invoice위임으로 모든 고객 속성에 액세스 할 수 있지만 일반적인 경우에는 거의 필요하지 않습니다.

Rails 앱에서 인보이스를 보여주는 페이지를 생각해보십시오. 고객의 세부 정보를 보여주는 섹션이 상단에 있습니다. 따라서 인보이스 템플릿에서 다음과 같이 코드를 작성 하시겠습니까?

#customer-info
  = @invoice.customer_name
  = @invoice.customer_address
  ....

그것은 잘못되고 비효율적입니다. 더 나은 접근법은

#customer-info
  = render partial: 'invoice_header_customer', 
           locals: {customer: @invoice.customer}

그런 다음 고객이 부분적으로 모든 속성을 처리하도록하여 고객에게 속하게하십시오.

따라서 일반적으로 필요하지 않습니다. 그러나 모든 최근 송장을 보여주는 목록 페이지가있을 수 있으며 각 li고객의 이름을 표시 하는 브리핑 필드 가 있습니다. 이 경우 고객의 속성을 표시해야하며 템플릿을 다음과 같이 코딩하는 것이 합법적입니다.

= @invoice.customer_name

2.이 메소드 호출에 따라 추가 조치가 없습니다.

위의 목록 페이지에서 송장은 고객의 이름 속성을 요청했지만 실제 목적은 " 이름 표시 "이므로 기본적으로 여전히 동작 이지만 속성은 아닙니다 . 이 속성을 기반으로 한 추가 평가 및 조치는 없습니다. 귀하의 이름이 "마이크"인 경우 귀하를 좋아하고 30 일 동안 더 많은 크레딧을 제공합니다. 아니요, 인보이스는 더 이상 "이름을 보여주세요"라고 말합니다. 따라서 예제 2의 "Tell Do n't Ask"규칙에 따라 완전히 허용됩니다.


0

두 번째 기사에서 더 자세히 읽고 아이디어가 더 명확해질 것이라고 생각합니다. 이 아이디어는 고객에게 사례가 보관 된 곳 을 지불 하고 완전히 숨길 수있는 기능을 제공합니다 . 필드입니까, 지갑의 구성원입니까, 아니면 다른 것입니까? 호출자는 알지 못하고 알 필요가 없으며 구현 세부 사항이 변경되면 변경하지 않습니다.

class Wallet
  attr_accessor :cash
  def withdraw(amount)
     raise InsufficientFundsError if amount > cash
     cash -= amount
     amount
  end
end
class Customer
  has_one :wallet
  # behavior delegation
  def pay(amount)
    @wallet.withdraw(amount)
  end
end
class Paperboy
  def collect_money(customer, due_amount)
    @collected_amount += customer.pay(due_amount)
  end
end

따라서 두 번째 참조가 더 유용한 권장 사항을 제공한다고 생각합니다.

"하나의 점"만의 아이디어는 부분적인 성공인데, 세부적인 부분은 숨기지 만 여전히 별도의 구성 요소 사이의 결합을 증가시킵니다.


미안하지만 명확하지는 않았지만 두 번째 예를 완벽하게 이해했으며 게시 한 추상화를 만들어야한다는 것을 이해하지만 이해하지 못하는 것은 첫 번째 예입니다. 블로그 게시물에 따르면, 첫 번째 예가 잘못되었습니다
user2158382

0

Dan이이 기사에서 Paperboy, The Wallet, The Law Of Demeter와 같은 사례를 도출 한 것 같습니다 .

Law of Demeter 객체의 메서드는 다음과 같은 종류의 객체의 메서드 만 호출해야합니다.

  1. 그 자체
  2. 그 매개 변수
  3. 생성 / 인스턴스화하는 모든 개체
  4. 직접 구성 요소 객체

데메테르 법칙을 적용하는시기와 방법

이제 법을 잘 이해하고 이점을 얻었지만, 기존 코드에서 적용 할 수있는 장소를 식별하는 방법 (적용 할 수없는 경우)을 아직 논의하지 않았습니다.

  1. 일련의 'get'선언문 -Demeter of Law를 적용하는 가장 명백한 첫 번째 장소는 반복되는 get() 설명 이있는 코드의 장소입니다 .

    value = object.getX().getY().getTheValue();

    마치이 사례의 정식 인물이 경찰에 의해 인계 될 때처럼

    license = person.getWallet().getDriversLicense();

  2. 많은 '임시'객체 -코드가 다음과 같은 경우 위의 라이센스 예제가 더 나을 것입니다.

    Wallet tempWallet = person.getWallet(); license = tempWallet.getDriversLicense();

    동일하지만 감지하기가 더 어렵습니다.

  3. 많은 클래스 가져 오기 -작업중인 Java 프로젝트에는 실제로 사용하는 클래스 만 가져 오는 규칙이 있습니다. 당신은 같은 것을 보지 못합니다

    import java.awt.*;

    소스 코드에서. 이 규칙을 적용하면 12 개 정도의 import 문이 모두 같은 패키지에서 나오는 것이 드문 일이 아닙니다. 코드에서 이런 일이 발생하면 모호한 위반 사례를 찾아 보는 것이 좋습니다. 가져와야 할 경우 연결됩니다. 변경되는 경우에도 필요할 수 있습니다. 클래스를 명시 적으로 가져 오면 클래스의 실제 결합 정도를 알 수 있습니다.

귀하의 예가 Ruby로되어 있음을 이해하지만 모든 OOP 언어에 적용되어야합니다.

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