인터페이스 분리 원리에 대한 두 가지 모순 된 정의 – 어느 것이 맞습니까?


14

ISP에서 기사를 읽을 때 ISP에 대해 두 가지 상반되는 정의가있는 것 같습니다.

첫 번째 정의에 따르면 ( 1 , 2 , 3 참조) ISP는 인터페이스를 구현하는 클래스가 필요하지 않은 기능을 구현하도록 강요해서는 안된다고 말합니다. 따라서 뚱뚱한 인터페이스IFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

더 작은 인터페이스로 분할되어야 ISmall_1하고ISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

이 방법 때문에 내 MyClass유일한 방법이 필요 (구현 할 수 D()C()도에 대한 더미 구현을 제공하도록 강요하지 않고,) A(), B()C():

그러나 두 번째 정의에 따르면 ( 1 , 2 , Nazar Merza의 답변 참조 ) ISP는 MyClient호출하는 메소드 가 필요하지 않은 MyService메소드를 인식해서는 안된다고 MyService말합니다. 즉, 및 MyClient의 기능 만 필요한 경우 대신C()D()

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

MyService's메소드를 클라이언트 별 인터페이스 로 분리해야합니다 .

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

따라서 이전 정의에서 ISP의 목표는 " IFat 인터페이스를 구현하는 클래스의 수명을보다 쉽게 ​​만드는 것 "이고, 후자의 경우 ISP의 목표는 " MyService의 메소드를 호출하는 클라이언트의 삶을 더 쉽게 만드는 것 "입니다.

ISP의 두 가지 정의 중 실제로 올바른 것은 무엇입니까?

@MARJAN VENEMA

1.

따라서 IFat를 더 작은 인터페이스로 분할하려고 할 때 멤버의 응집력에 따라 ISmallinterface를 결정해야하는 방법이 있습니다.

동일한 인터페이스 내에 응집성있는 방법을 사용하는 것이 합리적이지만 ISP 패턴을 사용하면 클라이언트의 요구가 인터페이스의 "응집성"보다 우선한다고 생각했습니다. 다시 말해, ISP를 통해 특정 인터페이스에 필요한 방법을 동일한 인터페이스에 포함시켜야한다고 생각했습니다. 비록 응집력을 위해 해당 인터페이스에 포함되어야하는 방법도 동일한 인터페이스에 포함시켜야합니까?

따라서 전화해야 할 클라이언트가 CutGreens많았지 만 GrillMeatISP 패턴을 준수 하지 않으 려면 두 가지 방법이 응집력이 뛰어나더라도 CutGreens내부 에만 배치해야 ICook하지만 내부 에 배치해서는 안됩니다 GrillMeat!

2.

나는 당신의 혼란이 첫 번째 정의에서 숨겨진 가정에 기인한다고 생각합니다. 구현 클래스는 이미 단일 책임 원칙을 따르고 있습니다.

"SRP를 따르지 않는 클래스를 구현"함으로써 구현하는 클래스 IFat또는 ISmall_1/ 를 구현하는 클래스를 참조하고 ISmall_2있습니까? 구현하는 클래스를 참조한다고 가정 IFat합니까? 그렇다면 왜 SRP를 아직 따르지 않았다고 가정합니까?

감사


4
왜 같은 원칙에 의해 여러 개의 정의가 제공 될 수 없습니까?
Bobson 2016 년

5
이러한 정의는 서로 모순되지 않습니다.
Mike Partridge

1
물론 클라이언트의 요구가 인터페이스의 응집력보다 우선하지는 않습니다. 이 "룰"방식을 취해 모든 곳에서 전혀 이해가되지 않는 단일 메소드 인터페이스로 끝낼 수 있습니다. 규칙을 따르지 말고 이러한 규칙이 만들어진 목표를 생각해보십시오. "SRP를 따르지 않는 클래스"를 사용하여 예제에서 특정 클래스에 대해 이야기하지 않았거나 이미 SRP를 따르지 않았다고 이야기했습니다. 다시 읽으세요. 첫 번째 정의는 인터페이스가 ISP를 따르지 않고 클래스가 SRP를 따르는 경우에만 인터페이스를 분할합니다.
Marjan Venema 2016 년

