본문 바로가기
개발/설계

캡슐화는 무엇인가?

by 돼지얍 2023. 4. 28.
반응형

캡슐화는 자바와 같은 객체지향 언어를 처음 배우게 되면 객체지향의 장점으로 정의되는 이론 중 하나이다. 하지만 많은 개발 입문자들이 메소드에 왜 코드를 숨겨야 하는지 이론적으로만 알고 넘어가는 이유가 다수다. 그리고 언어를 배울 때 필요성이 직접적으로 느껴지지 않기 때문에 개념만을 살피게 된다.

물론, 교육을 받을 시간 동안 이러한 추상적인 이론에 붙잡혀 있을 여유가 없을 것이라고 생각한다. 나도 그랬다. 하지만 개발자는 변화에 대응해야 하고 객체지향 언어에서 핵심은 캡슐화와 인터페이스에 있다는 것이었다.

 

이글은 오브젝트의 저자 조영호님의 책을 바탕으로 정리한 글입니다.

http://www.yes24.com/Product/Goods/74219491

오브젝트 조영호 저자

코드를 숨기는 캡슐화의 장점

코드를 숨긴다는 것은 객체 간의 결합도를 최소화한다는 것이다. 하지만 결합도를 최소화한다는 것이 객체 간의 협력 관계를 없앤다는 뜻은 아니다.
그렇다면 협력관계란 무엇일까? 손님이 오프라인에서 음식을 주문하는 과정을 통해 객체 간 협력관계의 예시를 보자.

참고문헌: 객체지향의 사실과 오해(조영호 저)의 책내용의 객체 설계를 바탕으로 만들었습니다.

주문 성공 시나리오.

  1. 손님은 원하는 음식의 정보를 메뉴에게 요청한다.
  2. 메뉴 목록을 통해 원하는 메뉴를 고른다.
  3. 가게 주인에게 햄버거를 주문한다.
  4. 가게 주인은 햄버거를 만든다.
  5. 손님이 요청한 음식을 전달한다.

간편한 예시를 위해 계산에 대한 구체적인 행동을 제외하고 순수한 손님의 목적을 기반한 유스 케이스이다. 이 시나리오에서 우리는 어떤 객체를 예상할 수 있을까?

  • 손님: 손님은 주문을 성공시키기 위한 중요한 객체이다 주문을 요청하고 받는 것이 목적이다.
  • 메뉴: 손님에게 가게에서 제공하는 음식 메뉴를 보여 줄 수 있어야 한다.
  • 음식 정보: 메뉴는 손님이 원하는 메뉴의 정보를 제공해 줄 수 있어야 한다.
  • 가게 주인: 가게 주인은 요청된 음식을 전달할 책임을 가진다.
  • 음식: 요청 주문을 성공하기 위한 목적이자 핵심 객체이다.

이들을 자바의 클래스를 사용해 객체간의 협력을 구현해 보자.

 

손님: Customer 

class Customer {
    public void order(String foodName) {
        	
    }
}

order메소드는 주문 실행했을 때 구현되어야 할 코드 공간이다.

 

음식 정보: FoodItem

class FoodItem {
	
    private String name;
    private int price;
    
    public FoodItem(String name, int price) {
    	this.name = name;
        this.price = price;
    }
    //get메소드...
}

가게에서 제공하는 음식 메뉴의 정보가 담겨있다.

메뉴: Menu

class Menu {
    private List<FoodItem> foodList;
	
    public Menu(List<FoodItem> foodList) {
    	this.foodList = foodList;
    }
    
    public FoodItem choose(String foodName) {
    	for(FoodItem foodItem : foodList) {
            if(foodItem.getName().equals(foodName)) {
            	return foodItem;
            }
        }
    	 return null;
    }
}

메뉴 객체가 생성될 때 가게에서 제공하는 음식 메뉴의 리스트를 주입받는다. foodName을 인자로 받아 foodName과
이름이 일치하는 음식 정보를 반환한다. 없을 이 null을 반환한다.

 

음식: Food

class Food {
	
    private String name;
    private int price;
    
    public Food (FoodItem foodItem) {
    	this.name = foodItem.getName();
        this.price = foodItem.getPrice();
    }
    
}

음식은 foodItem에게 정보를 제공받아 음식 객체를 생성한다.

 

가게주인: Owner

class Owner {
	
    public Food cookFood(FoodItem foodItem) {
        return new Food(foodItem);
    }
}

가게 주인은 FoodItem을 전달받아 음식을 객체를 생성하여 제공한다.

 

이제 Customer 클래스로 돌아가서 손님의 주문메소드를 실행했을 때 벌어지는 일들을 구현해 보자.

class Customer {
    public void order(String foodName, Menu menu, Owner owner) {
        FoodItem foodItem = menu.choose(foodName);
        Food food = owner.cookFood(foodItem);
    }
}

주문을 수행하기 위해선 기존 foodName인 자뿐만 아니라 menu 객체와 owner라는 객체가 필요해진다. 항상 개념을 직접 구현할 때는 인터페이스 변경이 생길 수가 있다.


