Java 2-7 : 객체지향 PART 1 - 캡슐화

토스트빌런·2025년 3월 12일

캡슐화(Encapsulation)란?

📚캡슐화란 무엇인지 알아봅시다.

  • 객체의 정보를 외부에서 직접 접근하지 못하게 보호하는 개념입니다.
  • 캡슐처럼 감싸서 내부를 보호하고 외부로부터 내용물을 숨기고 있는 모습에서 유래됐습니다.
  • 클래스 혹은 객체의 캡슐화는 접근제어자 를 통해서 구현할 수 있습니다.

캡슐화가 왜 필요할까요?

📚캡슐화가 필요한 이유를 알아봅시다.

  • 예를 들어, 누군가가 여러분의 이름이나 나이를 마음대로 변경할 수 있다면 어떨까요?
  • 가문의 비밀처럼 외부에 노출하고 싶지 않은 정보가 있을 수도 있습니다.
  • 캡슐화를 통해 이러한 정보를 보호하고 필요한 경우에만 안전하게 접근할 수 있도록 합니다.
  • 캡슐화를 구현하기 위해서는 접근제어자 를 이해해야합니다.

접근제어자(Access Modifier)

📚접근제어자가 무엇인 알아봅시다.

  • 접근제어자는 클래스, 변수, 메서드, 생성자의 접근 범위를 제한하는 키워드입니다.
  • 캡슐화 구현을 위해 사용됩니다.

접근제어자클래스 내부패키지 내부상속한 클래스전체 공개
public
protected
default
private

접근제어자 활용 예시(public, private)

📚접근제어자 활용 예시를 살펴봅시다.

public class Person {      // ✅ 외부에서 접근 불가 

    public String name;    // ✅ 외부에서 접근 불가 
    private String secret; // ❌ 외부에서 접근 불가 
    
    public Person() {}    // ✅ 외부에서 접근 불가 

    public void methodA() {}  // ✅ 외부에서 접근 가능
    private void methodB() {} // ❌ 외부에서 접근 불가
}
Person person = new Person(); // ✅ 접근가능 생성자가 public
person.name;                  // ✅ 접근가능 변수가 public
person.secret;                // ❌ 접근불가능 변수가 private
person.methodA();             // ✅ 접근가능 메서드가 public
person.methodB();             // ❌ 접근불가능 메서드가 private

데이터 접근 - 게터(Getter)와 세터(Setter)

💡캡슐화가 된 데이터에 접근 방법

  • 캡슐화가 잘 적용된 클래스는 내부 데이터를 private 으로 보호하고 있습니다.
  • 데이터 조회나 변경이 필요한 경우 안전한 접근방법이 필요합니다.
  • 그 역할을 수행하는 메서드가 바로 게터(Getter)세터(Setter) 입니다.

💡게터(Getter) 활용법

  • 데이터를 안전하게 접근하기 위해 사용됩니다.
public class Person { 
    private String secret;
    
    public String getSecret() {
		    return this.secret; // ✅ 객체의 secret 속성 반환
    }
}
public class Main {
    public static void main(String[] args) {
		    Person p1 = new Person();
		    p1.secret; // ❌ 직접 접근 불가능
		    String newSecret = p1.getSecret(); // ✅ 게터를 활용해 접근가능
    }
}

💡세터(Setter) 활용법

  • 데이터를 안전하게 설정/변경하기 위해 사용됩니다.
public class Person { 
    private String secret;
    
    public void setSecret(String secret) {
		    this.secret = secret; // ✅ secret 속성 설정 및 변경
    }
}
public class Main {
    public static void main(String[] args) {
		    Person p1 = new Person();
		    p1.secret = "password"; // ❌ 직접접근, 변경 불가능
		    p1.setSecret("newPassword"); // ✅ 세터를 활용해 접근, 변경가능
    }
}

무분별한 세터가 무엇일까?

시나리오1: 핵시설을 관리하는 개발자

💡시나리오1: 핵시설을 관리하는 개발자

public class DataStore {
    String store;
}
DataStore dataStore = new DataStore();
dataStore.store = "B"; // ❌ 의문의 핵폭발 발생!
  • 여러분은 핵시설을 관리하는 개발자입니다.
  • store 변수에 문자열 "B" 가 들어오면 핵폭발이 발생하는 위험한 시스템을 다루고 있습니다.
  • 그런데 동료 개발자가 실수로 "B"를 입력해서 핵폭발이 일어난다면 그 책임은 누구에게 있을까요? → 여러분의 책임입니다. (🔥 당신의 코드가 이를 방지하지 못했기 때문!)

1️⃣해결책1: 접근제어자로 데이터 보호

