“상속성 구성”이“건조한 원칙”을 위반하고 있습니까?


36

예를 들어, 다른 클래스를 확장 할 클래스가 있다고 가정합니다.

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

일부 서브 클래스 :

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

사실, 서브 클래스에는 재정의 할 메소드가 없으며 일반적인 방법으로 HomePage에 액세스하지 않습니다.

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

로그인 페이지를 재사용하고 싶습니다. 그러나 https://stackoverflow.com/a/53354 에 따르면 LoginPage 인터페이스가 필요하지 않으므로 여기에서 작성을 선호해야하므로 상속을 사용하지 않습니다.

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

그러나 문제는 새 버전의 코드에서 발생합니다.

public LoginPage loginPage;

새 클래스가 추가되면 복제됩니다. LoginPage에 setter 및 getter가 필요한 경우 더 많은 코드를 복사해야합니다.

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

제 질문은 "상속성 구성"이 "건조한 원칙"을 위반하는 것입니까?


13
때로는 상속이나 구성이 필요하지 않습니다. 즉, 객체가 적은 클래스 하나가 작업을 수행한다고 말합니다.
Erik Eidt

23
왜 모든 페이지가 로그인 페이지가 되길 원합니까? 아마도 로그인 페이지가있을 것입니다.
Mr Cochese

29
그러나 상속을 extends LoginPage통해 모든 곳에서 복제 가 이루어집니다. 메이트 확인!
el.pescado

2
마지막 스 니펫에 문제가있는 경우 게터와 세터를 과도하게 사용했을 수 있습니다. 그들은 캡슐화를 위반하는 경향이 있습니다.
Brian McCutchon

4
따라서 페이지를 장식하고 LoginPage데코레이터를 만들 수 있습니다 . 더 이상 중복되지 않고 간단하게 page = new LoginPage(new EditInfoPage())완료됩니다. 또는 open-closed-principle을 사용하여 모든 페이지에 동적으로 추가 할 수있는 인증 모듈을 만듭니다. 코드 복제를 처리하는 많은 방법이 있으며 주로 새로운 추상화를 찾는 것과 관련이 있습니다. LoginPage잘못된 이름 일 가능성이 높습니다. 사용자가 해당 페이지를 탐색하는 동안 사용자를 인증 하고 해당 페이지로 리디렉션되는 동안 인증LoginPage 되지 않았거나 올바른 오류 메시지가 표시되지 않았는지 확인해야합니다.
Polygnome

답변:


46

당신은 반복되는 것이 걱정 되길 기다립니다

public LoginPage loginPage;

두 곳에서 DRY를 위반합니까? 그 논리에 의해

int x;

이제 전체 코드베이스에서 하나의 객체에만 존재할 수 있습니다. 블리.

DRY는 명심해야 할 것이지만 계속하십시오. 게다가

... extends LoginPage

DRY에 대한 항문조차도 당신의 대안으로 복제되고 있습니다.

유효한 DRY 문제는 여러 장소에서 정의 된 여러 장소에서 필요한 동일한 행동에 중점을 두어이 행동을 변경해야 할 경우 여러 장소를 변경해야합니다. 한 곳에서 결정을 내리면 한 곳에서만 변경하면됩니다. 하나의 객체 만이 LoginPage에 대한 참조를 보유 할 수있는 것은 아닙니다.

DRY는 맹목적으로 따라 가지 않아야합니다. 복사 및 붙여 넣기가 좋은 방법이나 클래스 이름을 생각하는 것보다 쉬워서 복제하는 경우 아마도 잘못된 것입니다.

그러나 다른 장소에 다른 책임이 있고 독립적으로 변경해야 할 가능성이 있기 때문에 같은 장소에 동일한 코드를 넣고 싶다면 DRY 시행을 완화하고 동일한 행동이 다른 정체성을 갖도록하는 것이 현명 할 것입니다 . 마법의 숫자를 금지하는 것과 같은 생각입니다.

DRY는 코드의 모양에 관한 것이 아닙니다. 마음이없는 반복으로 아이디어의 세부 사항을 확산시키지 않는 것이 중요합니다. 무의미한 반복은 사물이 실제로 나쁜 방향으로 향하고 있다는 관습이라는 것을 스스로에게 말하려고 할 때입니다.

