본문 바로가기

기타/객체지향의 탄생(2013)

객체지향의 탄생-Iterator Pattern

  1. 이터레이터 패턴(Iterator)

1. 나랑 친한 동네 형님도 프로그래머셨다. 그래서 늘상 피곤해하셨다. 형님은 대한민국의 팍팍한 업무여건에 고개를 절래절래 흔들고 최근에 아이 옷가게를 새로 차렸다. 그 모진 업무여건에서 벗어나 나만의 사업을 하시는 형님 얼굴을 최근에 봤더니 그렇게 행복해 보일수가 없었다. 형님의 사업이 대박나기를 바랐다.

 

그래도 나는 아직 프로그래머로 자부심을 가지는 이유는 우리가 하는 일이 사람을 행복하게 해주기 때문이다. 대표적인 예가 인터넷 쇼핑몰이다. 예전같으면 차타거나 발품팔고 직접 상점에가서 물건을 샀는데 지금은 클릭한번으로 물건 사면 집앞으로 배송된다. 이점은 이동하면서 생기는 사람과 지구의 에너지 소모를 절약하는데 기여한다. 특히 이것은 약자에게 힘이 되어주었을 것이다. 할머니나 장애인들은 어디 한번 나갈때 엄청난 에너지가 필요할 것인데 인터넷으로 클릭한번 하면 집앞으로 배송된다.

 

이 과정에서 가장 큰 역할을 수행하는 분이 바로 택배기사님이다. 일부 거친 택배기사님도 있지만 나는 택배기사님의 노고에 항상 고마워하고 있다. 그분들은 대한민국 동맥의 적혈구 같은 분들이다.

 

문제는 쇼핑몰 마다의 물류분류 시스템이 틀려서 시작됐다.

 

2. 쇼핑몰에서 택배물류창고로 보낼때 물류를 종류별 지역별로 분리하는 작업이 진행 될 것이다. A쇼핑몰에서 보내온 물류트럭이 있는데 이 물류트럭의 박스들에 찍힌 라벨은 꼭 배열방식으로 돌면서 물류를 분류하게끔 표기가 되어 있었다.

 

B쇼핑몰에서 보내온 물류트럭도 있는데 이 물류트럭의 박스들에 찍힌 라벨은 꼭 리스트방식으로 돌면서 물류를 분류하게끔 표기가 되어 있었다.

 

그래서 물류센터 직원은 배열방식으로 물류를 분류하는 방법도 알아야되고 리스트 방식으로 물류를 분류하는 방법도 알아야되며, 물류 분류 공정 시스템은 배열 분류 공정, 리스트 분류 공정, 헤시테이블 분류 공정도 구축이 되어 있어야 해서 업무능률이 떨어지고 공정 구축 및 관리 비용이 몇배로 증가되고 있다.

 

이 상황을 인식한 물류 어플리케이션 개발자는 고민하기 시작했다.

 

3. 각각의 쇼핑몰마다 라벨표기방식이 틀려 반복하면서 물류분류하는 방식이 다를 수 밖에 없다면 물류 센터에서 작은 공정을 하나 더 두기로 했다.

 

쇼핑몰별로 배열, 리스트, 해시테이블 방식등의 라벨 표기 방식을 확인한 다음 이터레이션 라벨로 새로 덮어씌어서 표기하는 것이다. 물론 이터레이션 라벨로 새로 덮어씌우는 절차는 최대한 자동화 처리 되게끔 구성되어 있다. 이제 이터레이션 라벨 표기 방식으로 덮어 씌어졌으면 이터레이션식 물류 분류 공정시스템으로 박스가 몽땅 이동되어 어느 쇼핑몰에서 왔던 분류가 한방에 처리 된다.

 

이렇게 되면 물류센터 직원은 이터레이션식 물류 분류 방법만 알면되고 배열, 리스트, 해시테이블등의 여러개로 분산되어 있던 물류공정시스템은 모두 폐기처리해도 된다. 물류처리 비용이 획기적으로 절감되었다.

 

4. 각 쇼핑몰에서 물류센터로 올때 물류분류 방식이 틀린것은 다음과 같이 표현된다.

 

[문류분류 방식이 다른 물류 센터, 장비도 3배 인력도 3배 더 들어간다.]

 

물류공장은 배열처리방식도 알아야하고 리스트처리방식도 알아야 된다. 이 반복 처리 작업을 캡슐화 처리한 결과는 다음과 같다.

 

[물류에 이터레이션 태그를 덧붙여 물류 공정을 단순화 한다.]

 

