인터페이스 분리 원리 : 인터페이스가 상당히 겹치는 경우 어떻게해야합니까?


9

에서 피어슨 새로운 국제 버전 : 애자일 소프트웨어 개발, 원칙, 패턴, 및 사례 :

때로는 다른 클라이언트 그룹이 호출 한 메소드가 겹칠 수 있습니다. 오버랩이 작 으면 그룹의 인터페이스가 분리되어 있어야합니다. 공통 기능은 모든 겹치는 인터페이스에 선언해야합니다. 서버 클래스는 각 인터페이스에서 공통 기능을 상속하지만 한 번만 구현합니다.

Bob 아저씨, 약간의 중복이있는 경우에 대해 이야기합니다.

겹치는 부분이 많으면 어떻게해야합니까?

우리가 가지고 있다고

Class UiInterface1;
Class UiInterface2;
Class UiInterface3;

Class UiIterface : public UiInterface1, public UiInterface2, public UiInterface3{};

UiInterface1와 사이에 상당한 중복이있는 경우 어떻게해야 UiInterface2합니까?


겹치는 인터페이스를 접할 때 공통 인터페이스를 그룹화 한 다음이 공통 메소드를 상속하여 전문화를 작성하는 상위 인터페이스를 작성합니다. 그러나! 전문화없이 공통 인터페이스를 사용하는 사람을 원하지 않으면 실제로 코드 복제를 수행해야합니다. 패 런트 공통 인터페이스를 도입하면 사람들이 해당 인터페이스를 사용할 수 있기 때문입니다.
Andy

질문은 조금 모호합니다. 사례에 따라 다양한 솔루션으로 대답 할 수 있습니다. 왜 중복이 커졌습니까?
Arthur Havlicek

답변:


1

주조

이것은 확실히 인용 된 책의 접근 방식과 완전히 접하게 될 것이지만, ISP에 더 잘 부합하는 한 가지 방법은 QueryInterfaceCOM 스타일 접근 방식을 사용하여 코드베이스의 한 중앙 영역에 캐스팅 사고 방식을 수용하는 것 입니다.

순수한 인터페이스 환경에서 겹치는 인터페이스를 디자인하려는 많은 유혹은 종종 정확하고 저격적인 책임을 수행하는 것보다 인터페이스를 "자급 자족"하도록 만드는 욕구에서 비롯됩니다.

예를 들어 다음과 같은 클라이언트 기능을 설계하는 것이 이상하게 보일 수 있습니다.

// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `position` and `parenting` parameters should point to the 
// same object.
Vec2i abs_position(IPosition* position, IParenting* parenting)
{
     const Vec2i xy = position->xy();
     auto parent = parenting->parent();
     if (parent)
     {
         // If the entity has a parent, return the sum of the
         // parent position and the entity's local position.
         return xy + abs_position(dynamic_cast<IPosition*>(parent),
                                  dynamic_cast<IParenting*>(parent));
     }
     return xy;
}

...이 인터페이스를 사용하여 클라이언트 코드에 오류가 발생하기 쉬운 캐스팅을 수행하거나 인수로 동일한 객체를 동일한 매개 변수의 여러 매개 변수에 여러 번 전달해야 할 책임이 있음을 감안할 때 상당히 추악하고 위험합니다. 함수. 우리는 결국 그래서 종종 더 희석의 우려 통합 인터페이스 설계를 원하는 IParentingIPosition같이 한 곳에서를 IGuiElement다음 마찬가지로 더 많은 멤버 함수를 유혹한다 직교 인터페이스의 우려 중복에 취약하게되는 그런이나 뭐 동일한 "자급 자족"이유.

혼합 책임과 캐스팅

완전히 증류되고 초단 수 책임이있는 인터페이스를 설계 할 때 종종 여러 다운 로스팅을 수락하거나 인터페이스를 통합하여 여러 책임을 수행해야합니다 (따라서 ISP와 SRP 모두에서 진행).