2
두 번째 정의는 구현 자에 대해서는 신경 쓰지 않습니다. 호출자의 관점에서 인터페이스를 정의하고 구현자가 이미 존재하는지 여부에 대한 가정을하지 않습니다. ISP를 따를 때 해당 인터페이스를 구현할 때 SRP를 작성할 때 당연히 SRP를 따르는 것으로 가정합니다.
Marjan Venema 2016 년

2
어떤 클라이언트가 존재하고 어떤 방법이 필요한지 미리 어떻게 알 수 있습니까? 당신은 할 수 없습니다. 미리 알 수있는 것은 인터페이스의 응집력입니다.
Tulains Córdova 2016

답변:


6

둘 다 맞다

ISP (Interface Segregation Principle)의 목적은 인터페이스를 작고 집중적으로 유지하는 것입니다. 모든 인터페이스 멤버는 매우 높은 응집력을 가져야합니다. 두 가지 정의는 모두 "전략 자 마스터"인터페이스를 피하기위한 것입니다.

인터페이스 분리와 SRP (Single Responsibility Principle)의 목표는 작고 응집력이 높은 소프트웨어 구성 요소를 보장하는 것입니다. 그들은 서로를 보완합니다. 인터페이스 분리는 인터페이스가 작고 집중적이며 응집력이 뛰어나도록합니다. 단일 책임 원칙에 따라 수업은 소규모, 집중력 및 응집력이 보장됩니다.

첫 번째로 언급 한 정의는 구현 자에 중점을두고, 두 번째는 클라이언트에 중점을 둡니다. @ user61852와 달리 구현자가 아닌 인터페이스의 사용자 / 호출자가됩니다.

나는 당신의 혼란이 첫 번째 정의에서 숨겨진 가정에 기인한다고 생각합니다. 구현 클래스는 이미 단일 책임 원칙을 따르고 있습니다.

클라이언트에게 인터페이스의 호출자 인 두 번째 정의는 의도 한 목표를 달성하는 더 좋은 방법입니다.

분리

귀하의 질문에 귀하는 다음과 같이 진술합니다.

이 방법으로 MyClass는 A (), B () 및 C ()에 대한 더미 구현을 제공하지 않고도 필요한 메소드 (D () 및 C ()) 만 구현할 수 있습니다.

그러나 그것은 세상을 뒤집어 놓고 있습니다.

  • 인터페이스를 구현하는 클래스는 구현중인 인터페이스에 필요한 것을 지시하지 않습니다.
  • 인터페이스는 구현 클래스가 제공해야하는 메소드를 나타냅니다.
  • 인터페이스의 호출자는 실제로 인터페이스를 제공하기 위해 인터페이스에 필요한 기능과 구현자가 제공해야하는 기능을 지시하는 것입니다.

따라서 IFat더 작은 인터페이스 로 분할 할 때 ISmall멤버가 얼마나 응집력이 있는지에 따라 인터페이스를 결정 해야하는 방법이 있습니다.

이 인터페이스를 고려하십시오.

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

어떤 방법을 사용 ICook하시겠습니까? 저것과 다른 몇 가지 일을하지만 다른 방법과는 다른 수업을하기 때문에 CleanSink함께 합 시겠습니까 GrillMeat? 또는 다음과 같은 두 개의보다 응집력있는 인터페이스로 분리 하시겠습니까?

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

인터페이스 선언 참고

인터페이스 정의는 독립된 단위로 작성하는 것이 바람직하지만, 호출자 또는 구현 자와 함께 있어야하는 경우 실제로 호출자와 함께 있어야합니다. 그렇지 않으면 호출자는 인터페이스의 목적을 완전히 상실하는 구현 자에 즉시 의존합니다. 참조 : 기본 클래스와 동일한 파일에서 인터페이스 선언, 좋은 습관입니까? 프로그래머들에게 왜 우리는 그것들을 구현하는 클래스가 아닌 그것들을 사용하는 클래스와 인터페이스를 배치해야 하는가? StackOverflow에.


1
내가 업데이트 한 것을 볼 수 있습니까?
EdvRusj

"호출자가 구현 자에 즉각적으로 의존한다 "... DIP를 요구하는 호출자 내부 변수, 매개 변수, 반환 값 등이 유형 ICook대신 유형 인 경우 DIP (종속성 역전 원칙)를 위반하는 경우에만 해당됩니다 SomeCookImplementor. 의지 할 필요가 없습니다 SomeCookImplementor.
Tulains Córdova 2016