이렇게 하여 반복 처리 되는 부분을 이터레이터 인터페이스로 통일시켰다. 물류공장은 배열과 리스트등의 데이터 구조를 알 필요없이 이터레이터에만 의존하여 박스 데이터를 반복처리한다.

 

  • 효율적이지 않은 자료구조 처리의

[물류센터가 여러 자료구조 처리 방법을 알고, 별도로 구현해야 한다.]

 

public class TacbaeItem {

    public String name;

    public String juso;

    public String price;

    

    public TacbaeItem(String name, String juso, String price) {

        this.name = name;

        this.juso = juso;

        this.price = price;

    }

 

    public String getName() {

        return name;

    }

 

    public String getJuso() {

        return juso;

    }

 

    public String getPrice() {

        return price;

    }

}

[택배 물품 클래스, '빈'역할을 수행한다.]

 

 

public class TruckArray {

    final int MAX_TACBAECOUNT = 3;

    int addCount = 0;

    TacbaeItem[] tacbaeItems;

    

    public TruckArray() {

        tacbaeItems = new TacbaeItem[MAX_TACBAECOUNT];

        

        addItem("난로", "서울시", "착불");

        addItem("영양제", "부천시", "선불");

        addItem("잠바", "부산시", "착불");

    }

    

    public void addItem(String name, String juso, String price) {

        TacbaeItem item = new TacbaeItem(name, juso, price);

        if(addCount >= MAX_TACBAECOUNT) {

            System.out.println("트럭 용량 때문에 더 이상 추가할 수 없습니다.");

        } else {

            tacbaeItems[addCount] = item;

            addCount++;

        }

    }

    

    public TacbaeItem[] getTacbaeItems() {

        return tacbaeItems;

    }

} 

[배열방식을 쓰는 트럭]

 

public class TruckArrayList {

    ArrayList tacbaeItems;

    

    public TruckArrayList() {

        tacbaeItems = new ArrayList();

        

        addItem("스티브잡스 자서전", "서울시", "착불");

        addItem("화장품", "부천시", "선불");

        addItem("아이패드", "부산시", "착불");

    }

    

    public void addItem(String name, String juso, String price) {

        TacbaeItem item = new TacbaeItem(name, juso, price);

        tacbaeItems.add(item);

    }

    

    public ArrayList getTacbaeItems() {

//        System.out.println("1====["+tacbaeItems+"]");

        return tacbaeItems;

    }

} 

[어레이 리스트를 쓰는 트럭]

 

 

public class TruckHashTable {

    Map tacbaeItems;

    

    public TruckHashTable() {

        tacbaeItems = new HashMap();

        

        addItem("USB허브", "서울시", "착불");

        addItem("아이폰 케이스", "부천시", "선불");

        addItem("아이패드 미니", "부산시", "착불");

    }

    

    public void addItem(String name, String juso, String price) {

        TacbaeItem item = new TacbaeItem(name, juso, price);

        tacbaeItems.put(name, item);

    }

    

    public Map getTacbaeItems() {

        return tacbaeItems;

    }

} 

[해시맵을 쓰는 트럭]

 

public class MulryuCenter {

    TacbaeItem itemArray[];

    List itemList;

    Map itemMap;

    

    ArrayList allItemList;

    

    public void getTruckArrayProcess() {

        TruckArray truckArray = new TruckArray();

        itemArray = truckArray.getTacbaeItems();

    }

    

    public void getTruckArrayListProcess() {

        TruckArrayList truckArray = new TruckArrayList();

        itemList = truckArray.getTacbaeItems();

    }

    

    public void getTruckHashTable() {

        TruckHashTable truckHash = new TruckHashTable();

        itemMap = truckHash.getTacbaeItems();

    }

    

    public void mulryuProcess() {

        // 택배 데이터를 가져온다.

        getTruckArrayProcess();

        getTruckArrayListProcess();

        getTruckHashTable();

        

        // 모든 택배 아이템을 이곳에 집어넣는다.

        allItemList = new ArrayList();

        

        // 가져온 데이터를 하나의 자료구조로 모은다.

        for(int i = 0 ; i < itemArray.length ; i++) {

            TacbaeItem item = itemArray[i];

            allItemList.add(item);

        }

        System.out.println("allItemList ["+allItemList+"] ["+allItemList.size()+"]");

        

        for(int i = 0 ; i < itemList.size() ; i++) {

            TacbaeItem item = (TacbaeItem)itemList.get(i);

            System.out.println("item list ["+item.name+"]");

            allItemList.add(item);

        }

        System.out.println("allItemList ["+allItemList+"] ["+allItemList.size()+"]");

        

        Iterator iterator = itemMap.values().iterator();

        do {

            if(!iterator.hasNext()) {

                break;

            }

            TacbaeItem item = (TacbaeItem)iterator.next();

            System.out.println("item map ["+item.name+"]");

            allItemList.add(item);

        } while(true);

        System.out.println("allItemList ["+allItemList+"] ["+allItemList.size()+"]");

        

        // 정리된 자료구조를 출력한다.

        for(int i = 0 ; i < allItemList.size(); i++) {

            TacbaeItem item = (TacbaeItem)allItemList.get(i);

            System.out.println("item ["+item.name+"]");

        }

        

    }

    

