프로그래머가 "객체가 아닌 인터페이스에 대한 코드"라는 말은 무엇을 의미합니까?


79

저는 TDD를 배우고 내 워크 플로우에 적용 하기 위해 매우 길고 힘든 탐구를 시작했습니다 . 나는 TDD가 IoC 원칙에 매우 잘 맞는다는 인상을 받고 있습니다.

여기에 TDD 태그가 달린 질문 중 일부를 살펴본 후 객체가 아닌 인터페이스에 대해 프로그래밍하는 것이 좋습니다.

이것이 무엇인지에 대한 간단한 코드 예제를 제공하고 실제 사용 사례에 적용하는 방법을 제공 할 수 있습니까? 간단한 예는 저 (및 배우고 싶은 다른 사람들)가 개념을 이해하는 데 중요합니다.


7
이것은 C # 특정 것이 아니라 일종의 OOP입니다 ...
Billy ONeal 2010

2
@Billy ONeal : 그럴 수도 있지만 인터페이스가 C # / Java에서 다르게 작동하기 때문에 가장 익숙한 언어를 먼저 배우고 싶습니다.

11
@Sergio Boombastic : 인터페이스 프로그래밍의 개념은 interfaceJava 또는 C # 에서와는 아무 관련이 없습니다 . 사실,이 인용문을 인용 한 책이 쓰여졌을 때 자바도 C #도 존재하지 않았습니다.
Jörg W Mittag

1
@ 르그 : 음,이 뭔가 함께 할 수 있습니다. 최근 OOP 언어의 인터페이스는 확실히 인용문에 설명 된대로 사용되도록 의도되었습니다.
Michael Petrotta 2010

1
@Michael Petrotta : 그래도 그들은 잘하지 못합니다. 예를 들어, 인터페이스 의이 List후 말한다 add리스트에 요소를 보내고, 소자의리스트 및리스트의 길이가 증가이다 1. [ interface List] ( Download.Oracle.Com/javase/7/docs/api/java/util/List.html#add ) 에서 실제로 어디에 표시 됩니까?
Jörg W Mittag

답변:


83

중히 여기다:

class MyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(MyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}

MyMethod만 허용 하기 때문에 단위 테스트를 위해 모의 객체 MyClass로 바꾸려면 MyClass할 수 없습니다. 인터페이스를 사용하는 것이 더 좋습니다.

interface IMyClass
{
    void Foo();
}

class MyClass : IMyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(IMyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}

이제 MyMethod특정 구체적인 구현이 아닌 인터페이스 만 사용하므로을 테스트 할 수 있습니다 . 그런 다음 해당 인터페이스를 구현하여 테스트 목적으로 원하는 모의 또는 가짜를 만들 수 있습니다. Rhino Mocks '와 같은 라이브러리도 있습니다.이 라이브러리 Rhino.Mocks.MockRepository.StrictMock<T>()는 모든 인터페이스를 사용하여 즉시 모의 개체를 만듭니다.


14
참고 : 사용중인 언어가 단어를 사용한다는 점에서 항상 실제 인터페이스 일 필요 는 없습니다 . 상속에 대한 Java 또는 C # 제한 사항을 고려할 때 완전히 불합리하지 않은 추상 클래스 일 수도 있습니다.
Joey

2
@Joey : 추상 클래스를 사용할 수 있습니다.하지만 그렇게한다면 상속 가능하도록 클래스를 디자인해야합니다. 이는 좀 더 많은 작업이 될 수 있습니다. 물론 언어 수준의 인터페이스가없는 C ++와 같은 언어에서는 정확히 그렇게합니다. 추상 클래스를 만듭니다. Java 및 C #에서는 여러 인터페이스에서 상속 할 수 있지만 하나의 클래스 만 상속 할 수 있으므로 인터페이스를 사용하는 것이 더 좋습니다. 다중 인터페이스 상속을 허용하면 인터페이스를 더 작게 만드는 것이 좋습니다. TM :)
Billy ONeal 2010

5
@Joey : 당신은 아무것도 사용할 필요조차 없습니다 . 예를 들어 Ruby에서 프로그래밍 하는 인터페이스 는 일반적으로 영어로만 문서화됩니다. 그렇다고해서 Interface 가되지는 않습니다 . 반대로, interface코드 전체에 키워드를 겹친다 고 해서 Interface 에 대해 프로그래밍하는 것은 아닙니다 . 사고 실험 : 끔찍하게 밀접하게 결합 된 비 응집 코드를 사용하십시오. 모든 클래스에 대해 간단히 복사하여 붙여넣고, 모든 메서드 본문을 삭제하고, class키워드를로 바꾸고 interface, 코드의 모든 참조를 해당 유형으로 업데이트합니다. 이제 코드가 더 나아 졌습니까?
Jörg W Mittag

:-) 사실, 그것을보고의 좋은 방법입니다 르그,
조이