당신이 정말로 불평하려고 생각하는 것을 상용구 코드라고합니다. 예, 상속보다는 구성을 사용하려면 상용구 코드가 필요합니다. 무료로 노출되는 것은 없습니다. 노출하는 코드를 작성해야합니다. 보일러 플레이트는 상태의 유연성, 노출 된 인터페이스를 좁히는 기능, 추상화 수준에 적합한 다른 이름을 부여 할 수 있으며, 훌륭한 ol 간접적 인 정보를 제공합니다. 내부, 그래서 당신은 정상적인 인터페이스에 직면하고 있습니다.

그러나 예, 추가 키보드 입력이 많이 있습니다. 코드를 읽을 때 상속 스택을 위아래로 튀는 요요 문제 를 예방할 수 있다면 가치가 있습니다.

이제는 상속을 거부하는 것이 아닙니다. 내가 가장 좋아하는 용도 중 하나는 예외에 새로운 이름을 부여하는 것입니다.

public class MyLoginPageWasNull extends NullPointerException{}

int x;코드베이스에 더 이상 제로 시간 이상 존재하지 않을 수 있습니다
K. 앨런 베이츠

@ K.AlanBates 실례합니다 ?
candied_orange

... 포인트 클래스 lol로 레토르트 할 줄 알았습니다. 포인트 클래스에 관심이있는 사람은 없습니다. 코드베이스로 들어가서 int a; int b; int x;'당신'을 제거하도록 강요 할 것입니다.
K. Alan Bates

@ K.AlanBates 와우. 이런 종류의 행동이 당신을 좋은 코더로 만들 것이라고 생각하는 것은 슬픈 일입니다. 최고의 코더는 다른 사람들에 대해 가장 나쁜 것들을 찾을 수있는 것이 아닙니다. 나머지를 더 좋게 만드는 것입니다.
candied_orange

무의미한 이름을 사용하는 것은 시작이 아니며 해당 코드의 개발자가 자산이 아닌 책임을 지도록합니다.
K. Alan Bates

127

DRY 원칙에 대한 일반적인 오해는 그것이 반복되는 코드 라인과 관련이 없다는 것입니다. DRY 원칙은 "모든 지식은 시스템 내에서 하나의 명백하고 권위있는 표현을 가져야합니다" 입니다. 코드가 아니라 지식에 관한 것입니다.

LoginPage로그인을 위해 페이지를 그리는 방법에 대해 알고 있습니다. EditInfoPage이를 수행하는 방법을 알고 있다면 이는 위반입니다. LoginPage비아 컴포지션 포함 은 DRY 원칙을 위반 하지 않습니다 .

DRY 원칙은 소프트웨어 엔지니어링에서 가장 잘못 사용되는 원칙 일 수 있으며 항상 코드를 복제하지 않는 원칙이 아니라 추상 영역 지식을 복제하지 않는 원칙으로 생각해야합니다. 실제로, 많은 경우 DRY를 올바르게 적용하면 코드를 복제하게되므로 반드시 나쁜 것은 아닙니다.


27
"단일 책임 원칙"은 훨씬 더 잘못 사용됩니다. 쉽게 두 번째로 :-)에 올 수도 "그나마 자신 반복"
gnasher729

28
"DRY"는 캐치 프레이즈 인 "반복하지 말아요"의 약자이지만 원칙 이름은 아닙니다 . 원칙의 실제 이름은 "Once And Only Once"입니다. 몇 단락을 설명하는 원칙을 위해 중요한 것은, 단락의 몇 가지를 이해하는 하지 세 글자 D, R,와 Y 암기
요 르그 W MITTAG

4
@ gnasher729 아시다시피, 실제로 SRP가 더 많이 오용 될 것입니다. 공평하지만, 많은 경우에 나는 종종 함께 오용되는 것으로 생각합니다. 저의 이론은 일반적으로 프로그래머가 "쉬운 이름"으로 두문자어를 사용한다고 믿어 선 안된다는 것입니다.
wasatz

