캡슐화(Encapsulation)

김헌규·2025년 4월 26일
post-thumbnail

먼저 캡슐화를 쉽게 이해하기 위해서는 접근 제어자라는 것을 먼저 이해하고 가면 좋다.

🧩 접근 제어자

접근 제어자란 클래스, 메서드, 필드 등 객체 지향 프로그래밍의 구성 요소에 대해 외부에서 접근할 수 있는 범위를 설정하는 키워드이다. 코드의 캡슐화를 지원하고, 데이터 보호와 프로그램의 안정성을 높이는 데 사용된다.

내가 주로 사용하는 자바에서는 네 가지의 접근 제어자를 제공한다.


🔒 private

private 접근 제어자를 사용하면 같은 클래스 내에서만 접근할 수 있다. 그래서 보통은 멤버 변수에 private을 사용하여 메소드를 통해서만 멤버 변수를 조작하는 방식을 많이 사용한다.

같은 클래스에서만 접근 허용!

public class SameClass {
    private String var = "같은 클래스만 허용"; // private 필드

    private String getVar() {                  // private 메소드
        return this.var;
    }
}

🌍 public

public 접근 제어자를 사용하면 해당 객체를 사용하는 프로그램 어디에서나 직접 접근이 가능하다. 주로 외부에서 자유롭게 호출해야 하는 메소드나, 공용 API로 제공되는 필드에 사용된다. 멤버 변수를 직접 public으로 노출하는 것은 권장되지 않으며, 보통은 private 변수에 대한 getter나 setter 메소드를 public으로 제공하는 형태로 사용한다.

프로그램 내의 모든 곳에서 누구나 접근 가능!

public class Everywhere {
    public String var = "누구든지 허용"; // public 필드

    public String getVar() {             // public 메소드
        return this.var;
    }
}

🗂️ default

default 접근 제어자는 명시적으로 접근 제어자를 작성하지 않았을 때 적용되며 같은 패키지 안에 있는 클래스들에서만 접근할 수 있다. 외부 패키지에서는 접근이 불가능하기 때문에 관련된 클래스들끼리만 내부적으로 사용해야 할 때 적합하다. 보통 라이브러리 내부 구현 세부사항이나 패키지 단위로 모듈화된 코드에서 사용된다.

같은 패키지 내에서만 접근 허용!

package test;

public class SamePackage {
    String sameVar = "같은 패키지는 허용"; // default 필드
}

같은 클래스도 허용!

package test;

public class SameClass {
    String var = "다른 패키지는 접근 불가"; // default 필드

    public static void main(String[] args) {
        SamePackage sp = new SamePackage();
        System.out.println(sp.sameVar);     // 같은 패키지는 허용
    }
}

🛡️ protected

protected 접근 제어자는 같은 패키지 안에 있는 클래스들은 물론, 다른 패키지에 있는 상속 받은 자식 클래스에서도 접근할 수 있게 한다. 주로 상속을 통해 기능을 확장할 때, 부모 클래스의 필드나 메소드를 자식 클래스가 접근할 수 있도록 허용하기 위해 사용한다. 다만, protected 멤버는 외부에 노출될 가능성이 있기 때문에 의도하지 않은 접근을 막고 싶다면 private와 함께 신중하게 설계해야 한다.

같은 패키지 허용!


package test;

public class SameClass {
    protected String sameVar = "다른 패키지에 속하는 자식 클래스까지 허용"; // protected 필드
}

다른 패키지에 속하는 자식 클래스도 접근 허용!

package test.other;

import test.SameClass; // test 패키지의 SameClass 클래스를 불러들여 포함시킴.

public class ChildClass extends SameClass {

    public static void main(String[] args) {
        SameClass = new SameClass();
        System.out.println(sp.sameVar); // 다른 패키지에 속하는 자식 클래스까지 허용
    }
}

접근 제어자같은 클래스의 멤버같은 패키지의 멤버자식 클래스의 멤버그 외의 영역
public
protectedX
defaultXX
privateXXX



자 이제 접근 제어자에 대해서 알고 왔으니 캡슐화에 대해서 좀 더 이해하기 쉬울 것이다.

📦 캡슐화란?

캡슐화는 객체의 상태(데이터)를 외부로부터 직접 접근하거나 조작하지 못하도록 보호하고 대신 메서드를 통해서만 접근하고 조작하도록 제한하는 객체 지향 프로그래밍의 핵심 원칙이다. 이러한 캡슐화를 통해 코드의 안정성과 유지 보수성을 높일 수 있으며, 불필요한 내부 구현 세부사항을 숨기고 필요한 부분만 외부에 공개할 수 있다.

