본문 바로가기

Fundamental/Design Pattern

[디자인 패턴] 9. 반복자 패턴, 컴포지트 패턴

 

 

반복자 패턴(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];
    ...
}

 

💥 그런데 위와 같은 코드는 아래와 같은 문제점이 있다는 걸 깨닫게 됩니다.

  1. 종업원은 PancakeHouseMenu와 DinerMenu와 같은 구상 클래스에 맞춰서 코딩하고 있습니다.
  2. 종업원은 각 메뉴의 구조를 알아야 하므로 캡슐화의 기본 원칙이 지켜지지 않고 있습니다.
  3. 각 메뉴 항목에 접근하려면 서로 다른 반복문이 필요하고, 메뉴가 추가되면 새로운 반복문을 다시 추가해야 합니다.
  4. 나중에 각 메뉴가 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).
  • 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.
  • 먼저 연락하지 마세요. 저희가 연락드리겠습니다(할리우드 원칙).
  • 어떤 클래스가 바뀌는 이유는 하나뿐이어야 한다.