조롱 외에도 구현 세부 정보를 숨기면 호출 코드를 변경하지 않고도 성능 등을 향상시키기 위해 아래의 코드를 변경할 수 있습니다. Java는 예를 들어 List를 사용하여이를 수행합니다. 아래의 구현은 어레이, 스택 등이 될 수 있습니다.
Brian Pan

18

그것은 모두 친밀감의 문제입니다. 구현 (실현 된 객체)에 코딩하는 경우 해당 "다른"코드와 그 소비자로서 매우 밀접한 관계에 있습니다. 그것은 당신이 그것을 구성하는 방법 (즉, 생성자 매개 변수로서, 아마도 setter로서 어떤 의존성을 가지고 있는지), 언제 폐기해야하는지 알아야하며, 아마도 그것 없이는 많은 일을 할 수 없다는 것을 의미합니다.

실현 된 객체 앞의 인터페이스를 통해 몇 가지 작업을 수행 할 수 있습니다.

  1. 하나의 경우 팩토리를 활용하여 객체의 인스턴스를 구성 할 수 있습니다. IOC 컨테이너는이를 매우 잘 수행하거나 직접 만들 수 있습니다. 책임을 벗어난 건설 의무를 사용하면 코드가 필요한 것을 얻고 있다고 가정 할 수 있습니다. 공장 벽의 다른 쪽에서는 실제 인스턴스를 만들거나 클래스의 모의 인스턴스를 만들 수 있습니다. 프로덕션에서는 물론 real을 사용하지만 테스트를 위해 시스템을 실행하지 않고도 다양한 시스템 상태를 테스트하기 위해 스텁 또는 동적 모의 인스턴스를 만들 수 있습니다.
  2. 물체가 어디에 있는지 알 필요가 없습니다. 이것은 대화하려는 개체가 프로세스 또는 시스템에 로컬 일 수도 있고 아닐 수도있는 분산 시스템에서 유용합니다. Java RMI 또는 이전 skool EJB를 프로그래밍 한 적이 있다면 클라이언트가 신경 쓸 필요가없는 원격 네트워킹 및 마샬링 임무를 수행하는 프록시를 숨기는 "인터페이스와 대화"하는 루틴을 알고 있습니다. WCF는 "인터페이스와 대화"라는 유사한 철학을 가지고 있으며 시스템이 대상 개체 / 서비스와 통신하는 방법을 결정하도록합니다.

** 업데이트 ** IOC 컨테이너 (공장)의 예에 대한 요청이있었습니다. 거의 모든 플랫폼에 대해 많은 것이 있지만 핵심에서는 다음과 같이 작동합니다.

  1. 애플리케이션 시작 루틴에서 컨테이너를 초기화합니다. 일부 프레임 워크는 구성 파일이나 코드 또는 둘 다를 통해이를 수행합니다.

  2. 컨테이너가 구현하는 인터페이스에 대한 팩토리로 생성 할 구현을 "등록"합니다 (예 : 서비스 인터페이스에 MyServiceImpl 등록). 이 등록 프로세스 동안 일반적으로 제공 할 수있는 몇 가지 동작 정책이 있습니다 (예 : 매번 새 인스턴스가 생성되거나 단일 인스턴스가 사용되는 경우)

  3. 컨테이너가 객체를 생성 할 때 생성 프로세스의 일부로 해당 객체에 종속성을 주입합니다 (즉, 객체가 다른 인터페이스에 의존하는 경우 해당 인터페이스의 구현이 차례로 제공됩니다).