의문점: 왜 메뉴를 선택하는 행동을 Customer 객체가 하는 것이 아닌 menu가 choose 메서드를 통해 실행하는 것일까?

분명 현실의 구성을 객체로 추상화한다면 메뉴 선택의 권한은 Customer가 가져야 할 것이다. 하지만 메뉴 객체의 메서드를 통해서 Menu 객체 직접 음식 이름을 인자로 받아 선택하여 고객에게 전달한다.

객체를 현실 세상에 비유하는 이유는 캡슐화하는 소프트웨어 객체의 자율성을 설명하기에 효과적이기 때문이다. 단순히 모방하는 것이 아닌 모든 객체는 자율성을 가져야 한다.

 

객체의 자율성

객체지향의 사실과 오해를 쓴 조영호 저자는 객체는 현실을 추상화한 것은 맞지만 세계가 같은 건 아니라고 말한다.

조영호 저자는 이에 대한 예시로 이상한 나라의 앨리스 동화를 설명했다.

현실에서의 트럼프 카드는 움직일 수 없고 앞면인가 뒷면인가 등 자신의 상태를 인간에게 의존한다.

하지만 이상한 나라에서는 트럼프 카드가 직접 생각하고 자신의 행동을 결정하며 자신이 앞면으로 노을 지 뒷면으로 노을 지 선택할 수 있고 창을 들고 걸어 다니기까지 한다. 동화 나라에서와 마찬가지로 소프트웨어라는 동화에서 객체는 자율성을 가지도록 설계해야 한다.

객체가 자율성을 가져야 한다는 사실을 알았다. 하지만 왜 아직 자율성을 가져야 하는지 이해하지 못한다. 다시 돌아가 Customer order 메서드의 menu 객체의 자율성을 제한했을 때 어떤 코드가 만들어질까?

class Customer {
    public void order(String foodName, Menu menu, Owner owner) {
        //메뉴에서 메뉴정보들을 가져와 손님이 직접 찾는다.
        List<FoodItem> foodList = menu.getFoodList();
        for(FoodItem foodItem : foodList) {
            if(foodItem.getName().equals(foodName)) {
            	return foodItem;
            }
        }
        
        Food food = owner.cookFood(foodItem);
    }
}

menu의 choose 메서드를 Customer 객체로 끌어와 손님이 직접 찾는 것처럼 재설계 하였다. 하지만 여기서 무엇이 문제가 될까? 바로 의존성이 추가되었다는 것이다.

만약 FoodItem을 찾는 기준이 추가되거나 변경되었다면, 손님은 해당 음식 이름에 맞는 메뉴를 찾는 과정이 더 복잡해질 것이다. 손님이 직접 메뉴를 선택하는 것처럼 보일 수 있지만, 실제로는 더 많은 단계를 거쳐야 한다.

이를 조금 더 쉽게 이해하기 위해 메뉴를 키오스크로 변경하여 설명해 보자면 기존 코드에서는 손님이 키오스크에서 특정 메뉴를 검색하고 결과만을 알면 됐었다. 그러나 이제 키오스크는 음식 메뉴의 데이터를 손님에게 전달하고, 결과를 찾는 알고리즘(방법)은 손님이 직접 해야 한다.

또한, 가게의 메뉴가 추가될 때마다 손님은 음식 메뉴 추가의 사실을 집에 있을 때나 일을 할 때나 계속 알고 있어야 한다.

이와 같이 Customer 객체가 Menu 객체가 구성하는 구체 정보에 대해 의존하게 되면서 FoodItem의 변화로 인한 사이드 이펙트를 Menu뿐만 아니라 Customer에게까지 끼치게 되었다.

 

캡슐화

캡슐화에 장점에 대해 설명하지 않았는데 캡슐화를 얘기하는가? 우리는 이미 캡슐화의 장점에 대해 알고 있다.

Customer 객체의 menu 인스턴스의 choose 메서드 실행을 통해 우리는 캡슐화의 장점을 이미 느끼고 있는 것이다.

캡슐화를 통해 Customer 객체는 menu의 내부 속성을 알지 못한다. 하지만 menu가 해당 음식 정보를 제공해 주는 복잡한 방법을 알지 않아도 된다. Customer는 손님이 원하는 음식 정보가 든 FoodItem 객체만을 받아 가게 주인에게 전달하면 되는 것이다.

 

이처럼 객체가 자신의 상태에 대한 결과를 자율적으로 전달하는 방법을 캡슐화를 통해 가능하다는 것을 알 수 있었고, 객체가 가지는 책임을 명시하고 해당 객체 클래스에 결과를 수행하는 방법을 캡슐화함으로써 변화에 따른 영향을 최소화할 수 있었다.

 

자율성 있는 객체를 이해함으로써, 캡슐 화가 중요한 것이 아니라 객체의 자율성을 위해 캡슐화와 캡슐화된 코드를 실행하는 인터페이스 메서드가 중요하다는 것을 알 수 있었다.

반응형

'개발 > 설계' 카테고리의 다른 글

OCP 원칙을 지키지 못할 상황일때  (0) 2023.03.22