Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C#] 인터페이스 (interface) 본문

Programming/C#

[Programming/C#] 인터페이스 (interface)

scii 2020. 9. 10. 14:23

인터페이스는 객체 지향 프로그래밍을 한층 더 강력하게 만들어주는 요소이다. 객체 지향 프로그래밍의 꽃이라고도 불리며 객체 지향 프로그래밍의 고수는 인터페이스를 잘 활용할 수 있어야 한다고 말하기도 한다.

인터페이스의 선언

인터페이스(Interface)는 다음과 같이 interface 키워드를 이용하여 선언한다.

interface 인터페이스이름
{
    반환형식 메소드이름1(매개 변수 목록);
    반환형식 메소드이름2(매개 변수 목록);
    .
    .
    .
}

// 인터페이스 실제 정의 예
interface ILogger
{
    void WriteLog( string log );
}

클래스를 선언하는 것과 비슷하다. 허나 메소드, 이벤트, 인덱서, 프로퍼티만을 가질 수 있다. 그런데 그나마도 구현부가 없다. 그리고 클래스는 접근 제한 한정자로 수식하지 않으면 기본적으로 private로 선언되지만, 인터페이스는 접근 제한 한정자를 사용할 수 없고 모든 것이 public으로 선언된다.
게다가 인터페이스는 추상 클래스와 마찬가지로 인스턴스도 만들 수 없다. 단, 인터페이스를 상속받는 클래스의 인스턴스를 만드는 것은 가능하다. 물론 이 때에도 규칙이 있다. 파생 클래스는 인터페이스에 선언되어 있는 모든 메소드 및 프로퍼티를 구현해줘야 하며 이 메소드들은 public 한정자로 수식해야 한다.

다음은 ILogger 인터페이스를 상속받는 파생 클래스의 예이다.

class ConsoleLogger : ILogger
{
    public void WriteLog(string message)
    {
        Console.WriteLine("{0} {1}", DateTime.Now.ToLocalTime(), message);
    }
}

위와 같이 선언한 클래스는 다음과 같이 인스턴스화가 가능하다.

ILogger logger = new ConsoleLogger();
logger.WriteLog("Hello World!");

위 코드처럼 인터페이스는 인스턴스를 못 만들지만, 참조는 만들 수 있다. 이 참조에 파생 클래스의 객체의 위치를 담는 것이다. 
파생 클래스는 부모 클래스와 같은 형식으로 간주한다. 이것은 인터페이스와 인터페이스로부터 상속받는 클래스의 관계에도 그대로 적용된다. 즉, ConsoleLogger의 객체는 ILogger의 객체로 취급할 수 있다는 이야기다. 
인터페이스 선언에 관련된 C# 문법은 이 정도가 거의 전부라고 할 수 있다. 

인터페이스 작명법

대개 C# 프로그래머들 사이에서는 인터페이스의 이름 앞에 'I' 를 붙이는 것이 관례이다.

인터페이스는 약속이다

인터페이스는 클래스가 따라야 하는 약속이다. 이 약속은 인터페이스로부터 파생될 클래스가 어떤 메소드를 구현해야 할지를 정의한다.
위에서 예로 들었던 ILogger 인터페이스는 자신으로부터 파생될 클래스가 반드시 WriteLog() 메소드를 구현하도록 강제한다. 어떤 클래스든 ILogger를 상속받아 ILogger에 선언되어 있는 WriteLog() 메소드를 구현하면 ILogger의 역할을 할 수 있다.  ILogger 인터페이스를 상속 받는 ConsoleLogger 클래스를 선언하여 콘솔에 로그를 출력하도록 WriteLog() 메소드를 구현했는데, 이 외에도 얼마든지 ILogger를 상속받는 새로운 클래스를 선언해서 파일에 로그를 출력하도록 할 수도 있고 네트워크 너머에 있는 서버에 저장하도록 패킷을 보낼 수도 있다.
예를 들어 사용자로부터 온도를 반복적으로 입력받아 기록하는 ClimateMonitor 클래스를 만드는데, 로그를 저장하는 방식 등은 ClimateMonitor 클래스를 사용하는 다른 프로그래머들의 입맛에 따라 결정할 수 있어야 한다고 생각해보자. 이런 때 인터페이스는 아주 훌륭한 해결책이 되어준다.
ClimateMonitor 클래스는 ILogger 형식의 참조 logger를 이용해서 사용자로부터 입력받은 온도를 기록한다. logger가 어떻게 이 메시지를 기록할지는 ClimateMonitor() 생성자에 매개 변수로 입력된 객체에 달려 있다. Callback 함수처럼 작동한다.

class ClimateMonitor
{
    private ILogger logger;
    
    public ClimateMonitor(ILogger logger) => this.logger = logger;
    
    public void start()
    {
        while (true)
        {
            Console.Write("온도 입력 : ");
            string temperature = Console.ReadLine();
            if (temperature == "")
                break;
            logger.WriteLog("현재 온도 : " + temperature);
        }
    }
}

다음과 같이 ConsoleLogger 객체를 생성자에 매개 변수로 넘기면 ClimateMonitor는 콘솔에 메시지를 출력하게 된다.

ClimateMonitor monitor = new ClimateMonitor(new ConsoleLogger());
monitor.start();

이번엔 콘솔이 아닌 텍스트 파일에 로그를 출력하는 ILogger의 파생 클래스를 보자. FileLogger는 StreamWriter 클래스를 이용해 파일에 로그를 기록한다. 

class FileLogger : ILogger
{
    private StreamWriter writer;
    
    public FileLogger(string path)
    {
        writer = File.CreateText(path);
        writer.AutoFlush = true;
    }
    
    public void WriteLog(string message)
    {
        writer.WriteLine("{0} {1}", DateTime.Now.ToShortTimeString(), message);
    }
}

FileLogger의 객체를 다음과 같이 ClimateMonitor 생성자에 넘기면 이제 monitor 객체는 파일에 로그를 저장하는 기능을 갖게 된다.

ClimateMonitor monitor = new ClimateMonitor(new FileLogger("파일경로"));
mointor.start();

interface 사용 예제

using System;
using System.Globalization;
using System.IO;

namespace CSharpExample
{
    interface ILogger
    {
        void WriteLog(string message);
    }

    class ConsoleLogger : ILogger
    {
        public void WriteLog(string message)
        {
            DateTime dt = DateTime.Now.ToLocalTime();
            CultureInfo ciKo = new CultureInfo("ko-KR");
            string dt_format = dt.ToString("yyyy-MM-dd HH:mm:ss (ddd)", ciKo);
            Console.WriteLine($"{dt_format} {message}");
        }
    }

    class FileLogger : ILogger
    {
        private StreamWriter writer;

        public FileLogger(string path)
        {
            writer = File.CreateText(path);
            writer.AutoFlush = true;
        }

        public void WriteLog(string message)
        {
            DateTime dt = DateTime.Now.ToLocalTime();
            CultureInfo ciKo = new CultureInfo("ko-KR");
            string dt_format = dt.ToString("yyyy-MM-dd HH:mm:ss (ddd)", ciKo);
            writer.WriteLine($"{dt_format} {message}");
        }
    }

    class ClimateMonitor
    {
        private ILogger logger;

        // 생성자
        public ClimateMonitor(ILogger logger) => this.logger = logger;

        public void Start()
        {
            while (true)
            {
                Console.Write("온도 입력: ");
                string temperature = Console.ReadLine();
                if (temperature == "")
                    break;
                logger.WriteLog("현재 온도 : " + temperature);
            }
        }
    }

    class MainApp
    {
        static int Main(string[] args)
        {
            // 콘솔에 로그 기록
            ClimateMonitor consoleMonitor = new ClimateMonitor(new ConsoleLogger());
            consoleMonitor.Start();

            /*
            // 파일에 로그 기록
            string filePath = @"c:\users\scii\desktop\test.txt";
            ClimateMonitor fileMonitor = new ClimateMonitor(new FileLogger(filePath));
            fileMonitor.Start();
            */

            return 0;
        }
    }
}


/* 결과

온도 입력: 30
2020-09-10 16:14:02 (목) 현재 온도 : 30
온도 입력: 55
2020-09-10 16:14:03 (목) 현재 온도 : 55
온도 입력: 60
2020-09-10 16:14:04 (목) 현재 온도 : 60
온도 입력: 69
2020-09-10 16:14:05 (목) 현재 온도 : 69
온도 입력: 70
2020-09-10 16:14:08 (목) 현재 온도 : 70

*/
Comments