[language-agnostic] “인터페이스 프로그래밍”이란 무엇입니까?

나는 이것이 몇 번 언급 된 것을 보았고 그것이 무엇을 의미하는지 명확하지 않다. 언제, 왜 이렇게 하시겠습니까?

나는 인터페이스가 무엇을하는지 알고 있지만, 이것에 대해 명확하지 않다는 사실은 내가 올바르게 사용하는데 빠졌다고 생각합니다.

당신이해야한다면 그냥 그렇습니까?

IInterface classRef = new ObjectWhatever()

구현하는 클래스를 사용할 수 IInterface있습니까? 언제해야합니까? 내가 생각할 수있는 유일한 것은 메소드가 있고 구현을 제외하고 어떤 오브젝트가 전달 될지 확신 할 수없는 경우입니다 IInterface. 얼마나 자주 그렇게해야하는지 생각할 수 없습니다.

또한 인터페이스를 구현하는 객체를 취하는 메소드를 어떻게 작성할 수 있습니까? 가능합니까?



답변

이 질문에 대한 인터페이스에는 인터페이스와 느슨하게 결합 된 코드, 제어 반전 등에 대한 모든 종류의 세부 정보가 들어 있습니다. 상당히 까다로운 토론이 있으므로 인터페이스가 유용한 이유를 이해하기 위해 약간의 문제를 해결하고 싶습니다.

처음 인터페이스에 노출되기 시작했을 때 나는 그들의 관련성에 대해 혼란 스러웠다. 왜 당신이 그것들을 필요로하는지 이해하지 못했습니다. Java 또는 C #과 같은 언어를 사용하는 경우 이미 상속을 받았으며 인터페이스를 더 약한 상속 형식으로보고 “왜 귀찮게합니까?” 내가 옳다는 의미에서, 당신은 인터페이스를 일종의 약한 상속 형태로 생각할 수 있지만, 그 이상으로 공통된 특성이나 행동을 분류하는 수단으로 생각하여 언어 구성으로 사용하는 것을 마침내 이해했습니다. 잠재적으로 많은 관련이없는 객체 클래스.

예를 들어-SIM 게임이 있고 다음과 같은 클래스가 있다고 가정하십시오.

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

분명히이 두 객체는 ​​직접 상속 측면에서 공통점이 없습니다. 그러나 둘 다 성가 신다고 말할 수 있습니다.

우리 게임에는 저녁을 먹을 때 게임 플레이어를 괴롭히는 일종의 임의의 것이 필요하다고 가정 해 봅시다 . 이것은 HouseFly하나 Telemarketer또는 둘 다일 수 있지만 단일 함수로 두 가지를 어떻게 허용합니까? 서로 다른 유형의 객체에 같은 방식으로 “성가신 일을”하도록 요청하는 방법

인식의 핵심 은 모델링과 관련하여 아무것도 같지 않지만 공통적으로 느슨하게 해석되는 동작을 공유 Telemarketer하고 HouseFly공유한다는 것입니다. 따라서 둘 다 구현할 수있는 인터페이스를 만들어 봅시다.

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

우리는 이제 각자 자신의 방식으로 성가 시게 할 수있는 두 개의 클래스를 가지고 있습니다. 그리고 그들은 동일한 기본 클래스에서 파생 할 필요가없고 공통 계약을 충족시킬 필요가있는 공통의 고유 특성을 공유 할 필요가 없습니다 IPest. 당신은 단지해야합니다 BeAnnoying. 이와 관련하여 다음을 모델링 할 수 있습니다.

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

여기에는 많은 식당과 해충을 수용하는 식당이 있습니다. 인터페이스 사용에 주목하십시오. 이것은 우리의 작은 세계에서 pests배열 의 구성원 이 실제로 Telemarketer객체 또는 객체 가 될 수 있음을 의미 HouseFly합니다.

