[C#] 인터페이스 — 요점이 무엇입니까?

인터페이스의 이유는 저를 정말로 피합니다. 내가 이해 한 바에 따르면 C #에는 존재하지 않는 존재하지 않는 다중 상속에 대한 일종의 해결 방법입니다 (또는 그렇게 들었습니다).

내가 보는 것은 멤버와 함수를 미리 정의한 다음 클래스에서 다시 정의해야한다는 것입니다. 따라서 인터페이스가 중복됩니다. 그것은 단지 구문론적인 것 같은 느낌이 든다. .. 음, 나에게 쓰레기 (제발 악의를 가지지 말아라. 쓸모없는 것들처럼 정크).

스택 오버플로의 다른 C # 인터페이스 스레드에서 가져온 아래 예제에서 인터페이스 대신 Pizza라는 기본 클래스를 만들었습니다.

쉬운 예 (다른 스택 오버플로 기여에서 가져온)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}



답변

요점은 인터페이스가 계약을 나타냅니다 . 모든 구현 클래스에는 일련의 공개 메소드가 있어야합니다. 기술적으로 인터페이스는 구문, 즉 어떤 메소드가 있는지, 어떤 인수가 있고 어떤 것이 리턴되는지를 제어합니다. 일반적으로 문서에 의해서만 의미 체계를 캡슐화합니다.

그런 다음 인터페이스를 다르게 구현하고 마음대로 바꿀 수 있습니다. 귀하의 예에서, 모든 피자 인스턴스는 알 수없는 피자 유형의 인스턴스를 처리하는 모든 곳에서 IPizza사용할 수 있습니다 IPizza. 유형이 상속되는 모든 인스턴스 IPizza에는 Order()메소드 가 있으므로 순서를 지정할 수 있습니다 .

파이썬은 정적으로 타입이 아니므로 타입은 런타임에 유지되고 조회됩니다. 따라서 Order()모든 객체 에서 메소드를 호출 할 수 있습니다 . 객체가 그러한 메소드를 가지고 있고 아마도“Meh.«라고 말하지 않는다면 런타임은 행복합니다. C #에서는 그렇지 않습니다. 컴파일러는 올바른 호출을 담당하며 임의 object의 랜덤을 갖는 경우 런타임 중에 인스턴스에 해당 메소드가 있는지 여부를 아직 알지 못합니다. 컴파일러의 관점에서 확인할 수 없으므로 유효하지 않습니다. (반사 또는 dynamic키워드로 그러한 일을 할 수는 있지만 지금은 조금 먼 것 같습니다.)

또한 일반적인 의미의 인터페이스가 반드시 C # interface일 필요는 없으며 추상 클래스 또는 일반 클래스 일 수 있습니다 (대부분의 경우 모든 하위 클래스가 공통 코드를 공유 해야하는 경우 유용 할 수 있음) 그러나 interface충분합니다).


답변

아무도 인터페이스가 어떻게 유용한 지에 대한 명확한 용어를 실제로 설명하지 않았으므로, 나는 그것을 쏴볼 것입니다 (그리고 Shamim의 답변에서 약간의 아이디어를 훔칠 것입니다).

피자 주문 서비스에 대한 아이디어를 얻을 수 있습니다. 여러 종류의 피자를 가질 수 있으며 각 피자에 대한 일반적인 조치는 시스템에서 주문을 준비하는 것입니다. 각 피자 는 준비해야 하지만 각 피자 는 다르게 준비됩니다 . 예를 들어, 크러스트 피자를 주문할 때 시스템은 특정 재료가 식당에서 구할 수 있는지 확인하고 딥 디쉬 피자에 필요하지 않은 재료는 따로 보관해야합니다.

코드로 작성하면 기술적으로 할 수 있습니다.

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

