C#

인터페이스와 추상클래스

Barbarian developer 2024. 10. 31.

인터페이스는 클래스가 따라야 하는 약속 같은 것입니다. 이 인터페이스를 구현하는 클래스는 약속된 메서드를 반드시 만들어야 합니다. 인터페이스는 기능의 이름만 정해 놓고, 실제 내용은 만들지 않아요. 클래스가 그 기능을 실제로 어떻게 할지 정합니다.

 

using System;
using System.IO;

namespace Interface
{
    // ILogger 인터페이스: 로그를 기록하는 역할을 정의
    interface ILogger
    {
        void WriteLog(string message); // 로그를 기록하는 메서드
    }

    // ConsoleLogger 클래스: ILogger를 구현하며, 메시지를 콘솔에 출력하는 클래스
    class ConsoleLogger : ILogger
    {
        public void WriteLog(string message)
        {
            Console.WriteLine(
                "{0} {1}",
                DateTime.Now.ToLocalTime(), message); // 현재 시간과 메시지를 콘솔에 출력
        }
    }

    // FileLogger 클래스: ILogger를 구현하며, 메시지를 파일에 기록하는 클래스
    class FileLogger : ILogger
    {
        private StreamWriter writer; // 파일에 쓰기 위한 StreamWriter 객체

        // 생성자: 파일 경로를 받아 파일을 생성 및 열기
        public FileLogger(String path)
        {
            writer = File.CreateText(path); // 파일을 생성하여 StreamWriter로 작성
            writer.AutoFlush = true; // 버퍼를 자동으로 비우도록 설정
        }

        public void WriteLog(string message)
        {
            // 현재 시간과 메시지를 파일에 기록
            writer.WriteLine("{0} {1}", DateTime.Now.ToShortTimeString(), message);
        }
    }

    // ClimateMonitor 클래스: 온도를 입력받고 로그를 기록하는 클래스
    class ClimateMonitor
    {
        private ILogger logger; // ILogger 타입의 객체로, ConsoleLogger나 FileLogger를 받을 수 있음

        // 생성자: ILogger 타입의 객체를 받아 logger 필드에 저장
        public ClimateMonitor(ILogger logger)
        {
            this.logger = logger;
        }

        // start 메서드: 사용자로부터 온도를 입력받아 기록
        public void start()
        {
            while (true)
            {
                Console.Write("온도를 입력해주세요.:");
                string temperature = Console.ReadLine(); // 온도를 입력받음
                if (temperature == "") // 빈 문자열 입력 시 루프 종료
                    break;

                logger.WriteLog("현재온도 :" + temperature); // 입력된 온도를 로그에 기록
            }
        }
    }

    // 프로그램의 진입점
    class MainApp
    {
        static void Main(string[] args)
        {
            // FileLogger로 ClimateMonitor 객체 생성 (로그를 파일에 기록)
            ClimateMonitor monitor = new ClimateMonitor(new FileLogger("MyLog.txt"));
            monitor.start(); // 온도를 입력받아 파일에 기록
        }
    }
}

 

<실행결과>

 

 

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

 

 

인터페이스도 다른 인터페이스를 상속할 수 있습니다. 이렇게 하면 기존 인터페이스에 기능을 추가하면서도 원본 인터페이스는 수정하지 않아, 기존 코드를 안정적으로 유지할 수 있습니다.

인터페이스 상속이 필요한 이유

 

수정이 불가능한 외부 인터페이스에 기능을 추가할 때

예를 들어, .NET SDK에 포함된 인터페이스는 어셈블리 파일로만 제공되기 때문에 수정이 불가능합니다. 이때 새로운 기능을 추가한 인터페이스를 만들려면 기존 인터페이스를 상속하는 새로운 인터페이스를 만들어야 합니다.


기존 인터페이스를 수정하면 기존 코드에 문제가 발생할 때

인터페이스에 새로운 메서드를 추가하면, 이 인터페이스를 구현하는 모든 클래스에 새로운 메서드가 구현되어야 합니다. 기존에 작성된 많은 클래스가 이미 이 인터페이스를 구현하고 있는 경우 수정으로 인해 컴파일 오류가 발생할 수 있습니다.


이때는 기존 인터페이스를 상속하는 새로운 인터페이스를 만들어서, 기존 코드를 변경하지 않고도 필요한 기능을 추가할 수 있습니다.

 

인터페이스가 인터페이스를 상속하기 위해 사용하는 문법은 클래스의 문법과 똑같습니다.

 

using System;

namespace DerivedInterface
{
    // ILogger 인터페이스: 기본적인 로그 기능을 정의
    interface ILogger
    {
        void WriteLog(string message); // 로그를 기록하는 메서드
    }

    // IFormattableLogger 인터페이스: ILogger를 상속하며, 포맷팅된 로그를 기록하는 기능을 추가
    interface IFormattableLogger : ILogger
    {
        // 문자열 형식과 여러 인자를 받아 포맷팅된 메시지를 기록하는 메서드
        void WriteLog(string format, params Object[] args);
    }

    // ConsoleLogger2 클래스: IFormattableLogger를 구현하여 콘솔에 로그를 출력하는 클래스
    class ConsoleLogger2 : IFormattableLogger
    {
        // 단순한 메시지를 받아 로그를 기록하는 메서드
        public void WriteLog(string message)
        {
            Console.WriteLine(
                $"{DateTime.Now.ToLocalTime()}, {message}"); // 현재 시간과 메시지를 출력
        }