ServeDinner방법은 저녁 식사가 제공되고 식당에있는 사람들이 먹어야 할 때 호출됩니다. 우리의 작은 게임에서, 그것은 우리의 해충이 일을 할 때입니다-각 해충은 IPest인터페이스 를 통해 성가 시도록 지시받습니다 . 이러한 방법으로, 우리는 쉽게 모두를 가질 수 TelemarketersHouseFlys자신의 방법으로 각 성가신 일 – 우리는 우리가 뭔가 가지고에만 신경 DiningRoom해충이다 오브젝트를, 우리는 정말 그것이 무엇인지 상관 없어 그들은에서 아무것도 할 수 다른 사람과 공통입니다.

이 매우 고안된 의사 코드 예제 (예상보다 훨씬 길게 드래그 한 것)는 단순히 인터페이스를 사용할 때의 관점에서 마지막으로 불이 켜진 종류를 설명하기위한 것입니다. 나는이 예의 어리 석음에 대해 미리 사과하지만 그것이 이해하는 데 도움이되기를 바랍니다. 그리고 확실히, 여기에 당신이받은 다른 게시물 답변은 오늘날 디자인 패턴 및 개발 방법론에서 인터페이스를 사용하는 영역을 다루고 있습니다.


답변

내가 학생들에게 줄 때 사용한 구체적인 예는

List myList = new ArrayList(); // programming to the List interface

대신에

ArrayList myList = new ArrayList(); // this is bad

이것들은 짧은 프로그램에서 정확히 똑같아 보이지만 프로그램에서 myList100 번 계속 사용 하면 차이를 볼 수 있습니다. 첫 번째 선언은 인터페이스에 myList의해 정의 된 메소드 만 호출 List하므로 ArrayList특정 메소드 는 호출 하지 않습니다 . 이 방법으로 인터페이스에 프로그래밍 한 경우 나중에 실제로 필요한 것으로 결정할 수 있습니다

List myList = new TreeList();

한 지점에서만 코드를 변경하면됩니다. 이미 인터페이스에 프로그래밍했기 때문에 구현의 변경으로 인해 코드의 나머지 부분이 손상되지 않는다는 것을 이미 알고 있습니다 .

메소드 매개 변수와 반환 값에 대해 이야기 할 때 이점이 훨씬 분명합니다. 예를 들면 다음과 같습니다.

public ArrayList doSomething(HashMap map);

이 메소드 선언은 두 가지 구체적인 구현 ( ArrayListHashMap)에 연결됩니다. 해당 메소드가 다른 코드에서 호출되는 즉시 해당 유형이 변경되면 호출 코드도 변경해야합니다. 인터페이스에 프로그래밍하는 것이 좋습니다.

public List doSomething(Map map);

이제 어떤 종류의 List리턴인지, 어떤 종류의 Map매개 변수로 전달 되는지 는 중요하지 않습니다 . doSomething메소드 내에서 변경하면 호출 코드를 변경하지 않아도됩니다.


답변

인터페이스 프로그래밍은 “이 기능이 필요하며 그것이 어디에서 왔는지 상관하지 않습니다.”라고 말합니다.

(자바에서는) List인터페이스 ArrayListLinkedList구체적인 클래스를 고려하십시오 . 내가 관심있는 모든 것이 반복을 통해 액세스 해야하는 여러 데이터 항목을 포함하는 데이터 구조를 가지고 있다는 것이라면 List(그리고 99 %의 시간)을 선택합니다. 목록의 양쪽 끝에서 일정한 시간 삽입 / 삭제가 필요하다는 것을 알고 있다면 LinkedList구체적인 구현을 선택 하거나 대기열 인터페이스를 사용할 가능성이 큽니다 . 인덱스별로 임의 액세스가 필요하다는 것을 알고 있다면 ArrayList구체적인 클래스를 선택합니다 .


답변

인터페이스를 사용하면 클래스 간의 불필요한 커플 링을 제거 할뿐만 아니라 코드를 쉽게 테스트 할 수있는 핵심 요소가됩니다. 클래스에서 작업을 정의하는 인터페이스를 만들면 해당 기능을 사용하려는 클래스가 구현 클래스에 직접 의존하지 않고 해당 기능을 사용할 수 있습니다. 나중에 다른 구현을 변경하고 사용하기로 결정한 경우 구현이 인스턴스화되는 코드 부분 만 변경하면됩니다. 코드의 나머지 부분은 구현 클래스가 아닌 인터페이스에 의존하기 때문에 변경할 필요가 없습니다.