@ user61852 : 인터페이스 선언과 구현자가 동일한 단위에 있으면 즉시 해당 구현에 대한 종속성을 얻습니다. 반드시 런타임에 반드시 필요한 것은 아니지만 프로젝트 레벨에 있다는 것만으로도 확실하게 프로젝트 레벨에 있습니다. 프로젝트는 더 이상 사용하지 않거나 더 이상 컴파일 할 수 없습니다. 또한 Dependency Injection은 Dependency Inversion Principle과 다릅니다. 당신은 야생
Marjan Venema

이 질문 programmers.stackexchange.com/a/271142/61852 에서 코드 예제를 다시 사용하여 이미 승인 된 후에 개선했습니다. 나는 그 예에 대한 정당한 신용을 주었다.
Tulains Córdova

멋진 @ user61852 :) (그리고 신용 주셔서 감사합니다)
Marjan Venema

14

Gang of Four 문서에 사용 된 "client"라는 단어를 서비스 소비자와 같은 "client"와 혼동합니다.

Gang of Four 정의에서 의도 한 "클라이언트"는 인터페이스를 구현하는 클래스입니다. 클래스 A가 인터페이스 B를 구현하는 경우 A는 B의 클라이언트라고 말합니다. 그렇지 않으면 "클라이언트"(소비자에서와 같이)가 "클라이언트"가 사용하지 않는 인터페이스를 구현하도록 강요해서는 안됩니다. 아무것도 구현하지 않습니다. 이 문구는 "클라이언트"를 "구현 자"로 볼 때만 의미가 있습니다.

"클라이언트"가 큰 인터페이스를 구현하는 다른 클래스의 메소드를 "소비"(호출)하는 클래스를 의미 한 경우, 관심있는 두 가지 메소드를 호출하고 나머지를 무시함으로써 나머지 클래스와의 연결을 끊기에 충분합니다. 사용하지 않는 방법.

원칙의 정신은 관련된 메소드 세트에만 관심이있을 때 전체 인터페이스를 준수하기 위해 더미 메소드를 구현해야하는 "클라이언트"(인터페이스를 구현하는 클래스)를 피하는 것입니다.

또한 가능한 한 적은 양의 커플 링을 사용하여 한곳에서 변경하면 영향이 적습니다. 인터페이스를 분리하면 커플 링이 줄어 듭니다.

이 문제는 인터페이스가 너무 많은 경우에 하나의 인터페이스 대신 여러 인터페이스로 나누어야하는 메소드가있을 때 나타납니다.

두 코드 예제 모두 괜찮습니다 . "클라이언트"는 "다른 클래스가 제공하는 서비스 / 방법을 소비 / 호출하는 클래스"를 의미한다고 가정합니다.

나는 당신이 준 세 가지 링크에서 설명 된 개념에서 모순을 찾지 않습니다.

SOLID 대화에서 "클라이언트"가 구현 자임을 분명히하십시오 .


그러나 @pdr에 따르면 모든 링크의 코드 예제는 ISP를 준수하지만 ISP 정의는 "클라이언트 (다른 ​​클래스의 메소드를 호출하는 클래스)가 서비스에 대해 더 많이 알지 못하도록하는 것"에 관한 것입니다. 클라이언트 (구현 자)가 사용하지 않는 인터페이스를 강제로 실행하는 것을 방지합니다. "
EdvRusj 2016 년

1
@EdvRusj 내 답변은 Object Mentor (Bob Martin enterprise) 웹 사이트의 문서를 기반으로하며 유명한 Gang of Four에있을 때 Martin이 직접 작성했습니다. 아시다시피 Gnag of Four는 SOLID의 약어를 만들어 낸 Martin을 포함한 소프트웨어 엔지니어 그룹이 원칙을 식별하고 문서화했습니다. docs.google.com/a/cleancoder.com/file/d/…
Tulains Córdova

따라서 @pdr에 동의하지 않으므로 ISP의 첫 번째 정의 (원래 게시물 참조)가 더 합리적이라고 생각하십니까?
EdvRusj 2016 년

