안녕하세요! 이미지에 보이는 9가지 객체지향 디자인 패턴을 C#으로 실생활 예시와 함께 설명해 드리겠습니다.
실생활 시나리오: 피자 주문 애플리케이션
// 인터페이스 정의
public interface IPizza
{
void Prepare();
void Bake();
void Cut();
void Box();
}
// 구체적인 피자 클래스들
public class CheesePizza : IPizza
{
public void Prepare() { Console.WriteLine("치즈 피자 준비 중..."); }
public void Bake() { Console.WriteLine("치즈 피자 굽는 중..."); }
public void Cut() { Console.WriteLine("치즈 피자 자르는 중..."); }
public void Box() { Console.WriteLine("치즈 피자 포장 중..."); }
}
public class PepperoniPizza : IPizza
{
public void Prepare() { Console.WriteLine("페퍼로니 피자 준비 중..."); }
public void Bake() { Console.WriteLine("페퍼로니 피자 굽는 중..."); }
public void Cut() { Console.WriteLine("페퍼로니 피자 자르는 중..."); }
public void Box() { Console.WriteLine("페퍼로니 피자 포장 중..."); }
}
// 피자 팩토리
public class PizzaFactory
{
public IPizza CreatePizza(string type)
{
IPizza pizza = null;
if (type.Equals("cheese"))
{
pizza = new CheesePizza();
}
else if (type.Equals("pepperoni"))
{
pizza = new PepperoniPizza();
}
return pizza;
}
}
// 클라이언트 코드
public class PizzaStore
{
public static void Main()
{
PizzaFactory factory = new PizzaFactory();
// 치즈 피자 주문
IPizza cheesePizza = factory.CreatePizza("cheese");
cheesePizza.Prepare();
cheesePizza.Bake();
cheesePizza.Cut();
cheesePizza.Box();
}
}
설명: 팩토리 패턴은 객체 생성 로직을 캡슐화하여 클라이언트 코드와 분리합니다. 클라이언트는 구체적인 클래스를 알 필요 없이 팩토리를 통해 객체를 생성할 수 있습니다.
실생활 시나리오: 데이터베이스 연결 관리자
public class DatabaseConnection
{
// 단일 인스턴스 저장
private static DatabaseConnection instance;
// 스레드 안전을 위한 락 객체
private static readonly object lockObject = new object();
// 연결 정보
private string connectionString;
// 생성자는 private으로 외부에서 접근 불가
private DatabaseConnection()
{
connectionString = "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;";
Console.WriteLine("데이터베이스 연결 설정 완료");
}
// 인스턴스 접근 메서드 (스레드 안전한 구현)
public static DatabaseConnection GetInstance()
{
// 더블 체크 락킹
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new DatabaseConnection();
}
}
}
return instance;
}
public void Connect()
{
Console.WriteLine("데이터베이스에 연결 중...");
// 실제 연결 코드
}
public void Disconnect()
{
Console.WriteLine("데이터베이스 연결 해제 중...");
// 실제 연결 해제 코드
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 어디서든 같은 인스턴스 사용
DatabaseConnection connection1 = DatabaseConnection.GetInstance();
connection1.Connect();
// 두 번째 호출에도 같은 인스턴스 반환
DatabaseConnection connection2 = DatabaseConnection.GetInstance();
// 두 변수가 같은 인스턴스인지 확인
Console.WriteLine($"두 연결이 같은 인스턴스인가: {connection1 == connection2}");
connection2.Disconnect();
}
}
설명: 싱글톤 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 보장합니다. 데이터베이스 연결처럼 리소스가 많이 드는 작업은 여러 인스턴스를 만들면 비효율적이므로 싱글톤으로 관리합니다.
실생활 시나리오: 주문 시스템
// 제품 클래스 (결과물)
public class Pizza
{
public string Dough { get; set; }
public string Sauce { get; set; }
public List<string> Toppings { get; set; } = new List<string>();
public void Display()
{
Console.WriteLine($"도우: {Dough}");
Console.WriteLine($"소스: {Sauce}");
Console.WriteLine("토핑:");
foreach (var topping in Toppings)
{
Console.WriteLine($"- {topping}");
}
}
}
// 빌더 인터페이스
public interface IPizzaBuilder
{
void SetDough(string dough);
void SetSauce(string sauce);
void AddTopping(string topping);
Pizza Build();
}
// 구체적인 빌더
public class MargheritaPizzaBuilder : IPizzaBuilder
{
private Pizza pizza = new Pizza();
public void SetDough(string dough)
{
pizza.Dough = dough;
}
public void SetSauce(string sauce)
{
pizza.Sauce = sauce;
}
public void AddTopping(string topping)
{
pizza.Toppings.Add(topping);
}
public Pizza Build()
{
return pizza;
}
}
// 디렉터 클래스 (선택적)
public class PizzaDirector
{
private IPizzaBuilder builder;
public PizzaDirector(IPizzaBuilder builder)
{
this.builder = builder;
}
public void MakeMargherita()
{
builder.SetDough("씬 크러스트");
builder.SetSauce("토마토");
builder.AddTopping("모짜렐라 치즈");
builder.AddTopping("바질");
}
public void MakePepperoni()
{
builder.SetDough("두꺼운 크러스트");
builder.SetSauce("토마토");
builder.AddTopping("모짜렐라 치즈");
builder.AddTopping("페퍼로니");
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 빌더 생성
IPizzaBuilder builder = new MargheritaPizzaBuilder();
// 디렉터 사용
PizzaDirector director = new PizzaDirector(builder);
director.MakeMargherita();
// 피자 완성
Pizza pizza = builder.Build();
pizza.Display();
// 직접 빌더 사용
builder = new MargheritaPizzaBuilder();
builder.SetDough("일반 도우");
builder.SetSauce("바베큐");
builder.AddTopping("치즈");
builder.AddTopping("올리브");
builder.AddTopping("버섯");
Pizza customPizza = builder.Build();
customPizza.Display();
}
}
설명: 빌더 패턴은 복잡한 객체의 생성 과정을 단계별로 나누어 처리합니다. 위 예시에서는 피자를 만드는 과정을 도우 선택, 소스 선택, 토핑 추가 등의 단계로 나누어 처리합니다.
실생활 시나리오: 오래된 결제 시스템을 새로운 시스템에 통합
// 새 결제 시스템이 요구하는 인터페이스
public interface INewPaymentSystem
{
void ProcessPayment(decimal amount);
bool VerifyPayment(string paymentId);
}
// 기존 레거시 결제 시스템
public class LegacyPaymentSystem
{
public void MakePayment(double amount, string currency)
{
Console.WriteLine($"레거시 시스템: {amount} {currency} 결제 처리 중");
}
public int CheckStatus(string reference)
{
Console.WriteLine($"레거시 시스템: {reference} 결제 상태 확인 중");
return 1; // 1: 성공, 0: 실패
}
}
// 어댑터 클래스
public class PaymentSystemAdapter : INewPaymentSystem
{
private LegacyPaymentSystem legacySystem;
public PaymentSystemAdapter(LegacyPaymentSystem legacySystem)
{
this.legacySystem = legacySystem;
}
public void ProcessPayment(decimal amount)
{
// 새 인터페이스를 레거시 시스템에 맞게 변환
legacySystem.MakePayment((double)amount, "KRW");
}
public bool VerifyPayment(string paymentId)
{
// 레거시 시스템의 응답을 새 인터페이스 형식으로 변환
int status = legacySystem.CheckStatus(paymentId);
return status == 1;
}
}
// 클라이언트 코드
public class PaymentProcessor
{
private INewPaymentSystem paymentSystem;
public PaymentProcessor(INewPaymentSystem paymentSystem)
{
this.paymentSystem = paymentSystem;
}
public void MakeOrder(decimal amount, string orderId)
{
Console.WriteLine($"주문 ID: {orderId}, 금액: {amount}원 결제 시작");
paymentSystem.ProcessPayment(amount);
if (paymentSystem.VerifyPayment(orderId))
{
Console.WriteLine("결제 성공!");
}
else
{
Console.WriteLine("결제 실패!");
}
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 레거시 시스템
LegacyPaymentSystem legacySystem = new LegacyPaymentSystem();
// 어댑터를 통해 새 인터페이스로 변환
INewPaymentSystem adapter = new PaymentSystemAdapter(legacySystem);
// 클라이언트에서 어댑터를 통해 레거시 시스템 사용
PaymentProcessor processor = new PaymentProcessor(adapter);
processor.MakeOrder(50000, "ORDER123");
}
}
설명: 어댑터 패턴은 호환되지 않는 인터페이스를 함께 작동할 수 있게 해줍니다. 위 예시에서는 새로운 결제 시스템 인터페이스를 통해 레거시 결제 시스템을 사용할 수 있도록 어댑터를 만들었습니다.
실생활 시나리오: 카페 음료 주문 시스템
// 기본 인터페이스
public interface IBeverage
{
string GetDescription();
decimal GetCost();
}
// 구체적인 컴포넌트
public class Espresso : IBeverage
{
public string GetDescription()
{
return "에스프레소";
}
public decimal GetCost()
{
return 2000m;
}
}
public class Americano : IBeverage
{
public string GetDescription()
{
return "아메리카노";
}
public decimal GetCost()
{
return 3000m;
}
}
// 데코레이터 추상 클래스
public abstract class BeverageDecorator : IBeverage
{
protected IBeverage beverage;
public BeverageDecorator(IBeverage beverage)
{
this.beverage = beverage;
}
// 기본 구현은 위임
public virtual string GetDescription()
{
return beverage.GetDescription();
}
public virtual decimal GetCost()
{
return beverage.GetCost();
}
}
// 구체적인 데코레이터들
public class MilkDecorator : BeverageDecorator
{
public MilkDecorator(IBeverage beverage) : base(beverage) { }
public override string GetDescription()
{
return $"{beverage.GetDescription()}, 우유 추가";
}
public override decimal GetCost()
{
return beverage.GetCost() + 500m;
}
}
public class WhippedCreamDecorator : BeverageDecorator
{
public WhippedCreamDecorator(IBeverage beverage) : base(beverage) { }
public override string GetDescription()
{
return $"{beverage.GetDescription()}, 휘핑크림 추가";
}
public override decimal GetCost()
{
return beverage.GetCost() + 700m;
}
}
public class CaramelDecorator : BeverageDecorator
{
public CaramelDecorator(IBeverage beverage) : base(beverage) { }
public override string GetDescription()
{
return $"{beverage.GetDescription()}, 카라멜 시럽 추가";
}
public override decimal GetCost()
{
return beverage.GetCost() + 600m;
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 기본 에스프레소
IBeverage espresso = new Espresso();
Console.WriteLine($"{espresso.GetDescription()} - {espresso.GetCost():C}");
// 아메리카노에 우유 추가
IBeverage latteMacchiato = new MilkDecorator(new Americano());
Console.WriteLine($"{latteMacchiato.GetDescription()} - {latteMacchiato.GetCost():C}");
// 에스프레소에 우유, 휘핑크림, 카라멜 시럽 추가 (카푸치노)
IBeverage fancyCoffee = new CaramelDecorator(
new WhippedCreamDecorator(
new MilkDecorator(
new Espresso())));
Console.WriteLine($"{fancyCoffee.GetDescription()} - {fancyCoffee.GetCost():C}");
}
}
설명: 데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가할 수 있게 합니다. 위 예시에서는 기본 음료에 우유, 휘핑크림, 카라멜 등의 부가 요소를 조합하여 새로운 음료를 만들 수 있습니다.
실생활 시나리오: 고해상도 이미지 로딩 지연
// 인터페이스
public interface IImage
{
void Display();
}
// 실제 이미지 - 리소스가 많이 필요한 객체
public class RealImage : IImage
{
private string filename;
public RealImage(string filename)
{
this.filename = filename;
LoadFromDisk();
}
private void LoadFromDisk()
{
Console.WriteLine($"디스크에서 이미지 로딩 중: {filename}");
// 큰 이미지 파일 로딩을 시뮬레이션
Thread.Sleep(1000);
}
public void Display()
{
Console.WriteLine($"이미지 표시 중: {filename}");
}
}
// 프록시 이미지
public class ProxyImage : IImage
{
private RealImage realImage;
private string filename;
public ProxyImage(string filename)
{
this.filename = filename;
}
// 실제로 표시할 때만 RealImage 생성
public void Display()
{
if (realImage == null)
{
realImage = new RealImage(filename);
}
realImage.Display();
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 10개의 이미지를 갤러리에 추가
List<IImage> gallery = new List<IImage>();
for (int i = 1; i <= 10; i++)
{
gallery.Add(new ProxyImage($"image_{i}.jpg"));
}
Console.WriteLine("갤러리 로딩 완료 (이미지는 아직 로딩되지 않음)");
// 사용자가 3번째 이미지를 클릭
Console.WriteLine("\n사용자가 3번째 이미지 클릭");
gallery[2].Display();
// 다시 같은 이미지 클릭 (이미 로딩됨)
Console.WriteLine("\n사용자가 3번째 이미지 다시 클릭");
gallery[2].Display();
// 다른 이미지 클릭
Console.WriteLine("\n사용자가 5번째 이미지 클릭");
gallery[4].Display();
}
}
설명: 프록시 패턴은 다른 객체에 대한 접근을 제어하는 대리자 객체를 제공합니다. 위 예시에서는 무거운 이미지 객체를 실제로 필요할 때만 로딩하도록 제어합니다.
실생활 시나리오: 파일 시스템 구조
// 컴포넌트 인터페이스
public interface IFileSystemComponent
{
string Name { get; }
void Display(int depth = 0);
long GetSize();
}
// 리프 노드 - 파일
public class File : IFileSystemComponent
{
public string Name { get; private set; }
private long size;
public File(string name, long size)
{
Name = name;
this.size = size;
}
public void Display(int depth = 0)
{
Console.WriteLine($"{new string(' ', depth * 2)}파일: {Name} ({FormatSize(size)})");
}
public long GetSize()
{
return size;
}
private string FormatSize(long bytes)
{
if (bytes < 1024) return $"{bytes} B";
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
return $"{bytes / (1024.0 * 1024.0):F1} MB";
}
}
// 복합체 - 폴더
public class Directory : IFileSystemComponent
{
public string Name { get; private set; }
private List<IFileSystemComponent> children = new List<IFileSystemComponent>();
public Directory(string name)
{
Name = name;
}
public void Add(IFileSystemComponent component)
{
children.Add(component);
}
public void Remove(IFileSystemComponent component)
{
children.Remove(component);
}
public void Display(int depth = 0)
{
Console.WriteLine($"{new string(' ', depth * 2)}폴더: {Name} ({FormatSize(GetSize())})");
// 자식 표시
foreach (var component in children)
{
component.Display(depth + 1);
}
}
public long GetSize()
{
// 모든 자식의 크기 합산
long totalSize = 0;
foreach (var component in children)
{
totalSize += component.GetSize();
}
return totalSize;
}
private string FormatSize(long bytes)
{
if (bytes < 1024) return $"{bytes} B";
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
return $"{bytes / (1024.0 * 1024.0):F1} MB";
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 루트 폴더 생성
Directory root = new Directory("C:");
// 문서 폴더 생성
Directory docs = new Directory("Documents");
docs.Add(new File("resume.docx", 38400));
docs.Add(new File("report.pdf", 775000));
// 이미지 폴더 생성
Directory pictures = new Directory("Pictures");
pictures.Add(new File("vacation.jpg", 1560000));
pictures.Add(new File("family.png", 2800000));
// 음악 폴더와 하위 폴더
Directory music = new Directory("Music");
Directory kpop = new Directory("K-Pop");
kpop.Add(new File("song1.mp3", 4500000));
kpop.Add(new File("song2.mp3", 5200000));
music.Add(kpop);
music.Add(new File("classical.mp3", 6100000));
// 루트에 폴더 추가
root.Add(docs);
root.Add(pictures);
root.Add(music);
// 파일 시스템 표시
root.Display();
// 특정 폴더 크기 확인
Console.WriteLine($"\n음악 폴더 크기: {music.GetSize() / (1024.0 * 1024.0):F2} MB");
}
}
설명: 컴포지트 패턴은 객체들을 트리 구조로 구성하여 개별 객체와 객체 그룹을 같은 방식으로 다룰 수 있게 합니다. 위 예시에서는 파일과 폴더를 동일한 인터페이스로 처리합니다.
실생활 시나리오: 온라인 쇼핑몰의 결제 시스템
// 전략 인터페이스
public interface IPaymentStrategy
{
bool Pay(decimal amount);
string GetName();
}
// 구체적인 전략 - 신용카드
public class CreditCardPayment : IPaymentStrategy
{
private string cardNumber;
private string cvv;
private string expiryDate;
private string name;
public CreditCardPayment(string cardNumber, string cvv, string expiryDate, string name)
{
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expiryDate = expiryDate;
this.name = name;
}
public bool Pay(decimal amount)
{
// 실제로는 카드사에 결제 요청
Console.WriteLine($"신용카드로 {amount:C} 결제 처리 중...");
Console.WriteLine($"카드 번호: {MaskCardNumber(cardNumber)}");
return true;
}
public string GetName()
{
return "신용카드";
}
private string MaskCardNumber(string number)
{
// 카드 번호 보안 처리
if (number.Length < 4) return number;
return new string('*', number.Length - 4) + number.Substring(number.Length - 4);
}
}
// 구체적인 전략 - 페이팔
public class PaypalPayment : IPaymentStrategy
{
private string email;
private string password;
public PaypalPayment(string email, string password)
{
this.email = email;
this.password = password;
}
public bool Pay(decimal amount)
{
// 페이팔 API 호출
Console.WriteLine($"페이팔로 {amount:C} 결제 처리 중...");
Console.WriteLine($"이메일: {email}");
return true;
}
public string GetName()
{
return "페이팔";
}
}
// 상황 클래스
public class ShoppingCart
{
private List<string> items = new List<string>();
private Dictionary<string, decimal> prices = new Dictionary<string, decimal>();
public void AddItem(string item, decimal price)
{
items.Add(item);
prices[item] = price;
Console.WriteLine($"장바구니에 {item} 추가됨 - {price:C}");
}
public decimal CalculateTotal()
{
decimal sum = 0;
foreach (var item in items)
{
sum += prices[item];
}
return sum;
}
public void Checkout(IPaymentStrategy paymentMethod)
{
decimal amount = CalculateTotal();
Console.WriteLine($"\n총 결제 금액: {amount:C}");
Console.WriteLine($"결제 방법: {paymentMethod.GetName()}");
bool success = paymentMethod.Pay(amount);
if (success)
{
Console.WriteLine("결제 성공! 주문이 처리되었습니다.");
items.Clear();
}
else
{
Console.WriteLine("결제 실패! 다시 시도해주세요.");
}
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 장바구니 생성
ShoppingCart cart = new ShoppingCart();
// 상품 추가
cart.AddItem("노트북", 1200000);
cart.AddItem("마우스", 35000);
cart.AddItem("키보드", 89000);
// 신용카드로 결제
IPaymentStrategy creditCard = new CreditCardPayment("1234-5678-9012-3456", "123", "12/25", "홍길동");
cart.Checkout(creditCard);
// 새 장바구니 생성
cart = new ShoppingCart();
cart.AddItem("책", 25000);
cart.AddItem("커피머신", 150000);
// 페이팔로 결제
IPaymentStrategy paypal = new PaypalPayment("hong@example.com", "password");
cart.Checkout(paypal);
}
}
설명: 전략 패턴은 알고리즘 군을 정의하고 각각 캡슐화하여 교체 가능하게 만듭니다. 위 예시에서는 다양한 결제 방법을 전략으로 구현하여 쉽게 교체할 수 있습니다.
실생활 시나리오: 주식 시장 모니터링 앱
// 구체적인 주제 - 주식 시장
public class StockMarket : IStockMarket
{
private List<IInvestor> investors = new List<IInvestor>();
private Dictionary<string, decimal> stocks = new Dictionary<string, decimal>();
public void RegisterObserver(IInvestor investor)
{
investors.Add(investor);
}
public void RemoveObserver(IInvestor investor)
{
investors.Remove(investor);
}
public void NotifyObservers()
{
foreach (var stock in stocks)
{
foreach (var investor in investors)
{
investor.Update(stock.Key, stock.Value);
}
}
}
public void SetStockPrice(string symbol, decimal price)
{
Console.WriteLine($"\n주식 시장: {symbol} 가격 변동 -> {price:C}");
// 기존 가격이 있는지 확인
if (stocks.ContainsKey(symbol))
{
decimal oldPrice = stocks[symbol];
stocks[symbol] = price;
// 가격이 변경된 경우에만 알림
if (oldPrice != price)
{
NotifyObservers();
}
}
else
{
// 새 주식 추가
stocks[symbol] = price;
NotifyObservers();
}
}
}
// 구체적인 옵저버 - 투자자
public class Investor : IInvestor
{
private string name;
private Dictionary<string, decimal> portfolio = new Dictionary<string, decimal>();
public Investor(string name)
{
this.name = name;
}
public void AddStock(string symbol, decimal buyPrice)
{
portfolio[symbol] = buyPrice;
Console.WriteLine($"{name}이(가) {symbol} 주식을 {buyPrice:C}에 구매했습니다.");
}
public void Update(string stockSymbol, decimal currentPrice)
{
// 보유 중인 주식인 경우에만 반응
if (portfolio.ContainsKey(stockSymbol))
{
decimal buyPrice = portfolio[stockSymbol];
decimal profitLoss = currentPrice - buyPrice;
Console.WriteLine($"{name} 알림: {stockSymbol} 현재가 {currentPrice:C}, " +
$"매수가 {buyPrice:C}, 손익 {profitLoss:C} " +
$"({profitLoss / buyPrice:P2})");
// 투자자의 전략적 결정
if (profitLoss > 0 && profitLoss / buyPrice > 0.1m)
{
Console.WriteLine($"{name}의 결정: {stockSymbol} 주식이 10% 이상 상승했습니다. 일부 매도를 고려하세요!");
}
else if (profitLoss < 0 && Math.Abs(profitLoss / buyPrice) > 0.05m)
{
Console.WriteLine($"{name}의 결정: {stockSymbol} 주식이 5% 이상 하락했습니다. 추가 매수를 고려하세요!");
}
}
}
}
// 사용 예시
public class Program
{
public static void Main()
{
// 주식 시장 생성
StockMarket market = new StockMarket();
// 투자자 생성
Investor investor1 = new Investor("홍길동");
Investor investor2 = new Investor("김철수");
Investor investor3 = new Investor("이영희");
// 투자자들을 옵저버로 등록
market.RegisterObserver(investor1);
market.RegisterObserver(investor2);
market.RegisterObserver(investor3);
// 투자자들의 포트폴리오 설정
investor1.AddStock("SAMSUNG", 70000);
investor1.AddStock("NAVER", 350000);
investor2.AddStock("SAMSUNG", 65000);
investor2.AddStock("KAKAO", 120000);
investor3.AddStock("NAVER", 340000);
investor3.AddStock("KAKAO", 125000);
Console.WriteLine("\n=== 주식 시장 모니터링 시작 ===");
// 주식 가격 변동 시뮬레이션
market.SetStockPrice("SAMSUNG", 75000);
market.SetStockPrice("NAVER", 330000);
market.SetStockPrice("KAKAO", 135000);
// 이영희 투자자 제거
market.RemoveObserver(investor3);
Console.WriteLine("\n이영희님이 알림 서비스를 해지했습니다.");
// 다시 가격 변동
market.SetStockPrice("SAMSUNG", 78000);
market.SetStockPrice("KAKAO", 115000);
}
}
설명: 옵저버 패턴은 객체 간의 일대다 종속성을 정의하여 한 객체의 상태가 변경되면 모든 종속 객체에게 자동으로 알림이 갑니다. 위 예시에서는 주식 시장(Subject)이 가격 변동 시 모든 투자자(Observer)에게 알림을 보내는 방식으로 구현했습니다.
실생활 시나리오: 스마트홈 리모컨 시스템
// 커맨드 인터페이스
public interface ICommand
{
void Execute();
void Undo();
}
// 수신자(Receiver) 클래스들
public class Light
{
private string location;
public Light(string location)
{
this.location = location;
}
public void TurnOn()
{
Console.WriteLine($"{location} 조명이 켜졌습니다.");
}
public void TurnOff()
{
Console.WriteLine($"{location} 조명이 꺼졌습니다.");
}
}
public class Television
{
private int volume = 10;
private int channel = 1;
public void TurnOn()
{
Console.WriteLine("TV가 켜졌습니다.");
Console.WriteLine($"볼륨: {volume}, 채널: {channel}");
}
public void TurnOff()
{
Console.WriteLine("TV가 꺼졌습니다.");
}
public void SetVolume(int volume)
{
this.volume = volume;
Console.WriteLine($"TV 볼륨이 {volume}으로 설정되었습니다.");
}
public void SetChannel(int channel)
{
this.channel = channel;
Console.WriteLine($"TV 채널이 {channel}번으로 변경되었습니다.");
}
}
public class Thermostat
{
private int temperature = 22;
public void SetTemperature(int temperature)
{
this.temperature = temperature;
Console.WriteLine($"온도가 {temperature}°C로 설정되었습니다.");
}
public int GetTemperature()
{
return temperature;
}
}
// 구체적인 커맨드 클래스들
public class LightOnCommand : ICommand
{
private Light light;
public LightOnCommand(Light light)
{
this.light = light;
}
public void Execute()
{
light.TurnOn();
}
public void Undo()
{
light.TurnOff();
}
}
public class LightOffCommand : ICommand
{
private Light light;
public LightOffCommand(Light light)
{
this.light = light;
}
public void Execute()
{
light.TurnOff();
}
public void Undo()
{
light.TurnOn();
}
}
public class TVOnCommand : ICommand
{
private Television tv;
public TVOnCommand(Television tv)
{
this.tv = tv;
}
public void Execute()
{
tv.TurnOn();
}
public void Undo()
{
tv.TurnOff();
}
}
public class TVOffCommand : ICommand
{
private Television tv;
public TVOffCommand(Television tv)
{
this.tv = tv;
}
public void Execute()
{
tv.TurnOff();
}
public void Undo()
{
tv.TurnOn();
}
}
public class ThermostatSetCommand : ICommand
{
private Thermostat thermostat;
private int temperature;
private int previousTemperature;
public ThermostatSetCommand(Thermostat thermostat, int temperature)
{
this.thermostat = thermostat;
this.temperature = temperature;
}
public void Execute()
{
previousTemperature = thermostat.GetTemperature();
thermostat.SetTemperature(temperature);
}
public void Undo()
{
thermostat.SetTemperature(previousTemperature);
}
}
// 호출자(Invoker) 클래스
public class RemoteControl
{
private ICommand[] onCommands;
private ICommand[] offCommands;
private Stack<ICommand> commandHistory = new Stack<ICommand>();
public RemoteControl(int slots)
{
onCommands = new ICommand[slots];
offCommands = new ICommand[slots];
// 비어있는 커맨드로 초기화
ICommand noCommand = new NoCommand();
for (int i = 0; i < slots; i++)
{
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
{
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void PressOnButton(int slot)
{
onCommands[slot].Execute();
commandHistory.Push(onCommands[slot]);
}
public void PressOffButton(int slot)
{
offCommands[slot].Execute();
commandHistory.Push(offCommands[slot]);
}
public void PressUndoButton()
{
if (commandHistory.Count > 0)
{
ICommand command = commandHistory.Pop();
command.Undo();
}
else
{
Console.WriteLine("실행 취소할 명령이 없습니다.");
}
}
}
// 빈 커맨드 (Null Object 패턴)
public class NoCommand : ICommand
{
public void Execute() { }
public void Undo() { }
}
// 사용 예시
public class Program
{
public static void Main()
{
// 리모컨 초기화 (3개 슬롯)
RemoteControl remote = new RemoteControl(3);
// 디바이스 생성
Light livingRoomLight = new Light("거실");
Television tv = new Television();
Thermostat thermostat = new Thermostat();
// 커맨드 생성
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
TVOnCommand tvOn = new TVOnCommand(tv);
TVOffCommand tvOff = new TVOffCommand(tv);
ThermostatSetCommand thermostatHigh = new ThermostatSetCommand(thermostat, 25);
ThermostatSetCommand thermostatLow = new ThermostatSetCommand(thermostat, 18);
// 리모컨에 커맨드 설정
remote.SetCommand(0, livingRoomLightOn, livingRoomLightOff);
remote.SetCommand(1, tvOn, tvOff);
remote.SetCommand(2, thermostatHigh, thermostatLow);
// 리모컨 사용
Console.WriteLine("=== 스마트홈 리모컨 테스트 ===\n");
Console.WriteLine("거실 조명 켜기 버튼 누름:");
remote.PressOnButton(0);
Console.WriteLine("\nTV 켜기 버튼 누름:");
remote.PressOnButton(1);
Console.WriteLine("\n온도 높이기 버튼 누름:");
remote.PressOnButton(2);
Console.WriteLine("\n실행 취소 버튼 누름:");
remote.PressUndoButton();
Console.WriteLine("\nTV 끄기 버튼 누름:");
remote.PressOffButton(1);
Console.WriteLine("\n거실 조명 끄기 버튼 누름:");
remote.PressOffButton(0);
}
}
설명: 커맨드 패턴은 요청을 객체로 캡슐화하여 다양한 요청을 매개변수화하고, 요청을 큐에 저장하거나 로그에 기록하고, 취소 가능한 작업을 지원합니다. 위 예시에서는 스마트홈 리모컨이 다양한 가전제품을 제어하는 명령을 실행하고 되돌릴 수 있게 구현했습니다.
이미지에 보이는 9가지 디자인 패턴은 객체지향 프로그래밍에서 자주 사용되는 중요한 패턴들입니다:
이러한 디자인 패턴들은 코드의 재사용성, 유지보수성, 확장성을 높이는 데 큰 도움이 됩니다. 모든 패턴을 다 외우려고 하기보다는 문제 상황에 맞는 패턴을 찾아 적용하는 연습을 하는 것이 효과적입니다.
https://x.com/alexxubyte/status/1915433600096624731/photo/1
X의 Alex Xu님(@alexxubyte)
9 OOP Design Patterns You Must Know
x.com
IoC와 DI 개념 설명 - 개발자를 위한 가이드 (1) | 2025.04.24 |
---|---|
C# AutoMapper 사용법: 객체 간 매핑 자동화의 신세계 (0) | 2025.03.18 |
Visual Studio AI 템플릿으로 5분 만에 AI 챗봇 만들기 (0) | 2025.03.12 |
c# string.Join 과 Aggregate 의 차이점 (0) | 2024.12.26 |
C# 개발자를 위한 생산성과 코드 품질 향상 10가지 실천법 (0) | 2024.11.25 |