COM 스타일 접근 방식 ( QueryInterface일부만 사용)을 사용하여 다운 캐스팅 접근 방식을 사용하지만 코드베이스에서 하나의 중앙 위치로 캐스팅을 통합하고 다음과 같은 작업을 수행 할 수 있습니다.

// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should implement `IPosition` and optionally `IParenting`.
Vec2i abs_position(Object* obj)
{
     // `Object::query_interface` returns nullptr if the interface is
     // not provided by the entity. `Object` is an abstract base class
     // inherited by all entities using this interface query system.
     IPosition* position = obj->query_interface<IPosition>();
     assert(position && "obj does not implement IPosition!");
     const Vec2i xy = position->xy();

     IParenting* parenting = obj->query_interface<IParenting>();
     if (parenting && parenting->parent()->query_interface<IPosition>())
     {
         // If the entity implements IParenting and has a parent, 
         // return the sum of the parent position and the entity's 
         // local position.
         return xy + abs_position(parenting->parent());
     }
     return xy;
}

... 물론 안전한 유형의 래퍼와 원시 포인터보다 안전한 것을 얻기 위해 중앙에서 빌드 할 수있는 모든 것이 있으면 좋겠습니다.

이를 통해 겹치는 인터페이스를 디자인하려는 유혹이 종종 최소로 완화됩니다. ISP에 대해 걱정할 필요없이 C ++로 런타임에 의사 덕 타이핑의 유연성을 얻을 수있는 유연성을 얻을 수있는 매우 단일 한 책임 (때로는 하나의 멤버 함수 만 있음)을 갖는 인터페이스를 설계 할 수 있습니다. 객체가 특정 인터페이스를 지원하는지 확인하기 위해 객체를 쿼리하는 런타임 페널티의 상충 관계). 런타임 부분은 함수가 이러한 인터페이스를 구현하는 플러그인의 컴파일 타임 정보를 미리 가지고 있지 않은 소프트웨어 개발 키트를 사용한 설정에서 중요 할 수 있습니다.

템플릿

템플릿이 가능한 경우 (필요한 컴파일 타임 정보가 사전에 있으며, 개체를 잡을 때까지 손실되지 않습니다.) 다음과 같이하면됩니다.

// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should have `position` and `parent` methods.
template <class Entity>
Vec2i abs_position(Entity& obj)
{
     const Vec2i xy = obj.xy();
     if (obj.parent())
     {
         // If the entity has a parent, return the sum of the parent 
         // position and the entity's local position.
         return xy + abs_position(obj.parent());
     }
     return xy;
}

... 물론 그러한 경우 parent메소드는 동일한 Entity유형 을 반환해야합니다 .이 경우 인터페이스를 완전히 피하고 싶을 것입니다 (기본 포인터로 작업하기 위해 유형 정보를 종종 잃기를 원하기 때문에).

엔터티 구성 요소 시스템

유연성이나 성능 관점에서 COM 스타일 접근 방식을 추구하기 시작하면 업계에서 게임 엔진이 적용되는 것과 유사한 엔터티 구성 요소 시스템이 생길 수 있습니다. 이 시점에서 많은 객체 지향 접근 방식과 완전히 직각을 이룰 수 있지만 ECS는 GUI 디자인에 적용 할 수 있습니다. COM 스타일의 접근 방식을 사용하여 시도하십시오).

이 COM 스타일 솔루션은 GUI 툴킷 디자인이 진행되는 한 완전히 존재하며 ECS는 훨씬 더 많아 질 것이므로 많은 리소스가 뒷받침하는 것이 아닙니다. 그러나 겹치는 책임을 가진 인터페이스를 디자인하려는 유혹을 확실히 완화시켜 종종 비 관심으로 만듭니다.

실용적 접근

