C#프로그래밍 15 : 인터페이스, 인터페이스와 추상클래스의 비교

LeeWonjin·2022년 5월 17일
0

[학부]C#프로그래밍

목록 보기
15/21

특징

  • 가질 수 있다 : 메소드, 이벤트, 인덱서, 프로퍼티
  • 쓸 수 없다 : 한정자

인터페이스로 직접 인스턴스 생성 불가 (파생클래스의 참조는 할 수 있다.)

선언

interface키워드로 선언.
식별자 앞에 I 를 붙이는 것이 국룰

interface ISomething{
  // 구현부는 포함하지 않는다. 포함하는 경우는 뒤에서 설명
  int method1 ( 매개변수들 ); 
  void method2 ( 매개변수들 );
}

인터페이스를 상속하는 파생클래스

상속

인터페이스를 상속한 파생클래스는, 인터페이스에 정의된 것을 모두 구현해야 한다.
이 때, 인터페이스를 따라 구현하는 것은 public한정자가 붙어야 한다.

인터페이스의 프로퍼티

아래 예시에서는 메소드 뿐 아니라 프로퍼티도 인터페이스에 선언되어있는데,
자동구현 프로퍼티와 모양은 같지만 선언된 것임을 잘 기억해야 한다.

인터페이스에 선언한다고 해서 자동구현되는 것은 아니다.
인터페이스를 상속하는 클래스에서 인터페이스를 따라 선언했을 때 자동구현된다.

namespace Program
{
    interface ILogger
    {
        void ConsoleLog(string str);
        void FileLog(string str);
        void CloseFile();
    }
    class Logger : ILogger
    {
        private StreamWriter writer;

        public Logger(int id)
        {
            writer = new StreamWriter($"output_{id}.txt");
        }
        
        public void ConsoleLog(string str)
        {
            Console.WriteLine(str);
        }

        public void FileLog(string str)
        {
            writer.WriteLine(str);
            writer.AutoFlush = true;
        }

        public void CloseFile()
        {
            writer.Close();
        }
    }

    interface IAnimal { 
        string Sound { get; set; }
        void cry(); 
    }
    class Gorani : IAnimal
    {

        public ILogger Log {get; set;}
        public string Sound { get; set; }

        public void cry ()
        {
            Log.ConsoleLog(Sound);
            Log.FileLog(Sound);
        }

        public void stopCrying()
        {
            Log.CloseFile();
        }
    }

    class Program
    {
        static public void Main(string[] Args)
        {
            IAnimal gorani_1 = new Gorani() { 
                Sound = "weeaeaeak!",
                Log = new Logger(1)
            };
            Gorani gorani_2 = new Gorani()
            {
                Sound = "waaaaaarark!",
                Log = new Logger(2)
            };

            gorani_1.cry(); // weeaeaeak!
            gorani_2.cry(); // waaaaaarark!
        }
    }
}

다중 인터페이스 상속

인터페이스는 다중상속 할 수 있다.

클래스 다중상속시 나타나는 죽음의 다이아몬드 문제가 인터페이스 다중상속시에는 나타나지 않기 때문이다.
(애초에 구현부를 작성하지 않기 때문)

죽음의 다이아몬드 문제 : 여러 클래스를 상속받을 때 같은 식별자 + 같은 종류의 멤버가 있다면, 어떤 것을 상속받을 지 결정할 수 없는 것.

namespace Program
{
    interface IA { void A(); }
    interface IB { void B(); }

    class Child : IA, IB
    {
        public void A() { Console.WriteLine("A()"); }
        public void B() { Console.WriteLine("B()"); }
    }

    class Program
    {
        static public void Main(string[] Args)
        {
            Child child = new Child();
            child.A(); // A()
            child.B(); // B()
        }      
    }
} 

인터페이스의 다형성

같은 인터페이스를 상속해도, (당연히)각자 자기가 구현한 메소드를 뱉는다.
아래 예시는 다형성뿐 아니라 느슨한 결합이라는 특징을 보여준다.

namespace Program
{
    interface I { void WhoAmI(); }

    class Child_A : I
    {
        public void WhoAmI() { Console.WriteLine("Child_A"); }
    }
    class Child_B : I
    {
        public void WhoAmI() { Console.WriteLine("Child_B"); }
    }

    class Printer
    {
        public void Print(I instance)
        {
            instance.WhoAmI();
        }
    }
    class Program
    {
        static public void Main(string[] Args)
        {
            Child_A a = new Child_A();
            Child_B b = new Child_B();
            Printer p = new Printer();

            // 다형성
            a.WhoAmI(); // Child_A
            b.WhoAmI(); // Child_B

            Console.WriteLine();

            // 느슨한 결함
            p.Print(a); // Child_A
            p.Print(b); // Child_B
        }
    }
}

빈 인터페이스의 상속

어떤 클래스에 태그를 붙이듯 사용할 수 있다.

namespace Program
{
    interface IStudent {  }
    class Person { public virtual void Say() { } }

    class Person_A : Person, IStudent
    {
        public override void Say()
        {
            Console.WriteLine("I AM Person_A");
        }
    }
    class Person_B : Person
    {
        public override void Say()
        {
            Console.WriteLine("I AM Person_B");
        }
    }

    class Program
    {
        static public void Main(string[] Args)
        {
            Person_A a = new Person_A();
            Person_B b = new Person_B();

            Person[] arr = { a, b };
            foreach(Person item in arr) // I AM Person_A
            {
                if (item is IStudent)
                    item.Say();
            }
        }      
    }
}