그러나 딥 디쉬 피자 (C # 용어로)는 Prepare()박제 빵 껍질과 방법 에 다른 속성을 설정해야 할 수 있으므로 많은 선택적 속성이 생겨 클래스가 확장되지 않습니다 (새로 추가하면 어떻게됩니까? 피자 종류).

이를 해결하는 올바른 방법은 인터페이스를 사용하는 것입니다. 인터페이스는 모든 피자를 준비 할 수 있지만 각 피자를 다르게 준비 할 수 있다고 선언합니다. 따라서 다음 인터페이스가있는 경우

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

이제 주문 처리 코드는 재료를 처리하기 위해 어떤 종류의 피자를 주문했는지 정확히 알 필요가 없습니다. 그것은 단지있다 :

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

각 피자 종류가 다르게 준비되어 있지만 코드의이 부분은 우리가 다루는 피자 종류를 신경 쓸 필요가 없으며 피자를 요구한다는 것을 알고 있으므로 각 피자 Prepare는 자동으로 각 피자를 올바르게 준비합니다 컬렉션에 여러 유형의 피자가 있더라도 유형에 따라 다릅니다.


답변

나에게 처음 시작할 때 코드를 더 쉽고 빠르게 작성하기 위해 그것들을 보지 않을 때만 이것에 대한 요점이 분명 해졌다. 이것은 그들의 목적이 아니다. 그들은 여러 가지 용도로 사용됩니다.

(이것의 사용을 시각화하는 것은 쉽지 않기 때문에 이것은 피자 비유를 잃을 것입니다)

화면에서 간단한 게임을 만들고 있다고 말하면 상호 작용하는 생물이 있습니다.

A : 프론트 엔드와 백엔드 구현 사이에 느슨한 결합을 도입하여 향후 코드를보다 쉽게 ​​유지 관리 할 수 ​​있습니다.

트롤 만있을 것이므로 이것을 시작할 수 있습니다.

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

프런트 엔드 :

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

2 주가 지나면 마케팅 부서에서 Orcs가 필요하다고 판단합니다. 그들은 트위터에서 그들에 대해 읽을 때 다음과 같이해야합니다.

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

프런트 엔드 :

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

그리고 이것이 어떻게 지저분 해지는 지 알 수 있습니다. 여기에서 인터페이스를 사용하여 프론트 엔드를 한 번 작성하고 (중요한 비트는 테스트) 테스트 한 다음 필요에 따라 추가 백엔드 항목을 꽂을 수 있습니다.

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

프런트 엔드는 다음과 같습니다.

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

프론트 엔드는 이제 ICreature 인터페이스에만 관심을 갖습니다. 트롤이나 오크의 내부 구현에 신경 쓰지 않고 ICreature를 구현한다는 사실에만 신경 쓰지 않습니다.

이 관점에서 이것을 볼 때 주목해야 할 중요한 점은 추상 생물체 클래스를 쉽게 사용할 수 있다는 것입니다.이 관점에서 이것은 동일한 효과가 있습니다.

그리고 창조물을 공장으로 추출 할 수 있습니다.

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

그리고 우리의 프론트 엔드는 다음과 같이 될 것입니다.

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

프론트 엔드는 이제 트롤과 오크가 구현 된 라이브러리 (공장이 별도의 라이브러리에있는 경우)에 대한 참조를 가질 필요가 없습니다.

B : 당신은 다른 생물체가 당신의 균질 한 데이터 구조에서 가질 수있는 기능을 가지고 있다고 하자.

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

프런트 엔드는 다음과 같습니다.

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C : 의존성 주입 사용법

프론트 엔드 코드와 백엔드 구현 사이에 매우 느슨한 결합이있을 때 대부분의 의존성 주입 프레임 워크는 작업하기가 더 쉽습니다. 위의 팩토리 예제를보고 팩토리가 인터페이스를 구현하도록하는 경우 :

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

우리의 프론트 엔드는 생성자를 통해 (일반적으로) MVC API 컨트롤러와 같이 이것을 주입 할 수 있습니다.

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

DI 프레임 워크 (예 : Ninject 또는 Autofac)를 사용하면 런타임에 생성자에서 ICreatureFactory가 필요할 때마다 CreatureFactory의 인스턴스가 작성되도록 코드를 설정할 수 있습니다. 이는 코드를 훌륭하고 단순하게 만듭니다.

또한 컨트롤러에 대한 단위 테스트를 작성할 때 조롱 된 ICreatureFactory를 제공 할 수 있습니다 (예를 들어 구체적인 구현에 DB 액세스가 필요하면 단위 테스트를 원하지 않는 경우) 컨트롤러에서 코드를 쉽게 테스트 할 수 있습니다 .

D : 다른 용도가 있습니다. 예를 들어 ‘레거시’이유로 잘 구성되지 않은 두 개의 프로젝트 A와 B가 있고 A는 B에 대한 참조가 있습니다.

그런 다음 B에서 이미 A에서 메소드를 호출해야하는 기능을 찾으십시오. 순환 참조를 얻을 때 구체적인 구현을 사용하여이를 수행 할 수 없습니다.

A의 클래스가 구현하는 인터페이스를 B로 선언 할 수 있습니다. 구체적인 객체가 A 유형 인 경우에도 B의 메소드에는 문제없이 인터페이스를 구현하는 클래스의 인스턴스가 전달 될 수 있습니다.


답변

설명 된 예는 다음과 같습니다.

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}


답변

위의 예는 의미가 없습니다. 클래스를 사용하여 위의 모든 예제를 수행 할 수 있습니다 ( 계약으로 만 작동하려는 경우 추상 클래스 ).

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

