Context: 어떤 상황, 환경 또는 상태
→ 특정한 시간 또는 장소에서 일어나는 일 또는 일련의 사건에 대한 맥락을 제공하며, 해당 상황에서 필요한 정보와 상태를 캡슐화하고 저장한다.
Contextual Information: 주어진 컨텍스트 내에서 중요한 정보나 데이터
→ 해당 상황을 더 명확하게 이해하고 처리하는 데 도움이 될 수 있다.
예를 들어, 병원에 가면 이름과 주민등록번호 앞자리가 contextual information이 되고 병원에 방문하는 상황 / 상태가 context가 된다.
Context 2가지 의미
- 어떤 종류의 상태나 환경을 캡슐화한 것
→ 컨텍스트는 특정한 시간이나 상황에서의 정보와 상태를 저장하고 나중에 그 정보와 상태를 재현하기 위해 사용된다.
- 예를 들어, 워드 프로세서에서 긴 문서를 작성하고 있는데 갑자기 컴퓨터의 전원이 꺼져버린다.
이때, 워드 프로세서는 현재 문서의 상태를 컨텍스트로 저장한다.
→ 해당 컨텍스트에는 문서의 내용, 커서 위치, 서식, 스크롤 위치 등이 포함된다.
다음에 컴퓨터를 다시 켰을 때, 워드 프로세서는 이 컨텍스트 정보를 사용하여 마지막 저장된 지점에서 문서 작업을 계속할 수 있게 해준다.- 작업을 중단하고 나중에 같은 지점에서 계속 진행하기 위해 저장하는 최소 데이터 집합
→ 프로세스 간 컨텍스트 스위치는 CPU가 다른 프로세스로 전환될 때 현재 프로세스의 상태를 저장하고, 나중에 해당 상태에서 작업을 계속할 수 있도록 한다.
Context API: React 라이브러리의 일부로, Context API를 사용하면 데이터를 컴포넌트 계층 구조를 통해 전달하지 않고도 컴포넌트 간에 데이터를 공유할 수 있다.
전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트 안에서 필요에 따라 교체할 수 있게 만드는 디자인 패턴
- Context: 컨텍스트는 전략 패턴을 사용하는 주체
- Strategy: 전략은 컨텍스트에서 사용하는 다양한 알고리즘을 캡슐화한 인터페이스나 추상 클래스
- Concrete Strategy: 컨크리트 전략은 전략 인터페이스를 구현하는 구체적인 클래스
전략 패턴은 컨텍스트와 전략 간의 느슨한 결합을 제공하며, 컨텍스트가 실행 시에 적절한 전략을 선택하고 교체할 수 있게 한다.
또한, 컨텍스트는 동적으로 다양한 알고리즘을 사용할 수 있으며 코드 재사용과 유지보수성을 개선할 수 있다.
예시
- PaymentStrategy 인터페이스: PaymentStrategy 인터페이스는 다양한 결제 전략을 정의
- KAKAO Card 및 LUNA Card 구체적인 전략: KAKAOCardStrategy 및 LUNACardStrategy는 PaymentStrategy 인터페이스를 구현하고, 각각의 결제 방법에 필요한 정보와 동작을 정의
- ShoppingCart 클래스: PaymentStrategy를 사용하여 다양한 결제 방법을 수용할 수 있으며, pay 메서드가 전략을 사용하여 실제 결제를 수행
- 메인 메서드: main 메서드에서 실제로 ShoppingCart를 사용하고, 다양한 전략(LUNACardStrategy 및 KAKAOCardStrategy)를 선택하여 결제를 진행
→ 전략 패턴을 통해 다른 결제 방법을 유연하게 사용할 수 있다.
// PaymentStrategy 인터페이스는 구체적인 전략들이 구현해야 하는 pay 메서드를 정의합니다.
interface PaymentStrategy {
public void pay(int amount);
}
// KAKAO Card에 대한 구체적인 PaymentStrategy
class KAKAOCardStrategy implements PaymentStrategy {
// ... (생성자 및 필드들)
@Override
public void pay(int amount) {
System.out.println(amount + " paid using KAKAOCard.");
}
}
// LUNA Card에 대한 구체적인 PaymentStrategy
class LUNACardStrategy implements PaymentStrategy {
// ... (생성자 및 필드들)
@Override
public void pay(int amount) {
System.out.println(amount + " paid using LUNACard.");
}
}
class Item {
private String name;
private int price;
public Item(String name, int cost) {
this.name = name;
this.price = cost;
}
public int getPrice() {
return price;
}
}
class ShoppingCart {
List<Item> items;
public ShoppingCart() {
this.items = new ArrayList<Item>();
}
public void addItem(Item item) {
this.items.add(item);
}
public int calculateTotal() {
int sum = 0;
for (Item item : items) {
sum += item.getPrice();
}
return sum;
}
public void pay(PaymentStrategy paymentMethod) {
int amount = calculateTotal();
paymentMethod.pay(amount);
}
}
public class HelloWorld {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item A = new Item("kundolA", 100);
Item B = new Item("kundolB", 300);
cart.addItem(A);
cart.addItem(B);
// LUNACard로 결제
cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
// KAKAO Card로 결제
cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
}
}
- 공통점
- 모듈화와 유연성: 시스템을 모듈화하여 각 모듈이 서로 독립적으로 변경 가능하게 하고 유연성을 높이는 데 중점을 둔다.
- 차이점
- 전략패턴: 어떠한 동일한 행동 계약을 기반으로 다양한 구현이 명시되어있는 인터페이스를 만드는 것을 포함
- 의존성주입: 일부 동작만을 구현하고 의존성을 주입하기만 하는 패턴
Observer Pattern은 주체(Subject)와 옵저버(Observer)들 간의 관계를 나타내는 디자인 패턴
→ 트위터의 메인 로직, 그리고 MVC패턴에 적용
- 주체 객체가 상태 변화를 관찰하다가 상태가 변할 때마다 옵저버 목록에 등록된 옵저버들에게 변화를 알려주는 메커니즘
- 주체(Subject): 상태를 관찰하는 대상 객체
- 옵저버(Observer): 상태가 변경되면 주체로부터 통지를 받아 적절한 동작을 수행
예시: 옵저버 패턴을 구현하여 주체(Topic)와 옵저버(TopicSubscriber) 간의 상호작용 보여준다.
// Subject 인터페이스는 옵저버들을 등록, 해지하고, 옵저버들에게 알릴 메서드를 정의
interface Subject {
public void register(Observer obj);
public void unregister(Observer obj);
public void notifyObservers();
public Object getUpdate(Observer obj);
}
// Observer 인터페이스는 주체로부터 업데이트를 받기 위한 update 메서드를 정의
interface Observer {
public void update();
}
// Topic 클래스는 Subject를 구현하여 주제의 상태를 추적하고 옵저버에게 업데이트를 알림
class Topic implements Subject {
private List<Observer> observers;
private String message;
public Topic() {
this.observers = new ArrayList<>();
this.message = "";
}
@Override
public void register(Observer obj) {
if (!observers.contains(obj)) observers.add(obj);
}
@Override
public void unregister(Observer obj) {
observers.remove(obj);
}
@Override
public void notifyObservers() {
this.observers.forEach(Observer::update);
}
@Override
public Object getUpdate(Observer obj) {
return this.message;
}
public void postMessage(String msg) {
System.out.println("Message sent to Topic: " + msg);
this.message = msg;
notifyObservers();
}
}
// TopicSubscriber 클래스는 Observer를 구현하여 주제의 업데이트를 수신
class TopicSubscriber implements Observer {
private String name;
private Subject topic;
public TopicSubscriber(String name, Subject topic) {
this.name = name;
this.topic = topic;
}
@Override
public void update() {
String msg = (String) topic.getUpdate(this);
System.out.println(name + " received a message: " + msg);
}
}
public class HelloWorld {
public static void main(String[] args) {
Topic topic = new Topic();
Observer a = new TopicSubscriber("a", topic);
Observer b = new TopicSubscriber("b", topic);
Observer c = new TopicSubscriber("c", topic);
topic.register(a);
topic.register(b);
topic.register(c);
topic.postMessage("Amumu is an OP champion!");
}
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/
객체가 어떤 대상 객체에 접근하기 전, 그 접근에 대한 흐름을 가로채서 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층이 있는 디자인패턴
다른 객체에 대한 인터페이스를 제공하여 해당 객체에 대한 접근을 제어하거나 중개하는 역할을 하는 객체를 생성하는 데 사용된다.
→ 다양한 상황에서 객체의 동작을 제어하거나 보안, 로깅, 캐싱, 지연 로딩 등을 구현할 때 유용하게 활용 될 수 있다.
예시: 프록시 패턴을 활용하여 이미지 파일이 실제로 필요할 때만 로딩하도록 한다.
// 서브젝트 인터페이스
interface Image {
void display();
}
// 실제 서브젝트 클래스
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
// 프록시 클래스
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
public class ProxyPatternExample {
public static void main(String[] args) {
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
// 이미지 파일은 실제로 로딩되지 않음
image1.display();
// 이미지 파일은 이미 로딩되었으므로 다시 로딩되지 않음
image1.display();
}
}
데이터 흐름을 단방향으로 관리하는 디자인 패턴
예시
- 페이스북은 메시지를 읽었는데 다른 페이지에서는 메시지가 안 읽었다고 뜨는 읽은 표시(mark seen)에 대한 기능장애를 경험했다.
→ 뷰가 모델에 영향을, 반대 상황에서도 영향을 미치는 경우가 발생하여 데이터를 일관성 있게 뷰에 공유하기가 어려웠다.
또한, 뷰와 모델 사이의 양방향 데이터 흐름으로 인한 복잡성으로 인해 버그 파악 및 수정이 어려웠다.
위와 같은 상황에 따라 페이스북에서 flux 패턴을 개발하였다.
- Action: 사용자의 이벤트 담당하며, 이벤트에 관한 객체를 만들어내 dispatcher에게 전달
→ 마우스 클릭, 글 작성- Dispatcher: 들어오는 Action 객체 정보를 기반 어떠한 행위를 할 것인가를 결정하며, action객체의 type를 기반으로 로직을 수행하고 이를 Store에 전달
→ 코드 레벨 수준에서는 swtich로 구현- Store: 애플리케이션 상태를 관리하고 저장하며 도메인의 상태, 사용자의 인터페이스 등의 상태 등을 모두 저장
- View: 데이터를 기반으로 표출이 되는 사용자 인터페이스
장점
- 데이터 일관성의 증대: 데이터 흐름이 단방향으로만 흐르기 때문에 데이터 일관성을 높아진다.
- 버그를 찾기가 쉬워짐: 데이터 흐름이 단순하고 예측 가능하게 되므로 버그를 찾고 수정하기가 쉬워진다.
- 단위테스팅이 쉬워짐: 각 구성 요소(디스패처, 스토어, 뷰)를 독립적으로 테스팅 할 수 있다.
Flux 패턴을 사용하여 개발된 상태 관리 라이브러리 Redux