@EdvRusj 둘 다 맞다고 생각합니다. 그러나 두 번째는 클라이언트 / 서버 비유를 사용하여 불필요한 혼란을 더합니다. 하나를 선택해야한다면 공식 Gang of Four 중 하나를 선택합니다. 그러나 결국 SOLID 원리의 정신 인 커플 링 및 불필요한 종속성을 줄이는 것이 중요합니다. 어느 것이 옳은지는 중요하지 않습니다. 중요한 것은 bahaviors에 따라 인터페이스를 분리해야한다는 것입니다. 그게 다야. 그러나 확실하지 않은 경우 원래 소스로 이동하십시오.
Tulains Córdova

3
"고객"이 SOLID 대화의 구현 자라는 귀하의 주장에 동의하지 않습니다. 우선 언어 제공자가 제공하는 (구현) 클라이언트를 제공자 (구현 자)에게 호출하는 것은 말이되지 않습니다. 또한 이것을 전달하려는 SOLID 관련 기사는 보지 못했지만 간단히 놓쳤을 수도 있습니다. 가장 중요한 것은 인터페이스의 구현자를 인터페이스에 무엇이 있어야하는지 결정하는 것으로 설정하는 것입니다. 그리고 그것은 나에게 이해가되지 않습니다. 인터페이스의 호출자 / 사용자는 인터페이스에서 필요한 것을 정의하고 해당 인터페이스의 구현 자 (복수)는 인터페이스를 제공합니다.
Marjan Venema 2016 년

5

ISP는 클라이언트가 알아야 할 것보다 서비스에 대해 더 많이 알지 못하도록하는 것입니다 (예 : 관련없는 변경으로부터 서비스를 보호). 두 번째 정의가 정확합니다. 내 독서에 따르면,이 세 기사 중 하나만 다른 것을 제안하고 ( 첫 번째 기사 ) 그것은 명백한 잘못입니다. (편집 : 아니오, 잘못이 아니라 오도의 소지가 있습니다.)

첫 번째 정의는 LSP와 훨씬 밀접하게 연결되어 있습니다.


3
ISP에서 클라이언트는 사용하지 않는 인터페이스 구성 요소를 강제로 사용해서는 안됩니다. LSP에서 SERVICES는 호출 코드에 메소드 A가 필요하기 때문에 메소드 D를 구현하도록 강요해서는 안됩니다. 모순되지 않고 상보 적입니다.
pdr

2
@EdvRusj, ClientA가 호출하는 InterfaceA를 구현하는 객체는 실제로 Client B에 필요한 InterfaceB를 구현하는 것과 동일한 객체 일 수 있습니다. 드문 경우에 동일한 클라이언트가 다른 클래스와 동일한 객체를 볼 필요가있는 경우에는 코드가 보통 "터치". 당신은 그것을 하나의 목적을 위해 A와 다른 목적을 위해 B로 보게 될 것입니다.
Amy Blankenship

1
@ EdvRusj : 인터페이스 정의를 다시 생각하면 도움이 될 수 있습니다. 항상 C # / Java 용어의 인터페이스는 아닙니다. 클라이언트 A가 랩퍼 클래스 AX를 사용하여 서비스 X와 "인터페이스"할 수 있도록 여러 개의 단순 클래스가 랩핑 된 복잡한 서비스를 가질 수 있습니다. 따라서 X를 A 및 AX에 영향을주는 방식으로 변경하면 X가 변경되지 않습니다. BX와 B에 영향을 미침
pdr

1
@EdvRusj : A와 B는 X를 호출하거나 하나는 Y를 호출하고 다른 하나는 Z를 호출하는 경우 신경 쓰지 않는다고 말하는 것이 더 정확합니다. 이것이 ISP의 기본 포인트입니다. 따라서 원하는 구현을 선택하고 나중에 마음을 쉽게 바꿀 수 있습니다. ISP는 한 경로 나 다른 경로를 선호하지 않지만 LSP와 SRP는 선호 할 수 있습니다.
pdr

1
@EdvRusj 아니오, 클라이언트 A는 서비스 X를 서비스 y로 대체 할 수 있으며 둘 다 인터페이스 AX를 구현합니다. X 및 / 또는 Y는 다른 인터페이스를 구현할 수 있지만 클라이언트가 AX라고 할 때 다른 인터페이스는 신경 쓰지 않습니다.
Amy Blankenship 2014 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.