17
IMHO 이것은 좋은 답변입니다. 그러나 공정하게 : 내 경험에 따르면 DRY 위반의 가장 빈번한 형태는 복사 붙여 넣기 프로그래밍과 코드 복제로 인해 발생합니다. 누군가가 한 번은 "일부 코드를 복사하여 붙여 넣을 때마다 중복 된 부분을 공통 기능으로 추출 할 수없는 경우 두 번 생각하십시오"라고 말했습니다.
Doc Brown

9
복사-붙여 넣기의 남용은 확실히 DRY를 위반하는 원동력이지만 단순히 복사-붙여 넣기를 금지하는 것은 DRY 수행에 대한 지침이 아닙니다. 그 방법이 다른 지식을 나타낼 때 동일한 코드로 두 가지 방법을 갖는 것에 대해 전혀 후회하지 않을 것입니다. 그들은 두 가지 책임을진다. 그들은 오늘날 동일한 코드를 가지고 있습니다. 독립적으로 자유롭게 변경할 수 있어야합니다. 예, 지식이 아니라 코드입니다. 잘 넣어 나는 다른 답변 중 하나를 썼지 만 이것에 절할 것입니다. +1.
candied_orange

12

짧은 대답 : 그렇습니다.

언뜻보기에 상속은 때때로 "내 재사용 클래스에 모든 공용 메소드와 속성이 1 : 1 방식으로 포함됩니다"라고 말하는 효과가 있기 때문에 코드의 일부를 절약 할 수 있습니다. 따라서 구성 요소에 10 개의 메서드 목록이 있으면 상속 된 클래스의 코드에서 해당 메서드를 반복 할 필요가 없습니다. 컴포지션 시나리오에서 해당 10 개의 메소드 중 9 개가 재사용 컴포넌트를 통해 공개적으로 노출되어야하는 경우, 9 개의 위임 호출을 작성하고 나머지는 제거해야합니다.

이것이 왜 참을 수 있습니까? 컴포지션 시나리오에서 복제 된 메소드를 살펴보십시오. 이러한 메소드는 독점적으로 컴포넌트 인터페이스에 대한 호출을 위임하므로 실제 로직은 포함되지 않습니다.

DRY 원칙의 핵심은 동일한 논리 규칙 이 인코딩 되는 코드에서 두 위치를 사용하지 않는 것입니다. 이러한 논리 규칙이 변경 될 때 DRY가 아닌 코드에서는 해당 위치 중 하나를 쉽게 적용하고 다른 위치를 잊어 버리기 때문에 오류가 발생합니다.

그러나 위임 호출에는 논리가 포함되어 있지 않기 때문에 일반적으로 이러한 변경의 대상이 아니므로 "구성보다 상속을 선호"할 때 실제 문제가 발생하지 않습니다. 그리고 컴포넌트를 사용하는 모든 클래스에서 공식적인 변경을 유발할 수있는 컴포넌트의 인터페이스가 변경 되더라도 컴파일러는 호출자 중 하나를 변경하는 것을 잊었을 때 알려줍니다.

당신의 예제에 대한주의 사항 : 나도 몰라 방법 HomePageEditInfoPage같이, 그러나 그들은 로그인 기능을하고, 경우 HomePage(또는이 EditInfoPage) A는 LoginPage 다음 상속 여기에 올바른 도구가 될 수 있습니다. 구성이보다 분명한 방식으로 더 나은 도구가 될 수있는 논란의 여지가없는 예는 아마도 상황을 더 명확하게 만들 것입니다.

어느 가정 HomePage이나은 EditInfoPage각각 A LoginPage, 그리고 당신이 쓴대로 하나의 일부 부품이 필요 꽤 가능성이보다 하나, 후자를 재사용하고 싶어 LoginPage하지 다. 이 경우 표시된 방식으로 컴포지션을 사용하는 것보다 더 나은 방법이 있습니다.

  • 재사용 가능한 부분을 LoginPage자체 구성 요소로 추출

  • 해당 구성 요소의 재사용 HomePageEditInfoPage그 내부에 현재 사용되는 것과 동일한 방법으로LoginPage

그렇게하면 상속에 대한 구성이 왜 그리고 언제 올바른 접근법인지가 더 명확해질 것입니다.

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