인터페이스와 동일한 동작을 얻습니다. List<Food>어떤 클래스가 맨 위에 있는지 알지 않고 반복 하여 만들 수 있습니다 .

더 적절한 예는 다중 상속입니다.

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

그런 다음 모든 것을 사용할 수 있으며 MenuItem각 메소드 호출을 처리하는 방법에 신경 쓰지 않습니다.

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order)
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}


답변

유추에 대한 간단한 설명

해결해야 할 문제 : 다형성의 목적은 무엇입니까?

유추 : 그래서 저는 건설 현장의 감독입니다.

상인은 항상 건설 현장을 걷습니다. 누가 그 문을 통과할지 모르겠습니다. 그러나 나는 기본적으로 그들에게 무엇을해야하는지 말한다.

  1. 목수라면 나무 발판을 만드십시오.
  2. 배관공이라면 “파이프를 설치하십시오”라고 말합니다
  3. 전기 기술자라면 “케이블을 뽑아 광섬유 케이블로 교체하십시오”라고 말합니다.

위의 접근 방식의 문제점은 내가해야한다는 것입니다. 그것은 특정 거래에 대한 모든 것을 알아야한다는 것을 의미합니다. 이 접근 방식과 관련된 비용 / 혜택이 있습니다.

무엇을해야할지 아는 의미 :

  • 이것은 목수의 코드가 : BuildScaffolding()에서 BuildScaffold()(예를 들어 약간의 이름 변경)으로 변경되면 호출 클래스 (예 : Foreperson클래스)도 변경해야합니다. 대신 (기본적으로 코드를 두 번 변경해야합니다) ) 딱 하나만. 다형성을 사용하면 기본적으로 동일한 결과를 얻기 위해 한 번만 변경하면됩니다.

  • 둘째, 당신은 끊임없이 묻지 않아도됩니다. 당신은 누구입니까? 알았어 … 너 누구 니? 알았어. …. 다형성-코드를 건조시키고 특정 상황에서 매우 효과적입니다.

  • 다형성을 사용하면 기존 코드를 변경하지 않고도 추가 직업 클래스를 쉽게 추가 할 수 있습니다. (즉, 두 번째 SOLID 설계 원칙 : 개방형 원칙).

해결책

누가 문을 걷든지 상관없이 “Work ()”라고 말할 수 있고, 배관공은 파이프를 다루고, 전기공은 전선을 다루는 직업을 존중합니다.

이 접근법의 장점은 다음과 같습니다. (i) 누가 그 문을 통해 들어가고 있는지 정확히 알 필요는 없습니다. 내가 알아야 할 것은 그들이 일종의 tradie가되고 일을 할 수 있다는 것입니다. , (ii) 특정 거래에 대해 알 필요가 없습니다. 비극은 그것을 처리 할 것입니다.

그래서 이것 대신에 :

If(electrician) then  electrician.FixCablesAndElectricity()

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

나는 이런 식으로 할 수 있습니다 :

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

장점은 무엇입니까?

이점은 목수 등의 특정 직업 요구 사항이 변경되면 감독자가 코드를 변경할 필요가 없으므로 알거나 돌볼 필요가 없다는 것입니다. 중요한 것은 목수가 Work ()의 ​​의미를 알고 있다는 것입니다. 둘째, 새로운 유형의 건설 노동자가 구직 사이트에 올 경우, 감독관은 거래에 대해 아무것도 알 필요가 없습니다. 모든 감독관은 건설 노동자 (예 : 용접기, Glazier, Tiler 등)가 Work ()를 완료하십시오.


설명 된 문제 및 해결책 (인터페이스 유무) :

인터페이스 없음 (예 1) :

예 1 : 인터페이스없이

인터페이스 없음 (예 2) :

예 2 : 인터페이스없이

인터페이스 :

예 3 : 인터페이스 사용의 이점

요약

인터페이스를 사용하면 자신이 누구인지 또는 무엇을 할 수 있는지에 대한 구체적인 지식 없이도 자신이 할당 된 작업을 수행 할 수 있습니다. 이를 통해 기존 코드를 변경하지 않고도 새로운 유형의 거래를 쉽게 추가 할 수 있습니다 (기술적으로는 아주 조금만 변경하는 것이 좋습니다).

위의 내용을 이해하지 못하거나 명확하지 않은 경우 의견을 요청하면 더 나은 답변을 얻으려고 노력할 것입니다.


답변

파이썬에서 사용할 수있는 오리 타이핑 이 없으면 C #은 인터페이스를 사용하여 추상화를 제공합니다. 클래스의 종속성이 모두 구체적 유형이면 다른 유형을 전달할 수 없습니다. 인터페이스를 사용하여 인터페이스를 구현하는 모든 유형을 전달할 수 있습니다.