본문 바로가기

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

객체지향의 탄생-캡슐화와 정보은닉

얼마전 이사간 우리집 내방은 창문이 훤히 뚫렸다. 창문 밖은 시원한 바람이 불었다. 창문 바깥에는 높은 건물도 없어 바깥 경치도 훤히 보였다. 나는 좋은집으로 이사왔다. 그러나 걱정도 되었다.

 

우리집이 2층인데다가 창문이 크고 넓어서 도둑이 마음만 먹으면 쉽게 칩입할수 있었다. 나는 고민하다가 결국 2층 넓은 창문을 방범창으로 막았다. 방범창으로 막았더니 내 집은 안전해지기는 했지만, 마치 감옥의 창살과 같은 느낌이라 보기 좋지는 않았다. 그래도 이 방법이 최선이었기 때문에 나는 곧 방범창에 익숙해졌다.

 

 

이번에 컴퓨터를 새로 마련했다. 기본 컴퓨터 사양에 업그레이드 옵션으로 구입하려고 했더니 돈이 들었다. 따로 램하고 하드디스크와 키보드를 구입하는 것이 돈을 더 아꼈다. 컴퓨터 본체가 오고 얼마뒤 램과 하드디스크, 키보드가 따로 도착했다.

 

램과 하드디스크 설치하고 나니 잘돌아간다. 따로 직접 설치한 비용과 컴퓨터 구입할 때 한방에 구입한 비용차이를 보니 몇십만원 가까이나 절약 되었다. 이렇게 컴퓨터와 컴퓨터 주요 부품이 모듈화 캡슐화가 잘되었기 때문에 나는 쉽게 원하는 사양으로 교체하여 쓸 수 있었다.

 

 

캡슐화란 용어는 용어의 뜻을 이해하는것도 두루뭉실하지만 그 쓰임새도 애매모호하다. 나는 몇년 경력이 되어서도 캡슐화란, 객체의 속성을 private로 만들어 이것을 get/set 메소드로 호출하는 방법으로만 이해했다. 그런데 저 객체지향 캡슐화 이론과는 다르게 실제 프로그래밍에서는 객체의 속성을 public으로 만들어 직접 호출하는것과의 차이점을 느끼지 못했다. 오히려 private로 선언하면 클라이언트에서 저 속성을 가져올때 get/set을 쓰느라 코드 보기가 더 지저분해서 좋게 생각하지 않았다.

 

public class BankInfo {

    public String bankCode = null;    

    public String bankName = null;

    public String email = null;

    public String tel = null;

    public String juso = null;    

}

 

public class BankInfo {

    private String bankCode = null;    

    private String bankName = null;

    private String email = null;

    private String tel = null;

    private String juso = null;

    

    public String getBankCode() {

        return bankCode;

    }

    public void setBankCode(String bankCode) {

        this.bankCode = bankCode;

    }

    public String getBankName() {

        return bankName;

    }

    public void setBankName(String bankName) {

        this.bankName = bankName;

}

    public String getEmail() {

        return email;

    }

    public void setEmail(String email) {

        this.email = email;

    }

    public String getTel() {

        return tel;

    }

    public void setTel(String tel) {

        this.tel = tel;

    }

    public String getJuso() {

        return juso;

    }

    public void setJuso(String juso) {

        this.juso = juso;

    }    

}

public 으로만 선언한 클래스가, private로 선언하고 getter/setter 로 구성한 클래스에 비해 더 깔끔해 보였다. 지금 생각해보면 눈뜬 장님처럼 시야가 어두웠다.

 

 

캡슐화는 객체지향 개발의 중요한 장점중에 하나지만, 나는 상속과 구성 폴리모피즘의 장점은 이해하면서 유독 캡슐화의 장점을 이해하지 못했다. 캡슐화의 정의와 캡슐화가 왜 좋은지 따로 공부해야 했다.

 

캡슐화의 의미는 관련있는 여러 정보들을 어떤 틀안에 담는 뜻이다. 어떤 틀안에 담는다는 것은 외부에 필요없는 정보들을 노출시키지 않고 숨기는 의미이다. 정보를 노출하지 않고 숨긴다는 것은 정보은닉의 정의와 같다.

 

캡슐화와 정보은닉을 하는 이유는 캡슐화를 통해 객체 안의 데이터가 다른 객체에게 잘못 조작되는 것을 막기 위해서이다. 예를 들어 박물관 객체에 유물 속성을 관람객 같은 다른 객체가 함부로 접근하지 못하게 보호 유리를 설치하는 것과 같다.

 

객체가 캡슐화가 잘 되었을 경우, 다른 객체가 속성에 직접 접근할수 없다. 그래서 속성이 보호되는 효과가 있다. 마치 친구에게 자신의 웹하드를 공개해주었는데 읽기 권한만 주고 쓰기 권한은 제한하여 자신의 파일을 보호하려는 경우와 비슷하다.

 

캡슐화를 통해 데이터가 다른 객체에게 잘못 조작되는 것을 막을 수 있다. 그래서 데이터의 주인 객체가 따로 계산하거나 확인한 값이 보존되는 효과가 있다. 이 점이 캡슐화의 장점을 이해하는데 중요한 단서이다.

 

public class MountainClimbingBoots { // 등산화 클래스

    private int durability = 60; // 내구성

    private boolean isGoreTex = true; // 고어텍스 유무

 

    public int getDurability() { // 내구성 속성 get접근 메소드

        if(isGoreTex) { // 고어텍스 기능이 맞으면

            return durability + 30; // 내구성에다가 30을 더해서 리턴한다.

        }

        return durability;

    }

}

 