의사 코드처럼 다음과 같이 보일 수 있습니다.

IocContainer container = new IocContainer();

//Register my impl for the Service Interface, with a Singleton policy
container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON);

//Use the container as a factory
Service myService = container.Resolve<Service>();

//Blissfully unaware of the implementation, call the service method.
myService.DoGoodWork();

포인트 1의 +1은 매우 명확하고 이해하기 쉬웠 습니다. 포인트 2는 내 머리 위로 엉망이 되었습니다. : P

1
@Sergio : hoserdude가 의미하는 바는 코드가 개체의 실제 구현자가 무엇인지 알지 못하는 경우가 많다는 것입니다. 이는 프레임 워크 또는 다른 라이브러리에서 자동으로 구현되기 때문입니다.
Billy ONeal 2010

IoC 컨테이너 공장의 좋은 기본 예를들 수 있습니까?
괴롭힘

9

인터페이스에 대해 프로그래밍 할 때 구체적인 유형이 아닌 인터페이스의 인스턴스를 사용하는 코드를 작성합니다. 예를 들어 생성자 주입을 통합하는 다음 패턴을 사용할 수 있습니다. 생성자 주입 및 제어 반전의 다른 부분은 인터페이스에 대해 프로그래밍 할 필요가 없지만 TDD 및 IoC 관점에서 왔기 때문에 이러한 방식으로 연결하여 일부 컨텍스트를 제공 할 수 있습니다. 익숙합니다.

public class PersonService
{
    private readonly IPersonRepository repository;

    public PersonService(IPersonRepository repository)
    {
        this.repository = repository;
    }

    public IList<Person> PeopleOverEighteen
    {
        get
        {
            return (from e in repository.Entities where e.Age > 18 select e).ToList();
        }
    }
}

저장소 개체가 전달되고 인터페이스 유형입니다. 인터페이스 전달의 이점은 사용법을 변경하지 않고 구체적인 구현을 '교환'할 수 있다는 것입니다.

예를 들어 런타임에 IoC 컨테이너가 데이터베이스에 연결되는 저장소를 주입한다고 가정합니다. 테스트 시간 동안 모의 또는 스텁 저장소를 전달하여 PeopleOverEighteen방법 을 실행할 수 있습니다 .


3
+1-인터페이스를 효과적으로 사용하기 위해 IoC 컨테이너와 같은 것이 실제로 필요한 이유가 없다는 점에 유의해야합니다.
Billy ONeal 2010

기본적으로 내가 얻는 유일한 이점은 Mocking 프레임 워크를 사용할 수 있다는 것입니다.

그리고 확장 성. 테스트 가능성은 결합이 낮고 응집력이 높은 시스템의 좋은 측면입니다. 인터페이스를 전달함으로써 실제 클래스가하는 일에 대한 우려를 제거합니다. 당신은 더 이상 관심없는 방법 이 그것이라고 주장 것만을 수행하지만, 하지 를 않습니다. 이를 통해 우려 사항을 분리하고 현재 작업의 특정 요구 사항에 집중할 수 있습니다.
Michael Shimmins 2010

@Sergio : 모든 종류의 모의를하려면 이와 같은 것이 필요합니다. 프레임 워크 만이 아닙니다. @Michael : "DDD"가 "개발자 주도 개발"이라고 생각했습니다 ... TDD 여야하나요?
Billy ONeal 2010

1
@Billy-나는 도메인 주도 개발을 가정하고 있었다
Michael Shimmins dec

3

일반적인 생각을 의미합니다. 구체적이지 않습니다.

사용자에게 메시지를 보내는 것을 알리는 애플리케이션이 있다고 가정합니다. 예를 들어 IMessage 인터페이스를 사용하여 작업하는 경우

interface IMessage
{
    public void Send();
}

사용자별로 메시지 수신 방식을 사용자 정의 할 수 있습니다. 예를 들어 누군가 이메일로 알림을 받기를 원하므로 IoC가 EmailMessage 구체적인 클래스를 생성합니다. 다른 사람들은 SMS를 원하고 SMSMessage의 인스턴스를 만듭니다.

