객체지향 프로그램의 규칙 - 캡슐화
캡슐화가 무엇인가?
캡슐화란, 객체의 상태 값의 읽기 및 수정을 외부로 부터 숨기는 개념이다.
왜 필요한데?
이전 게시물의 객체의 예제를 가져와 보았다.
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");
}
}
|
그렇다면, 어떻게 외부에서 age
와 height
를 변경할 수 있을까? 한 가지 방법으로는 getter
및 setter
메소드를 정의하여 값 변경 시 올바르지 않은 값으로 수정하지 못하도록 제어하는 것이다.
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 |
---|
| {
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로 테스트 한 결과 아래와 같이 주소가 다른 객체가 생성되었음을 확인할 수 있었다.
outputjshell> Animal shark = new Animal();
shark ==> Animal@52cc8049
jshell> Animal lion = new Animal();
lion ==> Animal@27973e9b