인터페이스를 활용한 콜백

메소드 UseCallback()은 인수로 ISomething형을 받는다.
Isomething인터페이스를 상속한 클래스는 Print()를 갖고 있을 것이기 때문에, 이를 믿고 호출할 수 있다.

namespace Program
{
    interface ISomething { string Print(); }

    class Something : ISomething
    {
        public string Print() { return "SOMETHING"; }
        public void CallAnotherMethod()
        {
            AnotherClass another = new AnotherClass();
            another.UseCallback(this);
        }
    }

    class AnotherClass
    {
        public void UseCallback(ISomething instance)
        {
            Console.WriteLine(instance.Print());
        }
    }

    class Program
    {
        static public void Main(string[] Args)
        {
            Something st = new Something();
            st.CallAnotherMethod(); // SOMETHING
        }      
    }
}

인터페이스를 상속하는 인터페이스

인터페이스도 인터페이스를 상속할 수 있다.
클래스 상속이랑 개념은 비슷함.

namespace Program
{
    interface IParent 
    { 
        void AAA(); 
    }

    interface IChild : IParent
    {
        void AAA(int n); // 오버로드
        void BBB(string s);
    }

    class Something : IChild // IParent, IChild를 모두 받는것이나 마찬가지
    {
        public void AAA() { Console.WriteLine("hihi"); }
        public void AAA(int n) { Console.WriteLine(n); }
        public void BBB(string s) { Console.WriteLine(s); }
    }
    class Program
    {
        static public void Main(string[] Args)
        {
            Something something = new Something();
            something.AAA();
            something.AAA(53);
            something.BBB("HIHIHIHIHIHIHIHIHIHIHI");
        }
    }
}

인터페이스의 기본구현 메소드

인터페이스는 구현부 없는 메소드 뿐 아니라, 구현이 있는 메소드도 가질 수 있다.
이를 기본구현 메소드라고 하는데, 이 메소드는 파생클래스에서 호출할 수 없다.

인터페이스를 상속받은 클래스를, 인터페이스로 업캐스팅 해야 호출할 수 있다.
만약 기본구현 메소드와 같은 식별자로 파생클래스에서 재정의하는 경우, 하이딩과 같이 동작한다.

namespace Program
{
    interface I
    { 
        void AAA();
        void BBB()
        {
            Console.WriteLine("BBB() in interface I");
        }
    }

    class Something : I
    {
        public void AAA() { Console.WriteLine("AAA() in class Something"); }
    }

    class Something_hasBBB : I
    {
        public void AAA() { Console.WriteLine("AAA() in class Something"); }
        public void BBB() { Console.WriteLine("BBB() in class Something");  }
    }

    class Program
    {
        static public void Main(string[] Args)
        {
            // 기본구현메소드 BBB 가짐
            Something obj_1 = new Something();
            obj_1.AAA(); // AAA() in class Something
            //obj_1.BBB(); // 컴파일에러
            ((I)obj_1).BBB(); // BBB() in Interface I

            Console.WriteLine();

            // BBB 덮어씌워짐
            Something_hasBBB obj_2 = new Something_hasBBB();
            obj_2.AAA(); // AAA() in class Something
            obj_2.BBB(); // BBB() in class Something
            ((I)obj_2).BBB(); // BBB() in class Something
        }
    }
}

추상클래스와의 차이

키워드

인터페이스는 interface, 추상클래스는 abstract

abstract class Something { }

기본 접근제한자

인터페이스는 public, 추상클래스는 private

메소드

  • 인터페이스는 두 가지 종류의 메소드가 있다.
    • 구현X : 파생클래스에서 구현해야 하는, 구현이 없는 메소드
      ---> 인터페이스 제한자 없고, 파생클래스 구현시 public
    • 구현O : 인터페이스로 업캐스팅 해야 접근가능한 기본구현 메소드
  • 추상클래스는 두 가지 종류의 메소드가 있다.
    • 구현X : 파생클래스에서 override키워드를 붙여 정의해야 하는 추상메소드
      ---> 추상클래스 제한자 아무거나 가능하고, 파생클래스 구현시 부모 따라감.
    • 구현O : 일반적인 클래스의 구현이 있는 메소드 (그냥 늘 보던 그 메소드)
namespace Program
{
    abstract class AbstractSomething
    {
        public abstract int AbstractNum { get; set; }
        public int Num { get; set; } = 53;

        public abstract void AbstractPrint();
        public void Print()
        {
            Console.WriteLine("Print()");
        }
    }

    class Something : AbstractSomething
    {
        public override int AbstractNum { get; set; } = 535353;

        public override void AbstractPrint()
        {
            Console.WriteLine("AbstractPrint()");
        }
    }

    class Program
    {
        static public void Main(string[] Args)
        {
            Something AA = new Something();

            Console.WriteLine(AA.Num); // 53
            Console.WriteLine(((AbstractSomething)AA).Num); // 53
            AA.Print(); // Print()
            ((AbstractSomething)AA).Print(); // Print()

            Console.WriteLine(AA.AbstractNum); // 535353
            Console.WriteLine(((AbstractSomething)AA).AbstractNum); // 535353
            AA.AbstractPrint(); // AbsPrint()
            ((AbstractSomething)AA).AbstractPrint(); // AbsPrint()
        }      
    }
}
profile
노는게 제일 좋습니다.

2개의 댓글