이것은 단위 테스트를 만드는 데 매우 유용합니다. 테스트중인 클래스에서 인터페이스에 의존하고 생성자 또는 속성 설정자를 통해 인터페이스 인스턴스를 클래스 (또는 필요에 따라 인터페이스 인스턴스를 빌드 할 수있는 팩토리)에 주입합니다. 클래스는 메소드에서 제공된 (또는 작성된) 인터페이스를 사용합니다. 테스트를 작성하려고 할 때 인터페이스를 조롱하거나 위조하고 단위 테스트에서 구성된 데이터로 응답하는 인터페이스를 제공 할 수 있습니다. 테스트중인 클래스는 구체적인 구현이 아닌 인터페이스 만 다루기 때문에이 작업을 수행 할 수 있습니다. 모의 또는 가짜 클래스를 포함하여 인터페이스를 구현하는 모든 클래스가 수행합니다.

편집 : 아래는 Erich Gamma가 자신의 인용문 “구현이 아닌 인터페이스에 대한 프로그램”을 설명하는 기사에 대한 링크입니다.

http://www.artima.com/lejava/articles/designprinciples.html


답변

인터페이스 프로그래밍은 Java 또는 .NET에서 볼 수있는 것처럼 추상 인터페이스와 전혀 관련이 없습니다. 심지어 OOP 개념이 아닙니다.

의미하는 것은 객체 또는 데이터 구조의 내부를 어지럽히 지 않습니다. 데이터와 상호 작용하려면 추상 프로그램 인터페이스 또는 API를 사용하십시오. Java 또는 C #에서는 원시 필드 액세스 대신 공용 특성 및 메소드를 사용합니다. C의 경우 원시 포인터 대신 함수를 사용하는 것을 의미합니다.

편집 : 데이터베이스를 사용하면 직접 테이블 액세스 대신 뷰 및 저장 프로 시저를 사용하는 것이 좋습니다.


답변

Inversion of Control을 살펴 봐야합니다.

이러한 시나리오에서는 다음과 같이 쓰지 않습니다.

IInterface classRef = new ObjectWhatever();

다음과 같이 작성하십시오.

IInterface classRef = container.Resolve<IInterface>();

이것은 container객체 의 규칙 기반 설정으로 들어가서 실제 객체를 생성합니다. ObjectWhatever가 될 수 있습니다. 중요한 것은이 규칙을 다른 유형의 객체를 모두 사용하는 것으로 대체 할 수 있으며 코드가 여전히 작동한다는 것입니다.

IoC를 테이블에서 분리하면 특정 유형의 객체 또는 객체 유형이 아닌 객체와 통신 할 수 있다는 것을 알고있는 코드를 작성할 수 있습니다 .

매개 변수를 전달할 때 유용합니다.

괄호로 묶인 질문에 관해서는 “또한 인터페이스를 구현하는 객체를 취하는 메소드를 어떻게 작성할 수 있습니까? 가능합니까?”, C #에서는 다음과 같이 매개 변수 유형에 대한 인터페이스 유형을 간단히 사용합니다.

public void DoSomethingToAnObject(IInterface whatever) { ... }

이것은 “특정한 일을하는 객체와 대화”에 바로 연결됩니다. 위에서 정의한 방법은 객체에서 무엇을 기대해야하는지 알고, IInterface의 모든 것을 구현하지만, 객체의 유형을 신경 쓰지 않고 계약을 준수하는 것만 신경 쓰면됩니다.

예를 들어, 당신은 아마도 계산기에 익숙하고 아마 당신의 하루에 꽤 며칠을 사용했을 것입니다. 그러나 대부분의 시간은 모두 다릅니다. 반면에 표준 계산기의 작동 방식을 알고 있으므로 각 계산기에 다른 기능이없는 특정 기능을 사용할 수없는 경우에도 모두 사용할 수 있습니다.

이것이 인터페이스의 아름다움입니다. 특정 동작을 기대할 수있는 객체가 전달 될 것이라는 것을 알고있는 코드를 작성할 수 있습니다. 어떤 종류의 객체인지는 신경 쓰지 않고 필요한 동작을 지원합니다.

