객체가 처한 현재 상황에 따라 스스로 취할 행동을 변경하는 패턴으로 상태 패턴이라 합니다.
이 또한 행동 패턴(Behavioral Design Patterns)중 하나입니다.
장점
- 단일 책임 원칙을 만족하며 특정 상태에 사용되는 코드를 별도의 클래스로 분리할 수 있습니다.
- 개방/폐쇄 원칙을 만족하며 기존 코드를 변경하지 않고 새 클래스를 추가할 수 있습니다.
- 조건문을 줄여 코드가 단순해지고 가독성을 높일 수 있습니다.
단점
- 처리하는 상태가 별로 없거나 상태의 변화가 적은 경우에 적용하는 것은 과유불급입니다.
이번 패턴도 역시 C#으로 Console 프로젝트를 하나 생성해서 할 것입니다.
이해를 위한 것이니 간단하게 만들겠습니다.
목표는 영수증 출력 기계(Receipt Printer)에 결제를 하고 영수증을 출력하는 처리입니다.
따라서 상태는 결제를 한 상태(Payment State)와 결제 하기 전 상태(Non Payment State)가 있습니다.
프로젝트의 tree구조는 아래와 같습니다.
State (Project)
|- Program.cs (Main)
|- ReceiptPrinter.cs
|- State.cs
|- PaymentState.cs
|- NonPaymentState.cs
그럼 구현해 보겠습니다.
ReceiptPrinter.cs
namespace State
{
public class ReceiptPrinter
{
private State state;
public ReceiptPrinter(State state)
{
this.SetState(state);
}
public void SetState(State state)
{
this.state = state;
this.state.SetReceiptPrinter(this);
}
public void PrintReceipt()
{
this.state.PrintReceipt();
}
public void PayFor()
{
this.state.PayFor();
}
}
}
State를 가지고 State변경과 State의 메소드를 호출하는 메소드를 구현해 줍니다.
State.cs
namespace State
{
public abstract class State
{
protected ReceiptPrinter receiptPrinter;
public void SetReceiptPrinter(ReceiptPrinter receiptPrinter)
{
this.receiptPrinter = receiptPrinter;
}
public abstract void PrintReceipt();
public abstract void PayFor();
}
}
abstract로 구현하여 자식 클래스들에게 필요한 행동을 구현하도록 유도합니다.
필요에 따라 리턴값을 변경하여 사용하면 됩니다.
PaymentState.cs
using System;
namespace State
{
public class PaymentState : State
{
public override void PayFor()
{
Console.WriteLine("You have already paid");
}
public override void PrintReceipt()
{
Console.WriteLine("Receipt Printing...");
this.receiptPrinter.SetState(new NonPaymentState());
}
}
}
Console.WriteLine을 통해 상황을 출력하도록 구현했습니다.
결제를 시도하면 이미 결제했다고 출력합니다.
State라는 부모 클래스에서 receiptPrinter를 선언해 놨기에 사용할 수 있으며 PrintReceipt를 한 뒤에 결제 되지 않은 상태로 변경해줍니다.
NonPaymentState.cs
using System;
namespace State
{
public class NonPaymentState : State
{
public override void PayFor()
{
Console.WriteLine("Paying...");
this.receiptPrinter.SetState(new PaymentState());
}
public override void PrintReceipt()
{
Console.WriteLine("You haven't paid yet");
}
}
}
결제를 하지 않은 상태의 클래스이기에 결제(PayFor)를 한 뒤 결제 상태(PaymentState)로 변경해 줍니다.
PrintReceipt를 시도하면 결제 하지 않았다고 출력합니다.
Program.cs
using System;
namespace State
{
class Program
{
static void Main(string[] args)
{
ReceiptPrinter receiptPrinter = new ReceiptPrinter(new NonPaymentState());
Console.Write("try payment: ");
receiptPrinter.PayFor();
Console.Write("end\nreceipt request: ");
receiptPrinter.PrintReceipt();
Console.WriteLine("end\n");
Console.Write("receipt request before payment: ");
receiptPrinter.PrintReceipt();
receiptPrinter.PayFor();
Console.Write("try double payment: ");
receiptPrinter.PayFor();
Console.Write("receipt request: ");
receiptPrinter.PrintReceipt();
}
}
}
Console.Write가 많아 복잡해 보이지만 실행 결과와 같이 보면 이해하기 수월합니다.
ReceiptPrint를 생성하고 결제를 시도했습니다. "Paying..."이라고 출력됐으며 Main에서 end를 출력시켜 결제를 확인했습니다.
그 후 영수증을 요청하고 원하는 동작을 했음을 확인할 수 있습니다.
이후에 이전에 영수증을 요청하여 결제가 안된 상태로 변경됐음에도 영수증 출력을 시도해서 아직 결제가 안됐다고 출력됨을 확인 할 수 있고 두 번 결제를 시도 했을 땐 이미 결제 했다고 뜨는 것을 확인 하실 수 있습니다.
이후에 영수증을 요청해도 정상적으로 출력됨을 확인 하실 수 있습니다.
이런 경우에 return값을 bool로 해서 메인에서 처리해도 되고 해당 State내부에서 다른 처리를 하는 것이 편할 것 같습니다.
여기서는 결제에 관련된 두 개의 상태만 봤으나 프로젝트를 하다보면 좀 더 다양한 상태를 처리해야 할 때를 만날 수 있을 것입니다.
그런 때에 유용하게 사용하시면 됩니다.
'Design Pattern' 카테고리의 다른 글
[Design Pattern] 6. Mediator Pattern(중재자 패턴) C# (0) | 2023.03.26 |
---|---|
[Design Pattern] 5. Observer Pattern(옵저버 패턴) C# (0) | 2023.03.25 |
[Design Pattern] 3. Chain of Responsibility Pattern(책임 연쇄 패턴) C# (0) | 2023.03.23 |
[Design Pattern] 2. Strategy Pattern(전략 패턴) C# (0) | 2023.03.22 |
[Design Pattern] 1. 디자인 패턴이란 무엇인가 (0) | 2023.03.21 |