전체 페이지뷰

2016년 12월 20일 화요일

C# Interface

공부하면서 가장 궁금했던게 interface의 용법이었습니다.
여기저기 검색해봐도 필요하다...필수적이다..때에 따라 필요하다..디자인패턴을 알면 반드시 필요함을 알게 될거다...우리 팀에서는 금기시 했다...등 다들 대답이 제각각이어서 감을 잡기 어려웠습니다.

일단 그 형식을 보면 제 궁금증이 어디로부터 기인했는가를 아실수 있을겁니다.

먼저 인터페이스의 사전적 의미부터 알아보도록 합니다.

사물과 사물 사이 또는 사물과 인간 사이의 경계에서, 상호 간의 소통을 위해 만들어진 물리적 매개체나 프로토콜을 말한다.출처 두산백과

인터페이스란 단어부터가 모호하고 추상적입니다. 그래서 더욱 개념을 잡기가 어려운 듯 합니다.

interface 인터페이스명
{
    메소드,이벤트,인덱서 등;
}

interface ILogger
{
    void ShowLog( string log );
}
cs
메소드만으로 이루어진 간단한 예를 들었습니다.
보시다시피 메소드의 이름(관례적으로 "I"로 시작하여 인터페이스임을 알수 있게 합니다)과 매개변수만 있고 실제 구현부가 없습니다. 그리고 기본적으로 public으로 설정됩니다. 인터페이스는 이것이 다입니다. 
처음에 볼 때, 아무 내용도 없는 이런 걸 왜 복잡하게 선언하나 생각이 들었습니다.

class ConsoleLogger : ILogger
{
    public void ShowLog( string message)
    {
        Console.WriteLine("{0} {1}", DateTime.Now.ToLocalTime(), message);
    }
}
cs
실제 이용시에는  이렇게 인터페이스를 상속받는 파생클래스를 만들고,
파생 클래스 내에서 반드시 인터페이스의 내용을 구체적으로 작성해 줍니다.

ILogger logger = new ConsoleLogger();
logger.ShowLog("Passenger Detected");
cs
이런 식으로 인스턴스를 만들어 사용합니다.

인터페이스만으로는 인스턴스를 만들거나 하는 행위를 할수 없지만 위와 같은 식으로 상속받은 클래스를 이용해 인터페이스의 객체로 취급 가능한 인스턴스를 생성할 수 있습니다.
다시 말해 어떤 클래스던지 ILogger를 상속받아 ShowLog()만 구현해 주면 ILogger의 역할을 할 수 있다는 말입니다.

콘솔이 아닌 텍스트 파일에 로그를 출력하는 클래스도 만들어 봅니다.

class FileLogger : ILogger
{
    private StreamWriter writer;
    public FileLogger( string path)
    {
        writer = File.CreateText(path);
        writer.AutoFlush = true;
    }
    public void ShowLog(string message)
    {
        writer.WriteLine("{0} {1}", DateTime.Now.ToShortTimeString(), message);
    }
}
cs
파일 출력에 대한 것은 나중에 다시 공부할 예정이므로 어떤식으로 받아 구현하는지만 보겠습니다.

이제 이 모든 것을 합쳐 활용하는 예를 완성해 보겠습니다.
using System;
using System.IO;
class Program
{
    //인터페이스 정의
    interface ILogger
    {
        void ShowLog(string log);
    }
    //콘솔출력 형식의 클래스
    class ConsoleLogger : ILogger
    {
        public void ShowLog(string message)
        {
            Console.WriteLine("{0} {1}", DateTime.Now.ToLocalTime(), message);
        }
    }
    // 파일출력 형태의 클래스
    class FileLogger : ILogger
    {
        private StreamWriter writer;
        public FileLogger( string path)
        {
            writer = File.CreateText(path);
            writer.AutoFlush = true;
        }
        public void ShowLog(string message)
        {
            writer.WriteLine("{0} {1}", DateTime.Now.ToShortTimeString(), message);
        }
    }
    // 가상의 감지장치 클래스
    class PassengerDetector
    {
        private ILogger logger;
        public PassengerDetector(ILogger logger)
        {
            this.logger = logger;
        }
        public void startToDetect()
        {
            while (true)
            {
                Console.Write("지나가는 사람이 있습니까(y/n)?");
                string anyPassenger = Console.ReadLine();
                if (anyPassenger == "n" || anyPassenger == "N")
                    break;
                else if (anyPassenger =="y" || anyPassenger =="Y")
                    logger.ShowLog("Passenger Detected");
            }
        }
    }
    static void Main(string[] args)
    {
        PassengerDetector detector1 = new PassengerDetector(new ConsoleLogger());
        detector1.startToDetect();
        //파일출력을 원하면 아래 코드
        //PassengerDetector detector2 = new PassengerDetector(new FileLogger("MyLog.txt"));
        //detector2.startToDetect();
    }
}
cs