    public static void main(String[] args) {

        

        MulryuCenter center = new MulryuCenter();

        center.mulryuProcess();

    }

} 

[물류 센터를 보면 자료구조가 다른 각 트럭별로 리스트를 뽑아 각각 따로 택배아이템을 뽑아내고 있다.]

 

  • 이터레이션 패턴으로 개선하다.

[물류센터는 자료구조 처리 방법을 모르고 이터레이터에게 위임하면 된다.]

 

public interface CustomIterator {

    boolean hasNext();

    Object next();

} 

[자료구조의 반복을 담당할 CustomIterator 인터페이스를 작성한다.]

 

 

public class ArrayIterator implements CustomIterator {

    TacbaeItem itemArray[];

    int position = 0;

    

    public ArrayIterator(TacbaeItem items[]) {

        this.itemArray = items;

    }

    

    @Override

    public boolean hasNext() {

        if(position >= itemArray.length || itemArray[position] == null) {

            return false;

        } else {

            return true;

        }

    }

 

    @Override

    public Object next() {

        TacbaeItem item = itemArray[position];

        position++;

        return item;

    }

    

} 

[배열 방식으로 택배 아이템을 추출하는 이터레이터를 생성한다.]

 

public class ArrayListIterator implements CustomIterator {

    ArrayList<TacbaeItem> tacbaeItems;

    int position = 0;

 

    public ArrayListIterator(ArrayList tacbaeItems) {

        this.tacbaeItems = tacbaeItems;

    }

    

    @Override

    public boolean hasNext() {

        if(position >= tacbaeItems.size() || tacbaeItems.get(position) == null) {

            return false;

        } else {

            return true;

        }

    }

 

    @Override

    public Object next() {

        TacbaeItem item = tacbaeItems.get(position);

        position++;

        return item;

    }

 

} 

[어레이 리스트 방식으로 택배 아이템을 추출하는 이터레이터를 작성한다.]

 

 

public class HashTableIterator implements CustomIterator {

    Map tacbaeItems;

    Iterator iterator;

    

    public HashTableIterator(Map tacbaeItems) {

        this.tacbaeItems = tacbaeItems;

        iterator = this.tacbaeItems.values().iterator();

    }

    

    @Override

    public boolean hasNext() {

        // TODO Auto-generated method stub

        return iterator.hasNext();

    }

 

    @Override

    public Object next() {

        // TODO Auto-generated method stub

        return iterator.next();

    }

 

} 

[해시맵 형식으로 택배 아이템을 추출하는 이터레이터를 작성한다.]

 

public interface TruckAggragate {

 

    public void addItem(String name, String juso, String price);

    

    public CustomIterator createIterator();

}

[이터레이터와 연동하여 자료구조 처리를 하는 클래스 가족의 부모 인터페이스]

 

public class TruckArrayGood implements TruckAggragate {

    final int MAX_TACBAECOUNT = 3;

    int addCount = 0;

    TacbaeItem[] tacbaeItems;

    

    public TruckArrayGood() {

        tacbaeItems = new TacbaeItem[MAX_TACBAECOUNT];

        

        addItem("난로", "서울시", "착불");

        addItem("영양제", "부천시", "선불");

        addItem("잠바", "부산시", "착불");

    }

    

    public void addItem(String name, String juso, String price) {

        TacbaeItem item = new TacbaeItem(name, juso, price);

        if(addCount >= MAX_TACBAECOUNT) {

            System.out.println("트럭 용량 때문에 더 이상 추가할 수 없습니다.");

        } else {

            tacbaeItems[addCount] = item;

            addCount++;

        }

    }

    

    public CustomIterator createIterator() {

        return new ArrayIterator(tacbaeItems);

    }

} 

[이터레이터를 리턴하는 배열을 사용하는 개선된 트럭 클래스]

 

 

public class TruckArrayListGood implements TruckAggragate {

