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