C#은 다중상속을 허용하지 않습니다. 그것은 이어받은 클래스들의 메소드나 속성간에 혼란을 줄 여지를 원천 차단하기 위해서인데, 위의 PassengerDetector 클래스는 ConsoleLogger나 FileLogger 중 어느 하나를 상속받는 것이 아니라 ILogger라는 최소한의 공통의 툴을 내부에 둠으로서 마치 두 클래스를 모두 상속 받는 것처럼 행동하고 있습니다. 그렇게 함으로서 다중상속을 허용치 않으면서도 마치 허용하는 듯한 유연성을 부여하고 있습니다.

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

클래스는 물론 구조체, 인터페이스도 인터페이스를 상속받을수 있습니다.
인터페이스를 수정할 수 없는 경우(예를 들어 .NET 프레임워크 내에 제공되는 인터페이스)나, 이미 인터페이스를 상속받아 만들어진 많은 클래스들이 존재하여 전부 수정하기 어려울 때 기존의 것을 수정하지 않고도 인터페이스를 상속받은 인터페이스를 작성하여 이용 가능합니다.

형식은 지금까지와 마찬가지로,

interface 파생인터페이스 : 부모인터페이스
{
    //추가할 목록;
}
입니다.

여러 개의 인터페이스 상속하기

인터페이스의 다중 상속이라는 방법으로 유연성을 더합니다.
using System;
using System.IO;
class Program
{
    interface IDanceable
    {
        void dance();
    }
    interface ISingable
    {
        void sing();
    }
    class MultiEntertainer : IDanceable, ISingable
    {
        public void dance()
        {
            Console.WriteLine("현란한 춤");
        }
        public void sing()
        {
            Console.WriteLine("4옥타브 스크림");
        }
    }
    static void Main(string[] args)
    {
        MultiEntertainer entertainer = new MultiEntertainer();
        entertainer.dance();
        entertainer.sing();
    }
}
cs


추상 클래스

"추상"이라는 이름에서 알 수 있듯이 "abstract"라는 키워드로 수식된 추상 클래스는 구체화된 인스턴스를 만들 수 없습니다. 다만 추상클래스를 상속하여 파생클래스를 만들면 그것으로 인스턴스를 만들어 사용 가능합니다.

abstract class 클래스명
{
    //코드;
}
와 같은 문법을 가집니다.

이 클래스의 내부에는 메소드를 가질 수 있는데 특별한 한정자를 쓰지 않으면 기본으로 private이 됩니다. 클래스이기 때문이죠. 인터페이스는 public이었습니다. 
만약 메소드까지 abstract로 지정한다면 그 메소드도 추상메소드가 되어 특별한 내용을 쓰지 않고 마치 인터페이스처럼 상속받은 클래스에서 구체적으로 구현해 줘야 합니다(이 경우에는 private이 아닌 public, protected,internal, protected internal 중 하나로 수식해 줘야 합니다).

예를 들어 살펴 보겠습니다.
using System;
class Program
{
    abstract class A
    {
        public void MethodA()
        {
            Console.WriteLine("추상클래스의 MethodA");
        }
        public abstract void MethodB();
    }
    class B :A
    {
        public override void MethodB()
        {
            Console.WriteLine("파생클래스의 MethodB");
        }
    }
    static void Main(string[] args)
    {
        A obj = new B();
        obj.MethodA();
        obj.MethodB();
    }
}
cs

추상 클래스 A를 정의하고 그 안에 일반 메소드인 MethodA()와 추상메소드 MethodB()를 작성했습니다. MethodA는 물론 추상이 아니므로 실제 내용까지 구형해 줘야 하며 상속시 구현할 필요가 없습니다.

인터페이스와 추상클래스를 두고 상당한 혼란이 옵니다만, 제 생각에 둘은 상속을 바라보는 관점의 차이에 있는 것 같습니다.

인터페이스는 서로 다른 클래스들이 같은 이름의 메소드를 작성하게 강제하여 사용자 인터페이스를 통일하게 합니다.
그리고, 추상 클래스는 형식만 존재하는 클래스를 작성하여 이로부터 파생되는 모든 클래스들이 메소드를 통일하게 합니다.

요컨대 메소드의 관점에서 클래스를 묶어주는 것이 인터페이스라면 클래스의 관점에서 메소드를 통일하게 하는 것이 추상클래스라는 것입니다. 이를 통해서 상속 시 발생할 수 있는 충돌 등의 문제를 해결하게 해 주면서도 다양한 상속을 가능하게 하는 것처럼 행동하게 해 주어 유연성을 더한다는 것입니다.

댓글 없음:

댓글 쓰기