대안은, 물론, 가드 조금 휴식을 취하거나 세부적인 수준에서 인터페이스를 설계 한 다음 사용하는 것이 거친 인터페이스를 만들 수를 상속 시작입니다 같은 IPositionPlusParenting어떤 모두에서 파생 IPositionIParenting(바람직하게 그보다 나은 이름으로). 순수한 인터페이스를 사용하면 일반적으로 적용되는 모 놀리 식 심층 계층 적 접근 방식 (Qt, MFC 등)만큼 ISP를 위반해서는 안됩니다. 실용적인 접근 방식은 단순히 여기저기서 약간의 중복을 받아 들일 수 있습니다. 그러나 이러한 종류의 COM 스타일 방식은 사용할 모든 조합에 대해 통합 된 인터페이스를 만들 필요가 없습니다. 이러한 경우 "자급 자족"문제는 완전히 제거되며, SRP와 ISP 모두와 싸우고 자하는 책임이 겹치는 인터페이스를 설계하려는 궁극적 인 유혹의 원천을 종종 제거합니다.


11

이것은 사건별로 판단해야합니다.

우선, SOLID 원칙은 바로 그 원칙이라는 것을 기억하십시오. 그들은 규칙이 아닙니다. 그들은 총알이 아닙니다. 그들은 단지 원칙 일뿐입니다. 그것은 그들의 중요성에서 벗어나지 않기 위해, 당신은 항상 그것들을 따라야합니다. 그러나 두 번째로 통증이 발생하면 필요할 때까지 흘려야합니다.

이를 염두에두고 왜 인터페이스를 먼저 분리해야하는지 생각해보십시오. 인터페이스의 아이디어는 "이 소비 코드가 소비되는 클래스에서 구현되어야하는 메소드 세트를 필요로하는 경우 구현에 대한 계약을 설정해야합니다.이 인터페이스와 함께 오브젝트를 제공하면 작업 할 수 있습니다. 그것으로."

ISP의 목적은 "필요한 계약이 기존 인터페이스의 하위 집합 인 경우 미래의 클래스에 기존 인터페이스를 적용해서는 안됩니다."라고 말합니다.

다음 코드를 고려하십시오.

public interface A
{
    void X();
    void Y();
}

public class Foo
{
     public void ConsumeX(A a)
     {
         a.X();
     }
}

이제 ConsumeX에 새 객체를 전달하려면 계약에 맞게 X () 및 Y ()를 구현해야하는 상황이 있습니다.

다음 예제와 같이 코드를 변경해야합니까?

public interface A
{
    void X();
    void Y();
}

public interface B
{
    void X();
}

public class Foo
{
     public void ConsumeX(B b)
     {
         b.X();
     }
}

ISP는 우리가해야한다고 제안하므로 그 결정에 기대어야합니다. 그러나 상황이 없으면 확실하지 않습니다. A와 B를 확장 할 가능성이 있습니까? 그것들이 독립적으로 확장 될 가능성이 있습니까? B가 A에 필요하지 않은 메소드를 구현할 가능성이 있습니까? 그렇지 않은 경우 A를 B에서 파생시킬 수 있습니다.

이것은 당신이해야 할 판단입니다. 그리고 전화를 걸기에 충분한 정보가 없다면 가장 간단한 옵션을 선택해야합니다. 첫 번째 코드 일 수도 있습니다.

왜? 나중에 마음이 바뀌기 쉽기 때문입니다. 새 클래스가 필요하면 새 인터페이스를 만들고 이전 클래스에서 모두 구현하십시오.


1
"먼저, SOLID 원칙은 그저 원칙이라는 것을 기억하십시오. 규칙이 아닙니다.은 총알이 아닙니다. 단지 원칙 일뿐입니다. 즉, 중요도에서 벗어나지 않기 위해 항상 몸을 기울여야합니다. 두 번째로 그들이 고통의 수준을 나타내면, 필요할 때까지 버려야합니다. " 이것은 모든 디자인 패턴 / 원칙 책의 첫 페이지에 있어야합니다. 또한 50 페이지마다 알림으로 표시되어야합니다.
Christian Rodriguez
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.