'김영한의 실전 자바 - 기본편' 강의를 들으면서 복습할만한 내용을 정리하였다.
패키지(package)는 이름 그대로 물건을 운송하기 위한 포장 용기나 그 포장 묶음을 뜻한다. 해당 패키지 안에 관련된 자바 클래스들을 넣으면 된다.
패키지는 보통 다음과 같이 계층 구조를 이룬다.
이렇게 하면 다음과 같이 총 3개의 패키지가 존재한다.
a
, a.b
, a.c
계층 구조상 a
패키지 하위에 a.b
패키지와 a.c
패키지가 있다.
그런데 이것은 우리 눈에 보기에 계층 구졸르 이룰 뿐이다. a
패키지와 a.b
, a.c
패키지는 서로 완전회 다른 패키지이다.
따라서 a
패키지의 클래스에서 a.b
패키지의 클래스가 필요하면 import
해서 사용해야 한다. 반대도 물론 마찬가지이다.
정리하면 패키지가 계층 구조를 이루더라도 모든 패키지는 서로 다른 패키지이다.
물론 사람이 이해하기 쉽게 계층 구조를 잘 활용해서 패키지를 분류하는 것은 좋다. 참고로 카테고리는 보통 큰 분류에서 세세한 분류로 점점 나누어진다. 패키지도 마찬가지이다.
자바는 public, private
같은 접근 제어자(access modifier)를 제공한다. 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있다.
이런 접근 제어자가 왜 필요할까?
스피커에 들어가는 소프트웨어를 개발하는 개발자라고 생각해보자. 스피커의 음량은 절대로 100을 넘으면 안된다는 요구사항이 있다.(100을 넘어가면 스피커가 고장난다고 가정하자)
Speaker
객체package access;
public class Speaker {
int volume;
Speaker(int volume) {
this.volume = volume;
}
void volumeUp() {
if (volume >= 100) {
System.out.println("음량을 증가할 수 없습니다. 최대 음량입니다.");
}
else {
volume += 10;
System.out.println("음량을 10 증가합니다.");
}
}
void volumeDown() {
volume -= 10;
System.out.println("volumeDown 호출");
}
void showVolume() {
System.out.println("현재 음량:" + volume);
}
}
위의 Speaker
객체에서 volumeUp
을 통해 볼륨을 올린다. 이 메서드를 통해 볼륨이 100이상 넘어가지 않도록 제어하고 있다.
새로운 개발자가 이 Speaker
객체를 사용한다고 해보자. 이때 새로운 개발자는 스피커가 100이상 넘어가면 스피커가 망가지는 것을 모른다. 새로운 개발자는 볼륨이 100이상 올라가지 않는 것을 보고 Speaker
클래스를 살펴본다. 이때 volume
필드를 직접 사용할 수 있기 때문에 새로운 개발자는 volume
필드에 직접 접근해 값을 200으로 설정하고 이 코드를 실행한 순간 스피커는 고장난다.
Speaker
객체를 사용하는 사용자는 Speaker
의 volume
필드와 메서드에 모두 접근할 수 있다.
앞서 volumeUp()
과 같은 메서드를 만들어서 음량이 100을 넘지 못하도록 제약을 걸어놨지만 소용이 없다. 왜냐하면 Speaker
를 사용하는 입장에서는 volume
필드에 직접 접근해서 원하는 값을 설정할 수 있기 때문이다.
이런 문제를 근본적으로 해결하기 위해서는 volume
필드의 외부 접근을 막을 수 있는 방법이 필요하다.
만약 Speaker
클래스를 개발하는 개발자가 처음부터 private
을 사용해서 volume
필드의 외부 접근을 막아두었다면 어떠했을까? 새로운 개발자도 volume
필드에 직접 접근하지 않고, volumeUp()
과 같은 메서드를 통해서 접근했을 것이다. 결과적으로 Speaker
가 망가지는 문제는 발생하지 않았을 것이다.
참고 : 좋은 프로그램은 무한한 자유도가 주어지는 프로그램이 아니라 적절한 제약을 제공하는 프로그램이다.
private
: 모든 외부 호출을 막는다.
default
(pacakage-private) : 같은 패키지안에서 호출은 허용한다.
protected
: 같은 패키지안에서 호출을 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
public
: 모든 외부 호출을 허용한다.
접근 제어자를 명시하지 않으면 같은 패키지 안에서 호출을 허용하는 default
접근 제어자가 적용된다.
default
라는 용어는 해당 접근 제어자가 기본값으로 사용되기 때문에 붙여진 이름이지만, 실제로는 package-private
이 더 정확환 표현이다. 왜냐하면 해당 접근 제어자를 사용하는 멤버는 동일한 패키지 내의 다른 클래스에서만 접근이 가능하기 때문이다.
접근 제어자는 필드와 메서드, 생성자에 사용된다.
추가로 클래스 레벨에도 일부 접근 제어자를 사용할 수 있다.
private
은 나의 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없다.
default
는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.
protected
는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없다.
public
은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다.
클래스 레벨의 접근 제어자 규칙
public, default
만 사용할 수 있따.private, protected
는 사용할 수 없다.public
클래스는 반드시 파일명과 이름이 같아야 한다.public
클래스는 하나만 등장할 수 있다.default
접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.캡슐화(Encapsulation)는 객체 지향 프로그래밍의 중요한 개념 중 하나다. 캡슐화는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말한다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있다.
캡슐화는 쉽게 이야기해서 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것이다.
객체에는 속성(데이터)과 기능(메서드)이 있다. 캡슐화에서 가장 필수로 숨겨야 하는 것은 속성(데이터)이다. Speaker
의 volume
을 떠올려 보자. 객체 내부의 데이터를 외부에서 함부로 접근하게 두면, 클래스 안에서 데이터를 다루는 모든 로직을 무시하고 데이터를 변경할 수 있다. 결국 모든 안전망을 다 빠져나가게 된다. 따라서 캡슐화가 깨진다.
우리가 자동차를 운전할 때 자동차 부품을 다 열어서 그 안에 있는 속도계를 직접 조절하지 않는다. 단지 자동차가 제공하는 액셀 기능을 사용해서 액셀을 밟으면 자동차가 나머지는 다 알아서 하는 것이다.
우리가 일상에서 생각할 수 있는 음악 플레이어를 떠올려보자. 음악 플레이어를 사용할 때 그 내부에 들어있는 전원부나, 볼륨 상태의 데이털르 직접 수정할 일이 있을까? 우리는 그냥 음악 플레이어의 켜고, 끄고, 볼륨을 조절하는 버튼을 누룰 뿐이다. 그 내부에 있는 전원부나, 볼륨의 상태 데이터를 직접 수정하지 않는다. 전원 버튼을 눌렀을 때 실제 전원을 받아서 전원을 켜는 것은 음악 플레이어의 일이다. 불륨을 높였을 때 내부에 있는 볼륨 장치들을 움직이고 불륨 수치를 조절하는 것도 음악 플레이어가 스스로 해야하는 일이다. 쉽게 이야기해서 우리는 음악 플레이어가 제공하는 기능을 통해서 음악 플레이어를 사용하는 것이다. 복잡하게 음악 플레이어의 내부를 까서 그 내부 데이터까지 우리가 직접 사용하는 것은 아다.
객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 한다.
객체의 기능 중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능들이 있다. 이런 기능도 모두 감추는 것이 좋다. 우리가 자동차를 운전하기 위해 자동차가 제공하는 복잡한 엔진 조절 기능, 배기 기능까지 우리가 알 필요는 없다. 우리는 단지 액셀과 핸들 정도의 기능만 알면 된다.
만약 사용자에게 이런 기능까지 모두 알려준다면, 사용자가 자동차에 대해 너무 많은 것을 알아야 한다.
사용자 입장에서 꼭 필요한 기능만 외부에 노출하자. 나머지 기능은 모두 내부로 숨기자
정리하면 데이터는 모두 숨기고, 기능은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화이다.