메쏘드 체이닝 vs 캡슐화


17

메소드 체인과 "단일 액세스 포인트"메소드의 고전적인 OOP 문제가 있습니다.

main.getA().getB().getC().transmogrify(x, y)

vs

main.getA().transmogrifyMyC(x, y)

첫 번째 클래스는 각 클래스가 더 작은 작업 집합에 대해서만 책임을지고 모든 것을 훨씬 모듈 방식으로 만드는 이점이있는 것 같습니다 .C에 메소드를 추가하는 것이 A, B 또는 C에서 노출시키지 않아도됩니다.

물론 단점은 캡슐화 가 약해서 두 번째 코드가 해결하는 것입니다. 이제 A는 그것을 통과하는 모든 메소드를 제어하고 원하는 경우 필드에 위임 할 수 있습니다.

나는 단일 솔루션이 없다는 것을 알고 있으며 물론 상황에 달려 있지만 두 스타일 간의 다른 중요한 차이점에 대한 의견을 듣고 싶습니다. 어떤 상황에서 두 스타일을 선호해야합니까? 지금 시도 할 때 일부 코드를 디자인하기 위해 인수를 사용하여 한 가지 방법을 결정하지 않는 것 같습니다.

답변:


25

데메테르법칙은 이것에 중요한 지침을 제공 한다고 생각합니다 .

Demeter of Law를 따를 때의 장점은 결과 소프트웨어가보다 유지 관리 가능하고 적응성이 좋은 경향이 있다는 것입니다. 객체는 다른 객체의 내부 구조에 덜 의존하기 때문에 호출자를 재 작업하지 않고도 객체 컨테이너를 변경할 수 있습니다.

Demeter of Law의 단점은 때때로 메소드 호출을 컴포넌트에 전파하기 위해 많은 수의 작은 "래퍼"메소드를 작성해야한다는 것입니다. 또한, 클래스의 인터페이스는 포함 된 클래스에 대한 메소드를 호스트 할 때 부피가 커져 응집력있는 인터페이스가없는 클래스가됩니다. 그러나 이것은 또한 잘못된 OO 디자인의 징후 일 수 있습니다.


그 법을 잊어 버렸습니다. 상기시켜 주셔서 감사합니다. 그러나 내가 여기서 요구하는 것은 주로 장단점이 무엇인지 , 또는 한 스타일을 다른 스타일보다 어떻게 사용해야하는지 더 정확하게 결정하는 것입니다.
Oak

@Oak, 장점과 단점을 설명하는 인용문을 추가했습니다.
Péter Török

10

나는 일반적으로 메소드 체인을 가능한 한 제한적으로 유지하려고합니다 ( Demeter of Demeter )

유일하게 예외는 유창한 인터페이스 / 내부 DSL 스타일 프로그래밍입니다.

Martin Fowler는 Domain-Specific-Languages 와 동일한 차이점을 만들지 만 위반 명령 쿼리 분리의 이유는 다음과 같습니다.

모든 메소드는 조치를 수행하는 명령이거나 호출자에게 데이터를 리턴하는 쿼리 여야하지만 둘다는 아니어야합니다.

70 페이지의 책에 나오는 파울러는 이렇게 말합니다.

커맨드-쿼리 분리는 프로그래밍에서 매우 귀중한 원칙이며, 팀이이를 사용하는 것이 좋습니다. 내부 DSL에서 Method Chaining을 사용한 결과 중 하나는 일반적으로이 원칙을 위반한다는 것입니다. 각 방법은 상태를 변경하지만 체인을 계속하기 위해 객체를 반환합니다. 나는 명령 쿼리 분리를 따르지 않는 사람들을 비난하는 데시벨을 많이 사용했으며 다시 그렇게 할 것입니다. 그러나 유창한 인터페이스는 다른 규칙 집합을 따르므로 여기서 허용하게되어 기쁩니다.


3

나는 당신이 적절한 추상화를 사용하고 있는지의 여부는 문제라고 생각합니다.

첫 번째 경우에는

interface IHasGetA {
    IHasGetB getA();
}

interface IHasGetB {
    IHasGetC getB();
}

interface IHasGetC {
    ITransmogrifyable getC();
}

interface ITransmogrifyable {
    void transmogrify(x,y);
}

main은 유형 IHasGetA입니다. 문제는 : 추상화가 적합한가? 답은 사소한 것이 아닙니다. 그리고이 경우에는 약간 떨어져 보이지만 어쨌든 이론적 인 예입니다. 그러나 다른 예를 구성하려면 다음을 수행하십시오.

main.getA(v).getB(w).getC(x).transmogrify(y, z);

