본문 바로가기

Java/Chapter 09. 정보 은닉 그리고 캡슐화

[Java] 09.03 - 캡슐화(Encapsulation)

들어가며

 캡슐화는 정보 은닉과
더불어

 객체지향 기반의 클래스
설꼐에 있어

 가장 기본이면서 중요한
원칙 중 하나이다.

 캡슐화는 문법적인 내용은
아니다.

 클래스 안에 '무엇을 넣을까'
에 대한 이론을 제시하는
내용이다.

 

콘택 600과 캡슐화

 코감기 약 중에서 대충 매우
유명한 약이 있었다.

 아주 오래 전에 나온 약인데,
지금도 이름이

 '콘택600'에서 '콘택골드'로
변경되어 판매되고 있다.

 그리고 무엇보다 그 생김새가
다른 약들과는 확연히 구분된다.

 모든 약들이 그러하지만 이
약은 갭슐화가 잘 이뤄진 예로
볼 수 있다.

 이 약이 갖는 기능은 다음과
같다.

  • 흐르는 '콧물'을 멎게 하는 기능
  • 멈추지 않는 '재채기'를 가라앉혀 주는 기능
  • 답답한 '코막힘' 상태를 완화시켜 주는 기능

 그런데 만약에 이 약이 '콧물용',
'재채기용', '코막힘용' 캡슐로
나눠져 있다면,

 그래서 코감기에 걸렸을 때
총 3알의 캡슐을 복용해야
한다면,

 이는 캡슐화가 잘 이뤄지지
않은 예가 된다.

 그러나 이 약은 다음 하나의
목적을 달성하기 위해서

 하나의 캡슐 안에 모든 것을
적절히 담아 놓았다.

코감기에 동반되는 모든 증상을 완화시키는 강력한 처방.

 따라서 이 약은 캡슐화가
잘 된 예로 볼 수 있다.

 정리하면 캡슐화는 다음과
같이 정의할 수 있다.

하나의 목적을 이루기 위해 관련 있는 모든 것을
하나의 캡슐에 담아 두는 것.

 물론 객체지향 관점에서 위의
캡슐은 클래스에 해당한다.

 즉 위 문장은 다음과 같이
다시 쓸 수 있다.

하나의 목적을 이루기 위해 관련있는 모든 것을
하나의 클래스에 담아 두는 것.

 무조건 많이 담는다고 해서
캡슐화가 아니다.

 부족해도 안 되고 넘쳐도
문제가 된다.

 그리고 상황 및 목적에 따라
동일한 이름의 클래스에도

 담기는 내용이 달라진다.

그래서 캡슐화를 잘하려면 다양한 상황에서의 연습과 경험이 필요하다.

 그렇다면 캡슐화가 중요한
이유는 무엇인가?

 클래스들을 적절히 캡슐화
시키면 프로그램이 간결해진다.

 반면 캡슐화를 적절히 시키지
못하면

 프로그램이 복잡해질 뿐만
아니라

 구현 과정에서 문제가 생겨
더 이상 진행이 어려워지는
경우도 있다.

 

캡슐화가 이뤄지지 않은 예제

 다음의 콘택600의 복용 과정을
시뮬레이션 한 예제이다.

 그냥 그렇다고 하자.

 단, 캡슐화가 적절히 이뤄지지
않은 약의 상태를 보여준다.

 이로 인해 어떠한 불편함이
코드상에서 발생할 수 있는지
확인해보자.

class SinlivelCap{   // 콧물 디버프 해제
    void take(){
        System.out.println("『콧물』 상태 이상이 해제되었습니다.");
    }
}

class SneezeCap{    // 재채기 디버프 해제
    void take(){
        System.out.println("『재채기』 상태 이상이 해제되었습니다.");
    }
}

class SnuffleCap{   // 코막힘 디버프 해제
    void take(){
        System.out.println("『코막힘』 상태 이상이 해제되었습니다.");
    }
}

class ColdPatient{
    void takeSinivelCap(SinlivelCap cap){
        cap.take();
    }
    void takeSneezeCap(SneezeCap cap){
        cap.take();
    }
    void takeSnuffleCap(SnuffleCap cap){
        cap.take();
    }
}

class BadEncapsulation {
    public static void main(String[] args){
        ColdPatient suf = new ColdPatient();

        // 콧물 캡슐 복용
        suf.takeSinivelCap(new SinlivelCap());

        // 재채기 캡슐 복용
        suf.takeSneezeCap(new SneezeCap());

        // 코막힘 캡슐 복용
        suf.takeSnuffleCap(new SnuffleCap());
    }
}
/*
실행 결과
『콧물』 상태 이상이 해제되었습니다.
『재채기』 상태 이상이 해제되었습니다.
『코막힘』 상태 이상이 해제되었습니다.
*/

 위와 같은 클래스 구성에서는

 프로그래머가 다음 사실을
인지하고 있어야 한다.

코감기 상태 이상 해제를 위해
SinivelCap, SneezeCap, SnuffleCap
인스턴스를 생성해야 한다.

 더불어 어디까지나 가정이지만,

 약 복용에 있어서 다음 사항도
