본문 바로가기
개발/JAVA

캡슐화란 무엇인가? 어떤 이점이 있는가?

by 손너잘 2021. 2. 18.

객체지향을 공부하면 처음부터 귀에 못이 박히도록 듣는 단어가 있다. 바로 캡슐화다.

캡슐화가 도대체 뭐고, 이게 왜 중요한 요소일까?

 

일단 위키를 보도록 하자

 

캡슐화(영어: encapsulation)는 객체 지향 프로그래밍에서 다음 2가지 측면이 있다:

객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶고,
실제 구현 내용 일부를 외부에 감추어 은닉한다.

 

그래, 캡슐화는 객체의. 속성과 행위를 하나로 묶고 구현 내용을 외부에 감춘다는 것, 알겠다. 그래서 뭐 어쩌라는건가? 이게 왜 중요한가??

 

이에 대한 답을 한번 찾아가 보도록 하자.

 

위키의 정의에 따르면, 캡슐화는 클래스 안에다가 데이터와 데이터를 처리하는 행위를 묶어 놓는 것 이라고 말할 수 있다.

사실 객체지향을 처음 접하면 이 말도 뭔말인지 어려울 수 있다. (필자가 그랬으니..)

아래 예제를 보자.

class Capsule {
    int number;
    
    public Capsule(int number) {
        this.number = number;
    }
    
    public double getHalf() {
        return number / 2;
    }
}

------------------------------------------------------

class Main {
    public static void main(String[] args) {
        Capsule capsule = new Capsule(10);
        System.out.println(capsule.getHalf());
    }
}

간단한 소스이다. int값을 초기값으로 갖는 객체가 있고, 그 값의 절반을 반환하는 gethalf() 라는 메소드가 존재한다. 

여기서 캡슐화는 어느부분을 뜻할까? 바로 Capsule 클래스 자체를 의미한다.

캡슐화를 위키와 조금 다르게 말하면, 아래와 같이 말할 수 있다.

 

데이터와, 데이터를 처리하는 행위를 묶고, 외부에는 그 행위를 보여주지 않는 것.

 

그렇다면 위 소스에서 정말 위의 정의를 만족하는지 보자. Capsule 클래스는 int라는 데이터를 가지고 있다. 그리고 getHalf라는 데이터를 처리하는 행위또한 가지고 있다. 마지막으로 Main메소드의 입장에서, Capsule 클래스의 getHalf() 를 사용할 수는 있지만 구현이 어떻게 되어 있는지는 알 수 없다.

 

와! 위 정의를 만족한다! 그래, 그러면 캡슐화라는게 이런거란건 알겠다. 그런데 데이터와, 행위를 하나로 묶고, 그걸 외부에 노출시키지 않는게 왜 중요한가?

아래와 같이 코드를 작성하더라도 같은 행위가 아닌가?

class Capsule {
    int number;
    
    public Capsule(int number) {
        this.number = number;
    }
    
    public int getNumber() {
        return number;
    }
}

------------------------------------------------------

class Main {
    public static void main(String[] args) {
        Capsule capsule = new Capsule(10);
        System.out.println(capsule.getNumber() / 2D);
    }
}

그렇다, 결과론적으론 같은 결과를 도출한다. 하지만 이런 코드를 작성하면 여러가지 문제점이 발생할 수 있다.

그 문제점이 무엇인지 한번 아래에서 살펴보자.

 

캡슐화를 지키기 위한 규칙중에는 Tell, Don't Ask 라는 원칙이 있다.

객체 내부의 데이터를 꺼내와서 처리하는게 아닌, 객체에게 처리할 행위를 요청하라는 행위이다. 이러한 행위를 우리는 "객체에 메세지를 보낸다" 라고 말한다.

 

그렇다면 왜 캡슐화를 지키기 위해서는 데이터를 객체로부터 받아와서 처리하면 안된다고 하는걸까? 캡슐화의 장점을 살펴보면 그 이유를 간단히 이해할 수 있다.

 

캡슐화를 통해 우리가 얻을 수 있는 이점중 가장 큰것은 코드의 중복을 피할 수 있다는 점과, 데이터를 처리하는 동작 방식을 외부에서 알 필요가 없다는 점이다.

 