이 모든 경우에 사용자에게 알리는 코드는 변경되지 않습니다. 다른 구체적인 클래스를 추가하더라도.


@Billy [SOLID (의 "O"부분 en.wikipedia.org/wiki/Solid_(object-oriented_design) , 감사합니다
로렌

StackExchange가 귀하의 링크를 끊었습니다 :( en.wikipedia.org/wiki/Solid_%28object-oriented_design%29
Billy ONeal 2010

@Billy : 죄송합니다 ... 마지막 괄호를 먹었습니다. 이 사람은 일해야한다
로렌조


2

단위 테스트를 수행 할 때 인터페이스에 대한 프로그래밍의 큰 장점은 별도로 테스트하거나 테스트 중에 시뮬레이션하려는 종속성에서 코드 조각을 분리 할 수 ​​있다는 것입니다.

앞서 언급 한 예는 구성 값에 액세스하기 위해 인터페이스를 사용하는 것입니다. ConfigurationManager를 직접 보는 대신 구성 값에 액세스 할 수있는 하나 이상의 인터페이스를 제공 할 수 있습니다. 일반적으로 구성 파일에서 읽는 구현을 제공하지만 테스트를 위해 테스트 값을 반환하거나 예외를 던지는 구현을 사용할 수 있습니다.

데이터 액세스 계층도 고려하십시오. 비즈니스 로직이 특정 데이터 액세스 구현과 밀접하게 연결되어 있으면 필요한 데이터를 사용할 수있는 전체 데이터베이스 없이는 테스트하기가 어렵습니다. 데이터 액세스가 인터페이스 뒤에 숨겨져있는 경우 테스트에 필요한 데이터 만 제공 할 수 있습니다.

인터페이스를 사용하면 테스트에 사용할 수있는 "표면 영역"이 증가하여 실제로 코드의 개별 단위를 테스트하는 세밀한 테스트가 가능합니다.


2

문서를 읽은 후 코드를 사용할 사람처럼 코드를 테스트하십시오. 코드를 작성했거나 읽었으므로 가지고있는 지식을 바탕으로 아무것도 테스트하지 마십시오. 코드 가 예상대로 작동 하는지 확인하려고합니다 .

최상의 경우에는 테스트를 예제로 사용할 수 있어야합니다. Python의 doctest가 이에 대한 좋은 예제입니다.

이러한 지침을 따르면 구현 변경이 문제가되지 않습니다.

또한 내 경험상 애플리케이션의 각 "계층"을 테스트하는 것이 좋습니다. 당신은 그 자체로 의존성이없는 원자 단위를 갖게 될 것이고 결국 그 자체가 단위 인 애플리케이션에 도달 할 때까지 다른 단위에 의존하는 단위를 갖게 될 것입니다.

각 계층을 테스트해야합니다. 단위 A를 테스트하여 단위 A가 의존하는 단위 B도 테스트한다는 사실에 의존하지 마십시오 (규칙은 상속에도 적용됩니다.) 이것 역시 구현 세부 사항으로 취급되어야합니다. 자신을 반복하는 것처럼 느낄 수도 있습니다.

한 번 작성된 테스트는 변경 될 가능성이 없지만 테스트하는 코드는 거의 확실하게 변경됩니다.

실제로는 IO 및 외부 세계의 문제도 있으므로 필요한 경우 모의를 만들 수 있도록 인터페이스를 사용하려고합니다.

좀 더 동적 인 언어에서는별로 문제가되지 않습니다. 여기에서 덕 타이핑, 다중 상속 및 믹스 인을 사용하여 테스트 케이스를 작성할 수 있습니다. 일반적으로 상속을 싫어하기 시작하면 아마도 제대로하고있는 것입니다.


1

이 스크린 캐스트 는 C #을위한 애자일 개발 및 TDD에 대해 설명합니다.

인터페이스에 대한 코딩은 테스트에서 실제 개체 대신 모의 개체를 사용할 수 있음을 의미합니다. 좋은 모의 프레임 워크를 사용하면 원하는대로 모의 객체에서 할 수 있습니다.

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