지켜야 한다고 가정해보자.

 실제 프로그래밍에서 이러한 성격의 제약사항이 자주 등장한다.

약은 SinivelCap, SneezeCap, SnuffleCap의 순으로
복용해야 한다.

 지금 언급한 것이 캡슐화가
정상적으로 이뤄지지 않았을
때의 문제점이다.

 코감기 약 복용이라는 한 가지
목적 달성을 위해

 프로그래머가 알아야 할 것도
많고

 코드상에서의 약 복용 과정
또한 복잡한다.

 그렇다면 캡슐화가 잘 이뤄졌다면
상황은 어떻게 달라지는가?

 

캡슐화가 잘 이뤄진 예제 : 하나의 클래스로 캡슐화 완성하기

 다음은 캡슐화가 잘 이뤄진
사례를 보여주는 예제이다.

 앞서 보인 예제와 내용은
같지만 코드의 구성에서
차이가 난다.

class SinusCap{
    void sniTake(){
        System.out.println("『콧물』 상태 이상이 해제되었습니다.");
    }
    void sneTake(){
        System.out.println("『재채기』 상태 이상이 해제되었습니다.");
    }
    void snuTake(){
        System.out.println("『코막힘』 상태 이상이 해제되었습니다.");
    }
    
    void take(){    // 약의 복용 방법 및 순서가 담긴 메소드
        sniTake();
        sneTake();
        snuTake();
    }
}

class ColdPatient{
    void takeSinus(SinusCap cap){
        cap.take();
    }
}

class OneClassEncapsulation {
    public static void main(String[] args){
        ColdPatient suf = new ColdPatient();
        suf.takeSinus(new SinusCap());
    }
}
/*
실행 결과
『콧물』 상태 이상이 해제되었습니다.
『재채기』 상태 이상이 해제되었습니다.
『코막힘』 상태 이상이 해제되었습니다.
*/

 위 예제는 앞서 보인 예제와
결과면에서는 동일하다.

 그러나 SinusCap 클래스 안에

 코감기에 관련된 모든 내용이
캡슐화되었다.

 그래서 이의 복용과 관련된
코드를 담고 있는

 ColdPatient 클래스와 main
메소드가 이전 예제와 비교하여
매우 간단해졌다.

 즉 감기약 복용을 위해 알아야
할 내용은 다음이 전부이다.

SinusCap 인스턴스를 생성하고
take 메소드를 호출한다.

 다시 한번 언급하지만,

 캡슐화는 절대로 클래스를
크게 만들라는 뜻이 아니다.

 해당 클래스와 관련있는
내용을

 하나의 클래스에 모두 담되

 부족하게 담아서도, 넘치게
담아서도 안 된다는 뜻이다.

 

캡슐화가 잘 이뤄진 또 다른 예제 : 포함 관계로 캡슐화 완성하기

 한 클래스가 다른 클래스의
인스턴스를 멤버로 가질 수
있는데,

 이러한 관계를 가리켜 '포함
관계'라 한다.

 그리고 이러한 포함 관계는
캡슐화를 완성하는 과정에서도
사용이 된다.

 예를 들어서 앞서 캡슐화가
완성되지 않은 예에서

 다음 클래스드르을 정의한
바 있다.

SinivelCap, SneezeCap, SnuffleCap

 따라서 이들을 인스턴스 멤버로
갖는

 다음과 같은 클래스를 정의할
수 있으며

 이러한 방식으로도 캡슐화를
완성시킬 수 있다.

class SinusCap{
    SinlivelCap siCap= new SinlivelCap();
    SneezeCap szCap= new SneezeCap();
    SnuffleCap sfCap= new SnuffleCap();
    
    void take(){
        siCap.take(); szCap.take(); sfCap.take();
    }
}

 그리고 위와 같은 방식의
캡슐화 결과를

 예제에 적용한 결과는
다음과 같다.

class SinlivelCap{   // 콧물 디버프 해제
    void take(){
        System.out.println("『콧물』 상태 이상이 해제되었습니다.");
    }
}

class SneezeCap{    // 재채기 디버프 해제
    void take(){
        System.out.println("『재채기』 상태 이상이 해제되었습니다.");
    }
}

class SnuffleCap{   // 코막힘 디버프 해제
    void take(){
        System.out.println("『코막힘』 상태 이상이 해제되었습니다.");
    }
}

class SinusCap{
    SinlivelCap siCap= new SinlivelCap();
    SneezeCap szCap= new SneezeCap();
    SnuffleCap sfCap= new SnuffleCap();

    void take(){
        siCap.take(); szCap.take(); sfCap.take();
    }
}

class ColdPatient{
    void takeSinus(SinusCap cap){
        cap.take();
    }
}

class BadEncapsulation {
    public static void main(String[] args){
        ColdPatient suf = new ColdPatient();
        suf.takeSinus(new SinusCap());
    }
}
/*
실행 결과
『콧물』 상태 이상이 해제되었습니다.
『재채기』 상태 이상이 해제되었습니다.
『코막힘』 상태 이상이 해제되었습니다.
*/

참고 및 출처

윤성우의 열혈 Java 프로그래밍
국내도서
저자 : 윤성우
출판 : 오렌지미디어 2017.07.05
상세보기