        // 포맷팅된 메시지를 받아 로그를 기록하는 메서드
        public void WriteLog(string format, params Object[] args)
        {
            // 문자열을 format 형식에 맞게 포맷팅하여 message에 저장
            String message = String.Format(format, args);            
            Console.WriteLine(
                $"{DateTime.Now.ToLocalTime()}, {message}"); // 현재 시간과 포맷팅된 메시지를 출력
        }
    }

    // 프로그램의 메인 클래스
    class MainApp
    {
        static void Main(string[] args)
        {
            // IFormattableLogger 타입의 logger 변수에 ConsoleLogger2 객체를 생성하여 저장
            IFormattableLogger logger = new ConsoleLogger2();

            // 기본 로그 메서드 호출 - 단순한 메시지 출력
            logger.WriteLog("The world is not flat."); // 출력: 현재 시간과 "The world is not flat."

            // 포맷팅된 로그 메서드 호출 - {0}, {1}, {2} 자리에 1, 1, 2가 들어가 출력
            logger.WriteLog("{0} + {1} = {2}", 1, 1, 2); // 출력: 현재 시간과 "1 + 1 = 2"
        }
    }
}

 

인터페이스의 다중 상속 예

using System;

namespace MultiInterfaceInheritance
{
    // IRunnable 인터페이스는 Run 메서드를 선언하여 구현해야 하는 기능을 정의합니다.
    interface IRunnable
    {
        void Run(); // 달릴 수 있는 기능의 인터페이스 메서드
    }

    // IFlyable 인터페이스는 Fly 메서드를 선언하여 구현해야 하는 기능을 정의합니다.
    interface IFlyable
    {
        void Fly(); // 날 수 있는 기능의 인터페이스 메서드
    }

    // FlyingCar 클래스는 두 인터페이스를 구현하여, 달리는 기능과 나는 기능을 모두 구현합니다.
    class FlyingCar : IRunnable, IFlyable
    {
        // IRunnable 인터페이스의 Run 메서드를 구현합니다.
        public void Run()
        {
            Console.WriteLine("Run! Run!"); // 달릴 때 출력되는 메시지
        }

        // IFlyable 인터페이스의 Fly 메서드를 구현합니다.
        public void Fly()
        {
            Console.WriteLine("Fly! Fly!"); // 날 때 출력되는 메시지
        }
    }

    // 프로그램의 메인 클래스
    class MainApp
    {
        static void Main(string[] args)
        {
            // FlyingCar 객체를 생성합니다.
            FlyingCar car = new FlyingCar();
            
            // 생성한 객체의 Run 메서드를 호출합니다.
            car.Run(); // "Run! Run!" 출력
            
            // 생성한 객체의 Fly 메서드를 호출합니다.
            car.Fly(); // "Fly! Fly!" 출력

            // IRunnable 인터페이스 타입으로 형변환합니다.
            IRunnable runnable = car as IRunnable;
            // 형변환된 객체를 통해 Run 메서드를 호출합니다.
            runnable.Run(); // "Run! Run!" 출력

            // IFlyable 인터페이스 타입으로 형변환합니다.
            IFlyable flyable = car as IFlyable;
            // 형변환된 객체를 통해 Fly 메서드를 호출합니다.
            flyable.Fly(); // "Fly! Fly!" 출력
        }
    }
}

 

추상 클래스

 

 

using System;

namespace AbstractClass
{
    // AbstractBase는 추상 클래스입니다. 이 클래스는 추상 메서드와 구현된 메서드를 모두 포함할 수 있습니다.
    abstract class AbstractBase
    {
        // protected 메서드는 이 클래스와 파생 클래스에서만 접근할 수 있습니다.
        protected void PrivateMethodA()
        {
            Console.WriteLine("AbstractBase.PrivateMethodA()"); // 메서드가 호출되면 이 메시지를 출력합니다.
        }

        // public 메서드는 이 클래스의 인스턴스에서 누구나 호출할 수 있습니다.
        public void PublicMethodA()
        {
            Console.WriteLine("AbstractBase.PublicMethodA()"); // 메서드가 호출되면 이 메시지를 출력합니다.
        }

        // 추상 메서드는 구현이 없으며, 파생 클래스에서 반드시 구현해야 합니다.
        public abstract void AbstractMethodA();
    }

    // Derived 클래스는 AbstractBase를 상속받아 추상 메서드를 구현합니다.
    class Derived : AbstractBase
    {
        // AbstractMethodA를 Derived 클래스에서 구체적으로 구현합니다.
        public override void AbstractMethodA()
        {
            Console.WriteLine("Derived.AbstractMethodA()"); // 메서드가 호출되면 이 메시지를 출력합니다.
            PrivateMethodA(); // 상속받은 클래스 내에서 protected 메서드 호출이 가능합니다.
        }
    }

    // 프로그램의 메인 클래스입니다.
    class MainApp
    {
        static void Main(string[] args)
        {
            // AbstractBase 타입의 참조 변수로 Derived 객체를 생성합니다.
            AbstractBase obj = new Derived();
            
            // Derived 클래스의 AbstractMethodA() 메서드를 호출합니다.
            obj.AbstractMethodA(); // 출력: "Derived.AbstractMethodA()" 후 "AbstractBase.PrivateMethodA()"
            
            // AbstractBase 클래스의 PublicMethodA() 메서드를 호출합니다.
            obj.PublicMethodA(); // 출력: "AbstractBase.PublicMethodA()"
        }
    }
}

'C#' 카테고리의 다른 글

배열과 컬렉션 그리고 인덱서  (0) 2024.11.01
프로퍼티  (3) 2024.11.01
클래스  (0) 2024.10.30
메소드로 코드 간추리기  (0) 2024.10.30
코드의 흐름 제어하기  (1) 2024.10.30

댓글