종종보다 낫다

main.superTransmogrify(v, w, x, y, z);

후자의 예에서 두 때문에 thismain유형에 의존한다 v, w, x, yz. 또한 모든 메소드 선언에 6 개의 인수가있는 경우 코드가 실제로 더 좋아 보이지 않습니다.

서비스 로케이터에는 실제로 첫 번째 접근 방식이 필요합니다. 서비스 로케이터를 통해 생성 된 인스턴스에 액세스하고 싶지 않습니다.

따라서 개체를 통해 "연결"하면 많은 종속성이 생길 수 있으며 실제 클래스의 속성을 기반으로하는 경우 더 많은 종속성을 만들 수 있습니다.
그러나 추상화를 만드는 것, 즉 객체를 제공하는 것은 완전히 다른 것입니다.

예를 들어, 다음을 가질 수 있습니다.

class Main implements IHasGetA, IHasGetA, IHasGetA, ITransmogrifyable {
    IHasGetB getA() { return this; }
    IHasGetC getB() { return this; }
    ITransmogrifyable getC() { return this; }
    void transmogrify(x,y) {
        return x + y;//yeah!
    }
}

main의 인스턴스는 어디에 있습니까 Main? 클래스를 아는 main것이 IHasGetA대신에 종속성을 줄이면 Main실제로 커플 링이 매우 낮습니다. 호출 코드는 실제로 원래 객체의 마지막 메서드를 호출하고 있다는 사실조차 알지 못합니다. 실제로 분리의 정도를 보여줍니다.
구현의 내부에 깊숙이 아니라 간결하고 직교적인 추상화의 경로를 따라갑니다.


매개 변수 수가 크게 증가한다는 점에서 매우 흥미로운 점입니다.
Oak

2

@ Péter Török이 지적한 데 미터 의 법칙은 "소형"형태를 제안합니다.

또한 코드에서 명시 적으로 언급하는 메소드가 많을수록 클래스가 의존하는 클래스가 많을수록 유지 보수 문제가 증가합니다. 귀하의 예에서 간결한 양식은 두 개의 클래스에 의존하는 반면 더 긴 양식은 네 개의 클래스에 의존합니다. 더 긴 형태는 데메테르 법칙을 위반할뿐만 아니라; 또한 네 가지 참조 된 방법 중 하나를 변경할 때마다 코드를 변경합니다 (간결한 형태의 두 가지가 아닌).


반면에 맹목적으로 그 법을 따른다는 것은 A많은 방법으로 많은 방법 이 폭발 할 것이라는 것을 의미 A합니다. 여전히 의존성에 동의합니다. 클라이언트 코드에서 필요한 종속성의 양을 크게 줄입니다.
Oak

1
@Oak : 맹목적으로 행동하는 것은 결코 좋지 않습니다. 장단점을보고 증거에 따라 결정을 내려야합니다. 여기에는 Demeter의 법칙도 포함됩니다.
CesarGon

2

나는이 문제와 직접 씨름했다. 다른 객체에 깊이 도달하는 단점은 리팩토링을 수행 할 때 종속성이 많기 때문에 많은 코드를 변경해야한다는 것입니다. 또한 코드가 약간 부풀어지고 읽기가 더 어려워집니다.

반면에 단순히 메소드를 "통과"하는 클래스가 있다는 것은 여러 곳에서 여러 메소드를 선언해야하는 오버 헤드를 의미합니다.

이를 완화하고 일부 경우에 적합한 솔루션은 적절한 클래스에서 데이터 / 객체를 복사하여 일종의 파사드 객체를 작성하는 팩토리 클래스를 사용하는 것입니다. 그렇게하면 파사드 객체에 대해 코딩 할 수 있고 리팩터링 할 때 팩토리의 로직을 변경하기 만하면됩니다.


1

나는 종종 프로그램의 논리가 체인 된 방법으로 이해하기 쉽다는 것을 알게된다. 나에게 customer.getLastInvoice().itemCount()뇌보다 더 잘 맞는다 customer.countLastInvoiceItems().

여분의 커플 링을 유지해야하는 유지 관리 문제가 가치가 있는지 여부는 귀하에게 달려 있습니다. (나는 또한 작은 클래스에서 작은 함수를 좋아하기 때문에 체인을 연결하는 경향이 있습니다. 나는 그것이 옳다고 말하지는 않습니다-그것은 내가하는 일입니다.)


IMO customer.NrLastInvoices 또는 customer.LastInvoice.NrItems 여야합니다. 체인의 길이가 너무 길지 않기 때문에 조합의 양이 다소 많으면 아마 평평하지 않을 것입니다.
Homde
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.