자바에서는 접근제어자를 제공한다. 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거 나 제한할 수 있다.
public class Speaker {
private 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);
}
}
//필드에 직접 접근
System.out.println("volume 필드 직접 접근 수정");
speaker.volume = 200; //에러발생
speaker.showVolume(); //메소드는 접근가능
자바는 4가지 종류의 접근 제어자를 제공한다.
접근 제어자는 필드와 메서드, 생성자에 사용된다.
클래스 레벨에도 일부 접근 제어자를 사용할수 있다.
지역변수는 해당 블록안에서만 유효하므로 사용 의미도 없고, 사용할수 없다!
public class Speaker{ // 클래스 레벨(일부만 사용)
private int volume; // 필드
public Speaker(int volume){} // 생성자
public void volumUp(){} // 메서드
}
PublicClass.java
public class PublicClass {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
class DefaultClass1 {
}
class DefaultClass2 {
}
캡슐화는 객체지향 프로그래밍의 중요한 개념중 하나다. 캡슐화는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말한다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수있다.
쉽게말해, 속성과 기능을 하나로 묶고 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것이다.
어떤것을 숨기고 어떤것 을 노출해야 할까?
데이터를 숨겨라
객체에는 속성(데이터)과 기능(메서드)이 있다. 캡슐화에서 가낭 필수로 숨겨야 하는 것은 속성(데이터)이다. 데이터를 다루는 모든 로직이 무시되지 않도록 외부에서 직접 접근하는 것을 막아야한다.
객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야한다.
기능을 숨겨라
객체의 기능중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능들이 있다. 이런 기능도 모두 감추는 것이 좋다. 사용자 입장에서 꼭 필요한 기능만 외부에 노출하자. 나머지 기능은 모두 내부로 숨기자.
아래와 같이 캡슐화한 클래스를 보자.
BankAccount 를 사용하는 입장에서는 입금, 출금, 잔고에 해당하는 메서드만 알면 된다. 데이터 필드 balance, 입력금액을 검증하는 내용(isAmountValid)은 내부에 숨어있다.
접근제어자와 캡슐화를 통해 데이터를 안전하게 보호하고, BankAccount를 사용하는 개발자 입장에서 해당 기능을 사용하는 복잡도도 낮출 수 있다.
public class BankAccount {
private int balance;
public BankAccount() {
balance = 0;
}
// public 메서드: deposit
public void deposit(int amount) {
if (isAmountValid(amount)) {
balance += amount;
} else{
System.out.println("유효하지 않은 금액입니다.");
}
}
// public 메서드: withdraw
public void withdraw(int amount) {
if (isAmountValid(amount) && balance - amount >= 0) {
balance -= amount;
} else {
System.out.println("유효하지 않음 금액이거나 잔액이 부족합니다.");
}
}
// public 메서드: getBalance
public int getBalance() {
return balance;
}
private boolean isAmountValid(int amount) {
// 금액이 0보다 커야함
return amount > 0;
}
}
public void addItem(Item item) {
if (itemCount >= items.length) {
System.out.println("장바구니가 가득 찼습니다.");
return; //여긴 검증로직일 뿐이므로, 메인 로직을 else에 넣는 대신 if 문 내에 return 처리하면 좋다.
}
// items[itemCount++] = item 아래 두줄을 합쳐도 무방. 후위 연산자는 우선순위가 낮으므로 대입 먼저수행 후 ++이 수행된다.
items[itemCount] = item;
itemCount++;
}