구체적인 예를 들어 보겠습니다.

우리는 Windows 양식을위한 맞춤형 번역 시스템을 가지고 있습니다. 이 시스템은 폼의 컨트롤을 반복하고 각 텍스트를 번역합니다. 이 시스템은 텍스트 속성이있는 컨트롤 유형 및 이와 유사한 기본 항목과 같은 기본 컨트롤을 처리하는 방법을 알고 있지만 기본적인 사항은 부족합니다.

이제 컨트롤은 제어 할 수없는 미리 정의 된 클래스에서 상속되므로 다음 세 가지 중 하나를 수행 할 수 있습니다.

  1. 번역 시스템에 대한 지원을 구축하여 작업중인 제어 유형을 구체적으로 감지하고 올바른 비트를 번역합니다 (유지 관리 악몽).
  2. 기본 클래스에 대한 지원 구축 (모든 컨트롤이 미리 정의 된 다른 클래스에서 상속되므로 불가능)
  3. 인터페이스 지원 추가

그래서 우리는 nr을했다. 3. 모든 컨트롤은 “자체”를 번역 텍스트 / 규칙 컨테이너로 번역 할 수있는 기능인 ILocalizable을 구현합니다. 따라서 폼은 어떤 종류의 컨트롤을 찾았는지 알 필요가 없으며 특정 인터페이스를 구현하고 컨트롤을 지역화하기 위해 호출 할 수있는 메서드가 있다는 것을 알고 있습니다.


답변

인터페이스에 대한 코드 구현이 Java와 관련이 없으며 인터페이스 구성도 아닙니다.

이 개념은 Patterns / Gang of Four 서적에서 두드러졌지만, 아마도 그보다 훨씬 이전에 있었을 것입니다. 이 개념은 Java가 존재하기 전에 확실히 존재했습니다.

Java Interface 구문은이 아이디어를 돕기 위해 만들어졌으며, 사람들은 원래 의도가 아니라 의미의 중심 인 구문에 너무 집중했습니다. 그러나 Java, C ++, C # 등의 공개 및 개인 메소드 및 속성이있는 이유입니다.

그것은 단지 객체 또는 시스템의 공용 인터페이스와 상호 작용한다는 것을 의미합니다. 내부적으로 수행하는 방식에 대해 걱정하거나 예상하지 마십시오. 그것이 어떻게 구현되는지 걱정하지 마십시오. 객체 지향 코드에서는 공개 대 개인 메소드 / 속성이있는 이유입니다. 비공개 메소드는 클래스 내에서 내부 용으로 만 사용되므로 공개 메소드를 사용하려고합니다. 클래스의 구현을 구성하며 공용 인터페이스를 변경하지 않고 필요에 따라 변경할 수 있습니다. 기능과 관련하여 클래스의 메소드는 동일한 매개 변수로 호출 할 때마다 동일한 예상 결과로 동일한 조작을 수행한다고 가정하십시오. 이를 통해 저자는 사람들이 클래스와 상호 작용하는 방식을 방해하지 않고 클래스 작동 방식, 구현 방식을 변경할 수 있습니다.

또한 Interface 구문을 사용하지 않고도 구현이 아닌 인터페이스로 프로그래밍 할 수 있습니다. C ++에서 구현이 아닌 인터페이스로 프로그래밍 할 수 있으며, 인터페이스 구성이 없습니다. 두 개의 대규모 엔터프라이즈 시스템이 시스템 내부의 객체에서 메소드를 호출하는 대신 공용 인터페이스 (계약)를 통해 상호 작용하는 한 훨씬 더 강력하게 통합 할 수 있습니다. 인터페이스는 동일한 입력 매개 변수가 주어지면 항상 동일한 예상 방식으로 반응합니다. 인터페이스가 아닌 구현으로 구현 된 경우 이 개념은 여러 곳에서 작동합니다.

Java 인터페이스에는 ‘구현이 아닌 인터페이스에 대한 프로그램’이라는 개념과 관련이 있다는 생각을 떨치십시오. 개념을 적용하는 데 도움이 될 수 있지만 개념은 아닙니다 .