public class DataStore {

    private String store; 
}
DataStore dataStore = new DataStore();
dataStore.store = "B"; // ❌ 컴파일 오류! 직접 접근 불가!
  • store 변수에 private 접근제어자를 설정하면 외부에서 직접 변경할 수 없게 되어 핵폭발을 막을 수 있습니다. 하지만 문제가 하나 있습니다!
  • 이제 데이터를 아예 사용할 수 없게 되었습니다! 어떤 데이터도 저장할 수 없다면 이 클래스는 쓸모가 없어집니다.

2️⃣해결책2: 세터 추가

public class DataStore {
    private String store; 
    
    public void setStore(String data) {
        this.store = data;
    }
}
DataStore dataStore = new DataStore();
dataStore.setStore("B"); // ❌ 다시 핵폭발 발생!
  • store 변수는 private 이지만 세터를 통해 외부에서 값을 변경할 수 있게 되었습니다.
  • 하지만... 다시 "B"를 넣으면 핵폭발이 일어납니다!

💡무분별한 세터 사용사례

접근을 막아 놓고 다시 세터로 외부에 노출한다면, 접근제어를 하는 의미가 사라집니다.

이런 경우 무조건 세터를 만드는 것이 아니라 올바른 데이터만 저장될 수 있도록 제한해야 합니다.

3️⃣해결책 3: 안전한 데이터 설정 로직을 추가

public class DataStore {
    private String store; 
    
    public void setStore(String data) {
        if ("B".equals(data)) {
            System.out.println("❌ 'B'는 입력할 수 없습니다!");
        } else {
		        this.store = data;
        }
    }
}
  • "B"가 입력되면 안내메시지를 제공합니다.(핵폭발을 방지합니다.)
  • 이제 데이터는 안전하게 저장되고 원하지 않는 값이 들어오는 것도 막을 수 있습니다!

시나리오2: 로보트를 걷게 만들자

💡시나리오2: 로보트를 걷게 만들자

public class Robot {

    private boolean leftLeg;
    private boolean rightLeg;
}
  • 여러분은 로봇 개발자입니다.
  • 이제 로봇이 걷도록 구현해야합니다.
  • 왼쪽 → 오른족 다리 순으로 전기를 공급(true) 해줘야합니다.

1️⃣해결책 1: 세터 추가

public class Robot {
    private boolean leftLeg;
    private boolean rightLeg;

    public void setLeftLeg(boolean power) {
        this.leftLeg = power;
    }

    public void setRightLeg(boolean power) {
        this.rightLeg = power;
    }
}
Robot robot = new Robot();
robot.setLeftLeg(true);  // ✅ 왼쪽 다리 움직임
robot.setRightLeg(true); // ✅ 오른쪽 다리 움직임
  • 왼쪽 다리와 오른쪽 다리를 각각 움직일 수 있도록 setLeftLeg()setRightLeg() 메서드를 만들었습니다.
  • 클래스를 사용할 때 잘 활용할 수 있습니다. 하지만 고려해야 할 속성들이 많아진다면 각 각의 세터를 다루는 게 쉬운 일일까요?
public class Robot {
    private boolean leftLeg; // 세터필요
    private boolean rightLeg;// 세터필요
    private boolean leftArm; // 세터필요
    private boolean rightArm;// 세터필요
    private boolean leftEye; // 세터필요
    private boolean rightEye;// 세터필요
}
Robot robot = new Robot();
robot.setLeftLeg(true);  // ✅ 왼쪽 다리 움직임
robot.setLeftArm(true); // ⚠️ 왼쪽 팔을 먼저 움직이면 균형이 깨짐
robot.setRightLeg(true); // ✅ 오른쪽 다리 움직임
...
  • 속성이 많아질수록 세터를 호출하는 코드가 점점 복잡해지고 유지보수가 어려워집니다.

2️⃣해결책 2: 걷기 라는 동작을 직접 제공하기

public class Robot {
    private boolean leftLeg;
    private boolean rightLeg;
    private boolean leftArm;
    ...

    public void walk(boolean power) {
        System.out.println("🚶 왼쪽 다리 앞으로!");
        leftLeg = power;

        System.out.println("🚶 오른쪽 다리 앞으로!");
        rightLeg = true;
        
        ...
    }
}
  • 이제 걷는 동작이 하나의 메서드로 정의되었습니다.
  • 모든 요소를 조작하는 순서가 walks() 안에서 관리됨으로 사용이 간단해졌습니다.

💡단순한 세터들을 남용하는 것보다 의미 있는 동작을 제공하는 세터를 만드는 것이 무분별한 세터를 방지할 수 있습니다.

0개의 댓글