반복자 패턴(Iterator Pattern)
컬렉션의 구현 방법을 노출하지 않으면서 객체 내의 모든 항목에 접근하는 방법을 제공합니다.
컴포지트 패턴(Composite Pattern)
객체를 트리구조로 구성해서 계층구조를 구현합니다. 이 패턴을 사용하면 단일 객체와 트리로 구성된 복합 객체를 똑같은 방법으로 다룰 수 있습니다.
❗ 속보! 객체마을 식당과 팬케이크 하우스 합병
오랜 줄다리기 끝에 두 식당이 합병하기로 결정했습니다! ⚖️🤝
그런데 합병하고 보니, 두 식당은 각자 메뉴를 다루는 방법이 달라서 종업원이 주문을 어떻게 처리해야 고민을 하고 있네요. 🤔
🎯 문제 포인트
- 객체마을 식당에서는 메뉴들을 배열로 다룬다.
- 팬케이크 하우스에선 메뉴들을 ArrayList로 다룬다.
- 두 식당의 메뉴 구조를 똑같이 맞추려면 한 쪽에서 매우 큰 비용이 발생하기 때문에 서로 하기를 꺼려하는 상황이다.
문제를 해결하기 위해, 우선 객체마을 식당의 메뉴 구조를 살펴볼까요?
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("채식주의자용 BLT", "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴", true, 2.99);
addItem("BLT", "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴", false, 2.99);
addItem("오늘의 스프", "감자 샐러드를 곁들인 오늘의 스프", false, 3.29);
addItem("핫도그", "사워크라우트, 갖은 양념 양파, 치즈가 곁들여진 핫도그", false, 3.05);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.err.println("죄송합니다, 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다.");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems = numberOfItems + 1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
}
다음으로 팬케이크 하우스의 메뉴 구조입니다.
import java.util.ArrayList;
import java.util.List;
public class PancakeHouseMenu {
List<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
addItem("K&B 팬케이크 세트", "스크램블 에그와 토스트가 곁들여진 팬케이크", true, 2.99);
addItem("레귤러 팬케이크 세트", "달걀 프라이와 소시지가 곁들여진 팬케이크", false, 2.99);
addItem("블루베리 팬케이크", "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크", true, 3.49);
addItem("와플", "취향에 따라 블루베리나 딸기를 얹을 수 있는 와플", true, 3.59);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}
public ArrayList<MenuItem> getMenuItems() {
return (ArrayList<MenuItem>) menuItems;
}
}
위와 같은 상황에서 종업원이 두 식당의 모든 메뉴를 출력하려면 어떻게 해야 할까요?
상황이 이렇게 됐으니 배열과 ArrayList 구조에 맞춰서 각 for문을 통해 메뉴를 출력해볼 수 있겠네요.
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList<MenuItem> breakfaskItems = pancakeHouseMenu.getMenuItems();
DinerMenu dinerMenu = new DinerMenu();
MennuItem[] lunchItems = dinerMenu.getMenuItems();
for(int i = 0; i < breakfastItems.size(); i++) {
Menuitem menuItem = breakfastItem.get(i);
...
}
for(int i = 0; i < lunchItems.length; i++) {
MenuItem menuItem = lunchItems[i];
...
}
💥 그런데 위와 같은 코드는 아래와 같은 문제점이 있다는 걸 깨닫게 됩니다.
- 종업원은 PancakeHouseMenu와 DinerMenu와 같은 구상 클래스에 맞춰서 코딩하고 있습니다.
- 종업원은 각 메뉴의 구조를 알아야 하므로 캡슐화의 기본 원칙이 지켜지지 않고 있습니다.
- 각 메뉴 항목에 접근하려면 서로 다른 반복문이 필요하고, 메뉴가 추가되면 새로운 반복문을 다시 추가해야 합니다.
- 나중에 각 메뉴가 Hashtable과 같은 다른 구조로 변경하려면 코드를 많이 수정해야 합니다.
그럼 이제 어떻게 해야 할까요?
이전에 배운 것 중 가장 중요한 내용인 "바뀌는 부분을 캡슐화하라"를 활용해야 합니다.
지금 문제에서 바뀌는 부분은 반복 처리 방법이기 때문에 이 부분을 인터페이스로 캡슐화하면 좋겠네요.
우선 공통 Iterator와 각 식당의 Iterator를 만듭니다.
public interface Iterator {
boolean hasNext();
MenuItem next();
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
public MenuItem next() {
MenuItem menuItem = items[position];
position++;
return menuItem;
}
public boolean hasNext() {
return position < items.length && items[position] != null;
}
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
import java.util.List;
public class PancakeHouseIterator implements Iterator {
List<MenuItem> menuItems;
int index = 0;
public PancakeHouseIterator(List<MenuItem> menuItems) {
this.menuItems = menuItems;
}
public MenuItem next() {
MenuItem menuItem = menuItems.get(index);
index++;
return menuItem;
}
public boolean hasNext() {
return index < menuItems.size() && menuItems.get(index) != null;
}
}
다음으로 각 식당 메뉴 클래스에 createIterator 메서드를 추가합니다.
public class DinerMenu {
...
// 직접 메뉴 아이템을 반환하지 않고 Iterator를 활용합니다.
// public MenuItem[] getMenuItems() {
// return menuItems;
// }
public Iterator createIterator() {
return new DinerMenuIterator(menuItems);
}
}
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
import java.util.ArrayList;
import java.util.List;
public class PancakeHouseMenu {
...
// 직접 메뉴 아이템을 반환하지 않고 Iterator를 활용합니다.
// public ArrayList<MenuItem> getMenuItems() {
// return (ArrayList<MenuItem>) menuItems;
// }
public Iterator createIterator() {
return new PancakeHouseIterator(menuItems);
}
}
이제 종업원 코드를 만들어 볼게요.
public class Waitress {
PancakeHouseMenu pancakeHouseMenu;
DinerMenu dinerMenu;
public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator pancakeHouseIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
System.out.println("메뉴\n----\n아침 메뉴");
printMenu(pancakeHouseIterator);
System.out.println("\n점심 메뉴");
printMenu(dinerIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem) iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.print(menuItem.getDescription());
}
}
}
마지막으로 잘 동작하는지 확인해볼까요?
public class Application {
public static void main(String[] args) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
메뉴
----
아침 메뉴
K&B 팬케이크 세트, 2.99 -- 스크램블 에그와 토스트가 곁들여진 팬케이크레귤러 팬케이크 세트, 2.99 -- 달걀 프라이와 소시지가 곁들여진 팬케이크블루베리 팬케이크, 3.49 -- 신선한 블루베리와 블루베리 시럽으로 만든 팬케이크와플, 3.59 -- 취향에 따라 블루베리나 딸기를 얹을 수 있는 와플
점심 메뉴
채식주의자용 BLT, 2.99 -- 통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴BLT, 2.99 -- 통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴오늘의 스프, 3.29 -- 감자 샐러드를 곁들인 오늘의 스프핫도그, 3.05 -- 사워크라우트, 갖은 양념 양파, 치즈가 곁들여진 핫도그
🎉 이제 두 식당은 각자의 메뉴 구조를 변경하지 않아도 되고, 종업원이 각 식당의 메뉴 구조를 몰라도 되겠네요!
위에서 Iterator를 직접 구현해봤으니, 이번엔 Java에서 제공하는 java.util.Iterator를 사용해볼까요?
우선 기존 Iterator 인터페이스를 삭제하고, 각 클래스에 java.util.Iterator를 import 해주고, 그에 맞춰 수정합니다.
import java.util.Iterator;
public class DinerMenu {
...
public Iterator<MenuItem> createIterator() {
return new DinerMenuIterator(menuItems);
}
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class PancakeHouseMenu implements Menu {
...
public Iterator<MenuItem> createIterator() {
return menuItems.iterator();
}
}
다음으로 각 식당 Iterator의 implements를 수정합니다.
import java.util.Iterator;
public class DinerMenuIterator implements Iterator<MenuItem> {
...
public void remove() {
throw new UnsupportedOperationException("메뉴 항목은 지우면 안 됩니다.");
}
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
import java.util.List;
import java.util.Iterator;
public class PancakeHouseIterator implements Iterator<MenuItem> {
...
}
수정하는 김에 각 식당의 메뉴도 인터페이스로 통일시켜버립시다.
import java.util.Iterator;
public interface Menu {
public Iterator<MenuItem> createIterator();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// implements Menu가 추가됐네요!
public class DinerMenu implements Menu {
...
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public class PancakeHouseMenu implements Menu {
...
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
import java.util.Iterator;
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator<MenuItem> pancakeHouseIterator = pancakeHouseMenu.createIterator();
Iterator<MenuItem> dinerIterator = dinerMenu.createIterator();
System.out.println("메뉴\n----\n아침 메뉴");
printMenu(pancakeHouseIterator);
System.out.println("\n점심 메뉴");
printMenu(dinerIterator);
}
private void printMenu(Iterator<MenuItem> iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.print(menuItem.getDescription());
}
}
}
이제 java.util.Iterator로 변경한 구조로 다시 테스트해볼까요?
public class Application {
public static void main(String[] args) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
반복자 패턴(Iterator Pattern)
컬렉션의 구현 방법을 노출하지 않으면서 객체 내의 모든 항목에 접근하는 방법을 제공합니다.
🤔 집합체 클래스에서 내부 컬렉션 관련 기능과 반복자용 메서드 관련 기능을 전부 구현하면 어떻게 될까요? 그러면 클래스의 메서드가 늘어나겠죠. 근데 그게 그렇게 나쁠까요?
왜 그게 나쁜지 이해하려면 2가지 이유로 클래스가 바뀔 수 있다는 사실을 알아야 합니다.
- 첫째, 컬렉션이 바뀌게 되면 클래스를 바꿔야 합니다.
- 둘째, 반복자 관련 기능이 바뀌었을 때에도 클래스를 바꿔야 합니다.
반복자 패턴에서 배울 수 있는 객체지향 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 상호작용하는 개체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.
- 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다(OCP).
- 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.
- 먼저 연락하지 마세요. 저희가 연락드리겠습니다(할리우드 원칙).
- 어떤 클래스가 바뀌는 이유는 하나뿐이어야 한다.
클래스를 고치는 일은 최대한 피해야 사이드 이펙트를 방지할 수 있겠죠. 클래스를 변경해야 하는 이유가 2가지, 또는 그 이상이 되면 그 클래스를 고쳐야 할 가능성이 커지기 때문에, 하나의 클래스는 하나의 역할만 맡아야 합니다. 보통 이러한 클래스를 응집도가 높다고 합니다.
❗❗ 속보! 객체마을 카페 추가 합병 확정
두 식당은 커피 메뉴를 추가하기 위해 객체마을 카페도 추가 합병하기로 결정합니다. 🤝
위에서 반복자 패턴으로 구성을 해놨기 때문에 별 문제가 없을 것으로 예상했죠.
우선 객체마을 카페 메뉴를 살펴볼까요? ☕
import java.util.HashMap;
import java.util.Map;
public class CafeMenu {
Map<String, MenuItem> menuItems = new HashMap<>();
public CafeMenu() {
addItem("베지 버거와 에어 프라이", "통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거", true, 3.99);
addItem("오늘의 스프", "샐러드가 곁들여진 오늘의 스프", false, 3.69);
addItem("부리토", "통 핀토콩과 살사, 구아카몰이 곁들여진 푸짐한 부리토", true, 4.29);
}
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(name, menuItem);
}
public Map<String, MenuItem> getMenuItems() {
return menuItems;
}
}
위 카페 메뉴 코드를 두 식당이 사용하던 프레임워크에 맞게 수정해야겠네요.
...
import java.util.Iterator;
public class CafeMenu implements Menu {
...
// public Map<String, MenuItem> getMenuItems() {
// return menuItems;
// }
public Iterator<MenuItem> createIterator() {
return menuItems.values().iterator();
}
}
다음으로 종업원 코드에 카페 메뉴를 추가해야겠죠?
import java.util.Iterator;
public class Waitress {
...
Menu cafeMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
...
this.cafeMenu = cafeMenu;
}
public void printMenu() {
...
Iterator<MenuItem> cafeIterator = cafeMenu.createIterator();
...
System.out.println("\n카페 메뉴");
printMenu(cafeIterator);
}
...
}
이제 잘 작동하는지 테스트를 해봅시다.
public class Application {
public static void main(String[] args) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
CafeMenu cafeMenu = new CafeMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);
waitress.printMenu();
}
}
위처럼 새 집합체도 쉽게 추가할 수 있었네요! 🥳
...하지만 새로운 메뉴가 추가될 때마다 종업원에 코드를 계속 추가해줘야 하므로, 이는 OCP(Open Closed Principle)에 위배된다는 걸 알게 됩니다. 😞
지금까지의 구조도 꽤 훌륭하지만, 여러 메뉴를 한꺼번에 관리할 수 있는 방법이 필요합니다.
아예 메뉴를 List로 받아서 처리하면 어떨까요?
import java.util.Iterator;
import java.util.List;
public class Waitress {
List<Menu> menus;
public Waitress(List<Menu> menus) {
this.menus = menus;
}
public void printMenu() {
Iterator<Menu> menuIterator = menus.iterator();
while(menuIterator.hasNext()) {
Menu menu = menuIterator.next();
printMenu(menu.createIterator());
}
}
private void printMenu(Iterator<MenuItem> iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
import java.util.ArrayList;
import java.util.List;
public class Application {
public static void main(String[] args) {
List<Menu> menus = new ArrayList<>();
menus.add(new PancakeHouseMenu());
menus.add(new DinerMenu());
menus.add(new CafeMenu());
Waitress waitress = new Waitress(menus);
waitress.printMenu();
}
}
이제 새로운 메뉴가 추가되어도 종업원 코드를 수정하지 않아도 되겠네요! 😌🥳
😲❗ 그런데 이번에는 기존 메뉴에 디저트 서브 메뉴를 추가해달라고 하는군요. 하지만 지금의 구조로는 메뉴를 계층 구조로 만들기 힘들 것 같네요.
이런 상황에 사용되는 패턴이 바로 컴포지트 패턴입니다.
컴포지트 패턴(Composite Pattern)
객체를 트리구조로 구성해서 계층구조를 구현합니다. 이 패턴을 사용하면 단일 객체와 트리로 구성된 복합 객체를 똑같은 방법으로 다룰 수 있습니다.
그럼 컴포지트 패턴을 메뉴에 어떻게 적용할 수 있을까요?
우선 Menu 공통 인터페이스를 만들고, 복합(Composite) 메뉴, 단일(Leaf) 메뉴 클래스를 만듭니다.
// Menu 공통 인터페이스
public abstract class MenuComponent {
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// 하위 메뉴를 가지는 복합 메뉴
import java.util.ArrayList;
import java.util.List;
public class CompositeMenu extends MenuComponent {
List<MenuComponent> menuComponents = new ArrayList<>();
String name;
String description;
public CompositeMenu(String name, String description) {
this.name = name;
this.description = description;
}
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public void print() {
System.out.print("\n" + getName());
System.out.println(", " + getDescription());
System.out.println("-----------------------");
for (MenuComponent menuComponent : menuComponents) {
menuComponent.print();
}
}
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// 하위 메뉴가 없는 단일 메뉴
public class LeafMenu extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;
public LeafMenu(String name, String description, boolean vegetarian, double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public boolean isVegetarian() {
return vegetarian;
}
public void print() {
System.out.print(" " + getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}
이제 위 클래스들을 활용해서 트리 구조를 구현할 수 있겠네요! 🌳
그럼 바로 테스트해 볼까요?
public class Application {
public static void main(String[] args) {
MenuComponent pancakeHouseMenu = new CompositeMenu("팬케이크 하우스 메뉴", "아침 메뉴");
MenuComponent dinerMenu = new CompositeMenu("객체마을 식당 메뉴", "점심 메뉴");
MenuComponent cafeMenu = new CompositeMenu("카페 메뉴", "저녁 메뉴");
MenuComponent dessertMenu = new CompositeMenu("디저트 메뉴", "디저트를 즐겨 보세요");
// 최상위 메뉴 객체 생성
MenuComponent allMenus = new CompositeMenu("전체 메뉴", "전체 메뉴");
// 최상위 메뉴에 하위 메뉴들 추가
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
// 객체마을 식당 메뉴에 하위 메뉴 추가
dinerMenu.add(new LeafMenu(
"파스타",
"마리나라 소스 스파게티, 효모빵도 드립니다.",
true,
3.89
));
// 객체마을 식당 메뉴에 하위 메뉴 추가
dinerMenu.add(dessertMenu);
// 디저트 메뉴에 또 다른 메뉴 추가
dessertMenu.add(new LeafMenu(
"애플 파이",
"바삭바삭한 크러스트에 바닐라 아이스크림이 얹혀 있는 애플 파이",
true,
1.59
));
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
반복자 패턴(Iterator Pattern)
컬렉션의 구현 방법을 노출하지 않으면서 객체 내의 모든 항목에 접근하는 방법을 제공합니다.
컴포지트 패턴(Composite Pattern)
객체를 트리구조로 구성해서 계층구조를 구현합니다. 이 패턴을 사용하면 단일 객체와 트리로 구성된 복합 객체를 똑같은 방법으로 다룰 수 있습니다.
반복자 패턴, 컴포지트 패턴에서 배울 수 있는 객체지향 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 상호작용하는 개체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.
- 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다(OCP).
- 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.
- 먼저 연락하지 마세요. 저희가 연락드리겠습니다(할리우드 원칙).
- 어떤 클래스가 바뀌는 이유는 하나뿐이어야 한다.
'Fundamental > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 8. 템플릿 메소드 패턴 (1) | 2024.10.26 |
---|---|
[디자인 패턴] 7. 어댑터 패턴, 퍼사드 패턴 (1) | 2024.10.04 |
[디자인 패턴] 6. 커맨드 패턴(Command Pattern) (0) | 2024.09.29 |
[디자인 패턴] 5. 싱글턴 패턴(Singleton Pattern) (0) | 2024.09.20 |
[디자인 패턴] 4. 팩토리 패턴(Factory Pattern) (0) | 2024.09.10 |