위의 예는 캡슐화된 등산화 클래스이다. 등산화 클래스에는 내구성이란 속성이 있다. 내구성이란 속성은 기본적으로 60으로 지정되어 있다. 그리고 '클라이언트 객체'가 getter 메소드로 내구성 속성을 가지고 올때, 내구성 데이터를 조작하는 로직이 포함되어 있다.

 

위의 예제처럼 만약 고어텍스 기능이 있다면 내구성에 +30을 더해 80을 리턴하는 로직이 추가되어있다. 그런데 만약 내구성 속성이 public으로 오픈되어 있어서 클라이언트가 직접 속성 값을 가지고 오는 경우가 생긴다면 본래 등산화 객체 설계자가 의도했던 설계를 망가뜨리는 참사가 발생한다.

 

더 나아가 '클라이언트 객체'가 객체 구성요소의 쓰임새를 잘 모른채 직접 속성 값을 조작하는 경우가 생긴다면, 직접 조작한 '클라이언트 객체'뿐만 아니라 다른 '클라이언트 객체'까지 조작된 속성값을 써야 되기 때문에 피해가 확대되는 더 큰 참사도 발생될 수 있다. 마치 '산에서 떨어트린 담뱃재가 산불이 되는 경우'와 같다.

 

만약 '캡슐화'를 지키고 싶다면,

1. 속성은 private로 선언한다.

2. getter메소드를 만들어 get메소드로만 속성을 가져올수 있게 한다.

3. setter 메소드로 데이터를 제한적으로 조작하게 허용하거나, 아예 set메소드를 제거하여 외부로부터 속성값의 조작을 금지한다.

 

이렇게 하여 자신 객체의 설계 사상을 잘 모르는 다른 클라이언트 객체들의 '무자비한' 조작으로부터 자신의 속성와 설계 사상을 안전하게 보호한다. 이 사실을 깨닫게되면서 get/set메소드를 이용한 캡슐화 규칙을 지키기보다 속성을 조작하길 좋아했던 습관이 어떻게 잘못 되었는지 이해했다..

 

 

 

객체지향에서 캡슐화란 뜻은 다른 관점 에서도 사용한다. '객체지향 설계관점'에서도 사용한다. '변하는 기능을 분리해서 따로 캡슐화하라~' 는 객체지향 원칙을 쓰곤 한다.

 

캡슐화의 정의는 관련있는 여러 정보들을 어떤 틀안에 담는 것이다. 그렇다면 객체의 기능중에 일부를 그 객체로부터 분리하여 다른 '객체 그룹'으로 새롭게 구성하는 것도 캡슐화이다. 객체의 일부 기능을 다른 '객체 그룹'의 새로운 틀로 담았으니 이것도 캡슐화다. 이렇게 객체의 기능을 별도의 '객체 그룹' 틀로 분리하여 담으면, 프로그램의 유연성과 확장성이 좋아진다.

 

그림처럼 비슷한 개념의 기능을 하나의 객체에 모두 구현하려고 할 경우, 하나의 클래스에 온갖 로직이 섞여 객체가 지저분해진다. 그래서 유지보수 할때도 기능을 확장할 때도 쉽지 않다. 그러나 분리 가능한 기능을 별도의 '객체 그룹'으로 캡슐화 한다면, 하나의 클래스에 필요한 최소한의 기능만 깔끔하게 구현되고, 다른 모듈화된 기능은 원하는 기능만 선택할 수 있으므로 유지보수 하기 편하고 확장 및 변경도 쉽다.

 

 

정보은닉이란 개념은 캡슐화 개념과 거의 비슷하되, 정보은닉 규칙을 지키기 위해 캡슐화가 쓰인다. 캡슐화를 지키면 정보은닉 규칙도 함께 지킨다. 정보은닉은 외부에 필요없는 정보들을 노출시키지 않고 숨기는 것이다.

 

예를 들어 우리가 비행기 객체를 인식할 때 비행기를 구성하는 엔진, 전자제어장치 등의 복잡한 부품은 모른다. 다만 비행기로 '이동' 할 뿐이다. 사용자 입장에서는 '복잡한 속성'보다는 '중요 기능'만 알면 된다. 보통 세상이 이러므로 객체지향 개발에서도 '복잡한 부품'은 외부에 노출하지 않고, 객체 외부에서 인식하기 편리한 '중요 정보'만을 외부로 노출시켜야 한다.

 

이런 정보은닉을 잘 지원하는 객체지향 요소가 인터페이스이다. 인터페이스의 기능 명세를 설계하고 그 기능 명세의 구현은 하위 클래스에 맞긴다. 클라이언트는 인터페이스에만 의존되어 있다. 그렇게 하면 클라이언트는 인터페이스에 명시된 '기능의 용도'만 알기 때문에 정보은닉을 지킬 수 있다. 지금 설명에서도 '객체지향 보물지도' 그림을 떠올린다. '객체지향 보물지도'의 그림과 동일한 설명이다.

 

캡슐화와 정보은닉을 잘 활용하면,

1. 정밀한 제어가 필요한 속성이 다른 객체의 조작에 의해 훼손되는 경우를 막을 수 있다.

2. 객체끼리 최소한의 필요한 정보들만 의존한다.

3. 변하는 기능은 캡슐화를 통해 별도의 틀로 구성하여 기능의 수정과 확장 요구에 유연하게 대처할 수 있다.

4. 그 결과 객체간의 응집도가 높고 결합도가 낮아져 유연성과 확장성이 높아질 것이다.

 

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