코드의 중복을 피한다는 점과, 동작 방식을 외부에서 알 필요가 없다는 것 또한, 객체지향을 처음 접하면 그게 왜 중요한지 이해가 안될 수 있다. 이럴때는 예제를 통한 설명이 가장 확실하다.

 

어떤 물품의 10% 할인된 금액을 구해야 한다고 생각해 보자. 만일 데이터를 객체에서 받아와서 처리를 한다면 우리는 비즈니스 로직에 다음과 같은 코드를 추가할 것이다.

public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var(discountedPrice);
}

상품의 가격을 가지고 와서 10프로 할인된 가격을 구하고, 다른 로직으로 넘겼다. 즉, 위 코드는 데이터를 객체로 부터 받아와서 처리하는 로직을 구현하고 있다(아마 많이 본 형식의 코드일 것 이다). 그렇다면, 만일 10프로 할인된 금액을 다른 로직에서도 사용하게 된다면 어떻게 될까?

public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var(discountedPrice);
}

public void foo2(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var2(discountedPrice);
}

코드의 중복이 일어났다. 혹자는 그거 몇줄 안되는 코드 좀 중복 나면 어때?? 라고 생각할 수 있다. 하지만 위와 같은 로직이 서비스에서 수백번 필요하다 생각해 보자... 그걸 일일이 타이핑, 혹은 복붙하는 행위는 고역일 것 이다. 또는, 코드를 작성하는 코더가 변경되었다고 했을 때, 10프로 할인 로직을 아래처럼 작성해 버릴 수도 있다.

public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() - goods.getPrice() * 0.1;
    var(discountedPrice);
}

코드의 중복뿐 아니라 파편화 까지 일어났다.

 

좋다. 코드의 중복이 안좋다는것은 이제 알겠다. 그러면 데이터를 처리하는 방식의 외부에 드러나지 않는것은 어떤면에서 이점이 있을까?

위 예제에서 요구사항이 변경되어 10프로 할인된 금액이 아니라 20프로 할인된 금액으로 로직을 바꿔야 한다고 생각해 보자. 그렇다면 위와 같이 코드가 작성되었다면, 우리는 도대체 몇줄의 코드를 고쳐야 하는가? 그래, IDE의 검색기능을 이용해 편히 고칠 수 있다고 해 보자. 하지만 위에서 말한 것 처럼 코더가 바뀌어서 10프로 할인 로직이 다른 코드가 존재한다면? 이것또한 검색으로 찾을 수 있을것인가? 아마 매우 어려울 것 이다. 또한 이런 현상으로 인해 10프로 할인 로직을 20프로 할인로직으로 변경하지 못한 코드가 하나라도 존재한다면, 서비스에 큰 타격이 있을 것이다.

 

그렇다면 이번에는 데이터를 객체로 부터 받아오는게 아닌, 객체에게 처리를 요청하는 방식의 코드를 작성하면 어떻게 될까?

class Goods {
    int price = 10000;
    ...
    public int getDiscountedPrice() {
        return price * 0.9;
    }
}

public void foo(Goods goods) {
    double discountedPrice = goods.getDiscountedPrice();
    var(discountedPrice);
}

10프로 할인된 금액을 도출하는 로직이 객체 안으로 이동했다. 그에따라 foo()에서 할인된 금액을 생성하는 부분도, 비즈니스 로직이 10프로를 할인 하는게 아닌 Goods에게 "메세지를 보내서(메소드를 호출하여)" 데이터를 가지고 있는 Goods가 스스로 처리하도록 소스가 변경되었다.

 

그렇다면 이제 위에서 말했던 문제들을 다시 적용시켜 보자. 위와 같이 할인된 금액을 사용하는 로직이 수백개가 있고, 요구사항이 20프로를 할인하도록 변경됐다. 우리는 무엇을 바꾸면 되는가? getDiscountedPrice()의 로직을 수정하면 된다. 데이터를 처리하는 방식이 외부에 드러나는게 아닌, 객체 스스로 처리하도록 하니 모든 문제가 해결됐다.

 

이제 캡슐화의 강력함을 이해 하였는가?

 

필자는 이해하고 공중제비를 3바퀴나 돌았다.

댓글