    ArrayList tacbaeItems;

    

    public TruckArrayListGood() {

        tacbaeItems = new ArrayList();

        

        addItem("스티브잡스 자서전", "서울시", "착불");

        addItem("화장품", "부천시", "선불");

        addItem("아이패드", "부산시", "착불");

    }

    

    public void addItem(String name, String juso, String price) {

        TacbaeItem item = new TacbaeItem(name, juso, price);

        tacbaeItems.add(item);

    }

    

    public CustomIterator createIterator() {

        return new ArrayListIterator(tacbaeItems);

    }

} 

[이터레이터를 리턴하는 어레이 리스트를 사용하는 개선된 트럭 클랙스]

 

public class TruckHashTableGood implements TruckAggragate{

    Map tacbaeItems;

    

    public TruckHashTableGood() {

        tacbaeItems = new HashMap();

        

        addItem("USB허브", "서울시", "착불");

        addItem("아이폰 케이스", "부천시", "선불");

        addItem("아이패드 미니", "부산시", "착불");

    }

    

    public void addItem(String name, String juso, String price) {

        TacbaeItem item = new TacbaeItem(name, juso, price);

        tacbaeItems.put(name, item);

    }

    

    public CustomIterator createIterator() {

        return new HashTableIterator(tacbaeItems);

    }

} 

[이터레이터를 리턴하는 해시맵을 사용하는 개선된 트럭 클래스]

 

 

public class MulryuCenterGood {

    

    public void mulryuProcess() {

        TruckArrayGood truckArray = new TruckArrayGood();

        TruckArrayListGood truckArrayList = new TruckArrayListGood();

        TruckHashTableGood truckHash = new TruckHashTableGood();

 

        CustomIterator arrayIterator = truckArray.createIterator();

        CustomIterator listIterator = truckArrayList.createIterator();

        CustomIterator hashIterator = truckHash.createIterator();

        

        printMenu(arrayIterator);

        printMenu(listIterator);

        printMenu(listIterator);

    }

    

    private void printMenu(CustomIterator iterator) {

        while(iterator.hasNext()) {

            TacbaeItem tacbaeItem = (TacbaeItem)iterator.next();

            System.out.println("tacbaeItem ["+tacbaeItem.name+"]");

        }

    }

    

    public static void main(String[] args) {

        

        MulryuCenter center = new MulryuCenter();

        center.mulryuProcess();

    }

} 

[이터레이터를 사용하여 반복처리를 하나의 메소드에서 통일하여 사용할 수 있다. 깔끔해진 로직을 확인한다.]

 

5. 이터레이션 패턴은 변하는 것을 변하지 않는 부분으로부터 캡슐화하라. 추상화된 클래스에 의존하라는 전형적인 디자인 원칙을 따르고 있다.

 

반복하는 부분은 위의 예처럼 배열이면 배열, 리스트면 리스트, 해시테이블이면 해시테이블 방식에 따라 로직이 변경될 수 있다. 이 변경되는 반복 처리 부분을 이터레이션 패턴으로 캡슐화하여 객체지향의 장점을 고스란히 이어가게 된다.

 

또한 기존에는 배열, 리스트, 해시테이블등 여러 자료구조 컬렉션 객체에 의존했어야 했는데 이터레이션 패턴을 통해 의존성을 이터레이션 객체 하나로 줄이게 되어 높은 응집도 낮은 결합도를 지키는데 도움이 되었다.

 

이터레이션 패턴은 자료구조 구현 방법을 노출하지 않아도 해당 자료구조에 대해 일관성있게 접근하는 방법을 제공하는 패턴이다. 자료구조를 더하고 빼고 읽고 삭제하는데 반복작업은 꼭 필요하다. 그래서 이터레이터 패턴은 실제 프로그래밍에서 유익하게 활용할 수 있다.

 

[설명]

 

 

덧 ) 이 객체지향의 탄생 원고는 제가 책으로 내려다가 일단 잘 안되었는데요. 이유는 비문이 많다. 단락내 주제가 중복된다. 어떤 상황 설명을 과장한다.등 입니다. 그래도 원고를 일단 블로그에 몽땅 풀어보고 언젠가 제대로 교정해서 다시 도전할 생각입니다. 이점을 감안해서 읽고 객체지향을 이해하는데 도움이 되셨으면 좋겠습니다. 의견도 주셨으면 좋겠습니다. 원고 조금만 교정하면 괜찮을것 같은 출판사 관계자분의 피드백도 환영합니다. 매주 월요일 발행 예정입니다.