Skip to content

객체지향 프로그램의 규칙 - 캡슐화

캡슐화가 무엇인가?

캡슐화란, 객체의 상태 값의 읽기 및 수정을 외부로 부터 숨기는 개념이다.

왜 필요한데?

이전 게시물의 객체의 예제를 가져와 보았다.

needtoEncapsulation.java
{
    Animal shark = new Animal(); // shark 객체 생성

    // 객체의 상태 설정
    shark.age = 30;
    shark.height = 183;

    // 객체의 행위 실행
    shark.walk();
}

외부의 5줄과 6줄에서 변수의 상태를 직접 변경하였다. 변수의 상태를 직접 변경하면 발생할 수 이슈는 객체를 의도하지 않은 방향으로 사용할 수 있기 때문이다

아래의 예제처럼 캡슐화를 사용하지 않을경우 age 값 및 height 값을 음수로 설정하여 개발자가 의도하지 않게 사용이 가능하다.

needtoEncapsulation2.java
{
    Animal shark = new Animal(); // shark 객체 생성

    // 개발자가 의도하지 않은대로 값을 변경
    shark.age = -3020;
    shark.height = -1012020;

    // 객체의 행위 실행
    shark.walk();
}

이를 해결하기 위해 필요한 것이 캡슐화이며, 캡슐화는 여러가지 형태로 표현 가능하다.

상태 값을 직접 변경하도록 제어하기 위해 private 키워드를 추가하였다. 이렇게 되면 외부에서 변수에 직접 접근이 불가능하다.

addPrivateKeyword.java
class Animal{
    // 데이터 부분 : 객체의 상태(state)를 정의한다.
    // private 키워드를 추가하여 위의 사용을 제한하자.
    private int age;
    private int height;

    // 메소드 부분 : 객체의 행위(action)를 정의한다.
    void walk(){
        System.out.println("walk");
    }
}

그렇다면, 어떻게 외부에서 ageheight를 변경할 수 있을까? 한 가지 방법으로는 gettersetter메소드를 정의하여 값 변경 시 올바르지 않은 값으로 수정하지 못하도록 제어하는 것이다.

addPrivateKeyword.java
class Animal{
    // 데이터 부분 : 객체의 상태(state)를 정의한다.
    // private 키워드를 추가하여 위의 사용을 제한하자.
    private int age;
    private int height;

    // 메소드 부분 : 객체의 행위(action)를 정의한다.
    void walk(){
        System.out.println("walk");
    }

    public int getAge(){
        return this.age;
    }

    public void setAge(int age){
        if(age >= 0){
            this.age = age;
        }
        else { 
            this.age // set default value is 0
        }
    }
}

17 줄에서 age를 변경 할 때 setter 메소드를 통해 값을 검증하여 의도하지 않은 값을 제어한다

계속 setter메소드로 초기화 하라는거냐?

각각의 맴버 변수의 값을 처음에 설정하기 위해 setter 메소드를 사용해야 하는 걸까? 한 번에 객체를 생성할 때 초기화 할 수 있는 방법이 있을까?

생성자(Constructor)를 이용하면, 객체를 생성하는 동시에 맴버 변수의 값을 초기화 할 수 있다.

아래는 생성자를 정의하는 예제이다.

makeConstructor.java
class Animal{
    // 데이터 부분 : 객체의 상태(state)를 정의한다.
    // private 키워드를 추가하여 위의 사용을 제한하자.
    private int age;
    private int height;


    // 생성자 정의 부분, 객체 명과 파라미터를 정의하면 된다.
    Animal(int age, int height){
        this.age = age;
        this.height = height;
    }
    // 메소드 부분 : 객체의 행위(action)를 정의한다.

}

생성자를 만들었으면 이제부터 외부에서 아래와 같이 사용이 가능하다

makeObject.java
1
2
3
{
    Animal shark = new Animal(-30300,-128388); // 맴버 변수가 제어가 되지 않은 모습이다.
}

명심하자 생성자를 만들 때도 외부에서 사용 하기 때문에 통제가 불가능하다. 각 맴버변수를 초기화 할 때, setter함수를 이용해주어야 한다.

makeConstructor-fix.java
class Animal{
    // 데이터 부분 : 객체의 상태(state)를 정의한다.
    // private 키워드를 추가하여 위의 사용을 제한하자.
    private int age;
    private int height;


    // 생성자 정의 부분, 객체 명과 파라미터를 정의하면 된다.
    Animal(int age, int height){
        // setter를 통해 객체의 값을 제어하자
        this.setAge(age); 
        this.setHeight(height);
    }
    // 메소드 부분 : 객체의 행위(action)를 정의한다.
    void setAge(int age){
        // 중략
    }
    void setHeight(int height){
        // 중략
    }
}

new Animal();이 더이상 안먹히는데?

모든 클래스는 default 생성자가 존재한다.

default 생성자는 객체를 생성할 때, 아무런 행위를 하지 않고 생성자를 정의하기 전까지 존재한다.

즉, 위의 코드는 생성자를 정의했기 때문에 더이상 new Animal();로 객체를 생성할 수 없어진 것이다.

만약 default 생성자를 사용하기 위해선 아래와 같이 새로 정의를 해야한다. default 생성자를 정의할 때, 이미 정의한 생성자를 이용하여 만드는 것이 좋은 습관이다.

makeConstructor-fix.java
class Animal{
    // 데이터 부분 : 객체의 상태(state)를 정의한다.
    // private 키워드를 추가하여 위의 사용을 제한하자.
    private int age;
    private int height;


    Animal(){
        this(0, 0); // new Animal(0, 0)과 동일
    }

    // 생성자 정의 부분, 객체 명과 파라미터를 정의하면 된다.
    Animal(int age, int height){
        // setter를 통해 객체의 값을 제어하자
        this.setAge(age); 
        this.setHeight(height);
    }
    // 메소드 부분 : 객체의 행위(action)를 정의한다.
    void setAge(int age){
        // 중략
    }
    void setHeight(int height){
        // 중략
    }
}

this(0, 0) 부분이 서로 다른 메모리 주소를 가진 객체를 만들어지는 지 확인하기 위해 jshell로 테스트 한 결과 아래와 같이 주소가 다른 객체가 생성되었음을 확인할 수 있었다.

output
jshell> Animal shark = new Animal();
shark ==> Animal@52cc8049

jshell> Animal lion = new Animal();
lion ==> Animal@27973e9b

Comments