예를 들어, 접근 제어자를 활용하여 객체의 상태(데이터)나 내부에서 사용하는 기능(메소드) private으로 접근을 제한하고 객체의 상태를 조작하는 메소드를 public으로 공개하는 방식이 캡슐화에 해당한다.


간단한 코드로 예시를 들어보겠다.

아래는 부적절한 예시로 멤버 변수에 직접 접근하여 조작하는 방식이다. 게임에서 캐릭터의 레벨의 만렙이 10이라고 가정해 보자.


public class Character {
	
    public String name;
    public int level = 1;
    
    public Character(String name) {
    	this.name = name;
    }
    
    public void levelUp() {
    	if (level >= 10) {
        	System.out.println("더이상 레벨을 증가 시킬 수 없습니다. 최대 레벨입니다."); 
        } else {
        	level += 1;
            System.out.println("레벨 업!");
        } 
    }
}
	
public class CharacterMain {
	public static void main(String[] args) {
    	Character character = new Character("HG-KR98");
       	character.level = 12;
        character.levelUp(); // "더이상 레벨을 증가 시킬 수 없습니다. 최대 레벨입니다."
    }
}

위와 같은 코드에서는 멤버 변수를 외부에서 직접 변경할 수 있기 때문에 최대 레벨을 초과하는 캐릭터가 존재하는 비정상적인 상태가 발생한다. 그러므로 캐릭터의 데이터를 직접 접근하여 조작하지 못하도록 할 필요가 있다.


public class Character {
	
    private String name;
    private int level = 1;
    
    public Character(String name) {
    	this.name = name;
    }
    
    public void levelUp() {
    	if (level >= 10) {
        	System.out.println("더이상 레벨을 증가 시킬 수 없습니다. 최대 레벨입니다."); 
        } else {
        	level += 1;
            System.out.println("레벨 업!");
        } 
    }
    
    public int getLevel() {
        return level;
    }
    
}
	
public class CharacterMain {
	public static void main(String[] args) {
    	Character character = new Character("HG-KR98");
       	// character.level = 12; // 이렇게 작성하면 private 제어 접근자에 의해서 에러 발생!
        character.levelUp(); // 레벨 업! 
        System.out.println("현재 레벨: " + character.getLevel()); // 2
    }
}

그러므로 다시 위와 같은 예시에서 볼 수 있듯이 멤버 변수에 private으로 접근 제한을 설정하여 직접 멤버 변수를 조작하지 못하도록 하고 조작은 public으로 설정된 메소드로 조작하도록 하는 것이 캡슐화이다.


🧊 장점

  1. 정보 은닉 : 내부 구현을 숨기고 오직 공개된 인터페이스에만 접근할 수 있도록 함으로써 객체의 복잡성을 숨긴다.

  2. 데이터 보호 : 데이터에 직접 접근하는 것을 제한함으로써 데이터의 무결성을 보호하고 잘못된 사용을 방지한다.

  3. 코드 재사용 : 외부 인터페이스는 변경되지 않고 내부 구현을 변경할 수 있기 때문에 코드 재사용이 쉽다.

  4. 시스템 유지보수성 향상 : 내부 구현을 변경하여도 외부 인터페이스는 변경되지 않기 때문에 시스템의 유지보수가 쉽다.


🧃 단점

  1. 복잡성 증가 : 캡슐화는 코드의 복잡성을 증가시킬 수 있다.

  2. 성능 저하 : getter와 setter 메서드를 통해 데이터에 접근하려면 메서드 호출이 필요하므로 일부 성능 손실이 발생할 수 있다.

  3. 유연성 감소 : 캡슐화는 데이터와 그에 대한 연산을 하나로 묶지만, 때로는 데이터를 직접 조작하는 것이 필요할 경우도 있다.

  4. 접근 제한자 관련 문제 : 적절한 접근 제한자를 선택하지 않으면 캡슐화의 이점을 잃을 수 있다. 일부 개발자는 데이터에 대한 직접적인 접근을 허용하는 접근 제한자를 사용할 수 있으며, 이는 캡슐화의 이점을 상쇄시킬 수 있다.

  5. 리팩토링의 어려움 : 캡슐화된 코드를 변경하려면 getter와 setter 메서드의 시그니처를 변경해야 할 수 있다. 이 경우, 해당 클래스를 사용하는 다른 코드에도 변경 사항을 반영해야 할 수 있다.


참고
https://www.tcpschool.com/java/java_modifier_accessModifier
김영한의 실전 자바 - 기본편

profile
꾸준하게 가자

0개의 댓글