[OOP] 캡슐화(Encapsulation)란 ?

이석환·2024년 2월 16일
0

개념 정리

목록 보기
6/6
post-thumbnail

캡슐화

캡슐화란 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것

캡슐이란 단어는 일상 속에서도 자주 접할 수 있다.
예를 들어, 약국에서 처방받은 알약을 생각하면 쉬울 것 같다.
작은 알약 안에 증상을 낫게 하는 약재들이 섞여있고, 밖에서는 그 약재가 어떤 것인지 또는 손상을 방지할 수 있다.
만약 가루약이라면 어떨까 ? 바람에 쉽게 날라가버릴 것이다.
가루약을 캡슐화 시켜서 알약이 된다고 생각하자 !

이는 데이터 보호데이터 은닉과도 같다.
외부로부터 클래스에 정의된 속성과 기능을 보호(데이터 보호)하고, 필요한 부분만 외부로 노출(데이터 은닉)시켜 각 객체 고유의 독립성과 책임 영역을 안전하게 지키고자 한다.

해당 내용을 좀 더 자세하게 기술한 문서를 MDN Web Docs에서 발췌하면 다음과 같다.

캡슐화는 데이터와 함수를 하나의 컴포넌트(예, 클래스)로 압축하고, 그 다음에 해당 컴포넌트에 대한 접근을 제어하여 객체에서 '블랙박스'를 만듭니다. 이 때문에, 해당 클래스의 사용자는 숨겨진 구현이 아닌 해당 인터페이스(즉, 클래스 외부에 노출된 데이터 및 함수)만 알면 됩니다.

캡슐화를 구현하기 위해서는 두 가지가 있다.

접근 제어자 (Access Modifiers)

접근 제어자같은 클래스같은 패키지자식 클래스외부 어디서나
publicOOOO
protectedOOO
defaultOO
privateO

public -> protected -> default -> private 순으로 범위가 좁아진다.

public

접근 제어자가 public으로 선언되었다면, 어디서나 접근이 가능하다.

public class publicTest {
    public String publicString = "나의 접근 제어자는 public입니다.";
}

public class Main {

    public static void main(String[] args) {
        publicTest publicTest = new publicTest();
        System.out.println(publicTest.publicString);
    }
}

protected

접근 제어가자 protected로 선언되었다면, 동일 패키지의 클래스 또는 해당 클래스를 상속받은 클래스에서만 접근이 가능하다.

package protectedTest;

public class protectedTest1 {
    protected String protectedString = "나의 접근 제어자는 protected입니다.";
}
package protectedTest2;

import protectedTest1.protectedTest1;

public class protectedTest2 extends protectedTest1 {
    public void run(){
        System.out.println(protectedString);
    }
}
import protectedTest2.protectedTest2;

public class main  {

    public static void main(String[] args) {
        protectedTest2 protectedTest2 = new protectedTest2();
        protectedTest2.run();
    }
}

protectedTest1protectedTest2는 서로 다른 패키지에 존재한다.
하지만, 상속을 받았기 때문에 protected로 선언된 protectedString을 쓸 수 있다.

default

package defaultTest;

public class defaultTest {
    String defaultString = "나의 접근 제어자는 default입니다.";
}
package defaultTest;

public class Main {

    public static void main(String[] args) {
        defaultTest defaultTest = new defaultTest();
        System.out.println(defaultTest.defaultString);
    }
}

동일한 패키지인 defaultTest 내에 있는 것을 확인하면 된다.

private

public class privateTest {
    private String privateString = "나의 접근 제어자는 private입니다.";

    public String getPrivateString() {
        return privateString;
    }
}
public class main  {

    public static void main(String[] args) {
        privateTest privateTest = new privateTest();
        //System.out.println(privateTest.privateString);
        System.out.println(privateTest.getPrivateString());
    }
}

private으로 선언되었기 때문에 main메서드 두 번째 줄에 있는 문장은 실행되지 않는다.
아래에 설명할 Getter를 통해 privateTest 내에 private로 선언된 privateString을 가져온다.

Getter/Setter

위에서 private 설명할 때, 미리 나왔던 Getter이다.
보통 데이터를 다른 곳에서 변경하지 못하도록 접근 제어자를 private으로 두고, getter/setter를 사용하라고 한다.

사실 생각해 보면 쉽다.
private은 해당 클래스내를 제외하고 어디서든 접근을 하지 못한다.
당연히 캡슐화가 강해질 수 밖에 없다.
왜냐면, 내부를 몰라도 꺼내오고 세팅할 수 있으니까 ..
하지만 바로 위의 문장으로 인해 의아하게도 위험성도 당연히 존재한다.

Getter/Setter의 문제

간단하게 말하면 Setter같은 경우, 내부를 모르는 데 밖에서 해당 데이터를 마구잡이로 바꾸면 어떻게 될까 ?

또한, Getter/Setter를 외부에서 사용했을 때 과연 private으로 감춘 내용을 모를까 ?

public class privateTest {
    private String privateString = "나는 private이기 때문에 외부에서 절대 모를 거야";
    private int privateInt = 10;

    public String getPrivateString() {
        return privateString;
    }

    public void setPrivateString(String privateString) {
        this.privateString = privateString;
    }

    public int getPrivateInt() {
        return privateInt;
    }

    public void setPrivateInt(int privateInt) {
        this.privateInt = privateInt;
    }
}
public class main  {

    public static void main(String[] args) {
        privateTest privateTest = new privateTest();
        System.out.println(privateTest.getPrivateString() + " 내가 가지고 있는 숫자는 " + privateTest.getPrivateInt() + "야.");
        privateTest.setPrivateString("나는 private이지만 내가 바꿔버렸어");
        privateTest.setPrivateInt(20);
        System.out.println(privateTest.getPrivateString() + " 내가 가지고 있는 숫자는 " + privateTest.getPrivateInt() + "야.");
    }
}

어지러워지기 시작한다.
분명 캡슐화는 내부에 있는 속성을 숨기는 것이라고 했는데, 외부에 있는 main 함수에서 이를 꺼내는 것을 모자라서 변경하기 까지 한다.

또한, 해당 변수들이 사라진다면 Getter / Setter를 사용하고 있는 모든 곳에서 변경이
일어난다.

이는 각 객체들이 강한 결합을 하고 있다는 방증이다.

여기서 해결책을 찾아보자면, 객체에 메세지를 보내라 라는 표현 정도가 어울린다.

public class privateTest {
    private String privateString = "나는 private이기 때문에 외부에서 절대 모를 거야";
    private int privateInt = 10;

    public String privateSays(){
        return privateString + " 내가 가지고 있는 숫자는 " + privateInt + "야.";
    }
}
public class main  {

    public static void main(String[] args) {
        privateTest privateTest = new privateTest();
        System.out.println(privateTest.privateSays());
   }
}

또는 생성자를 통해서, 값을 주입하여 setter를 사용하지 않는 방법도 있다.

public class privateTest {
    private String privateString;
    private int privateInt;

    public privateTest(String privateString, int privateInt){
        this.privateString = privateString;
        this.privateInt = privateInt;
    }

    public String privateSays(){
        return privateString + " 나의 나이는 " + privateInt + "입니다.";
    }
}
public class main  {

    public static void main(String[] args) {
        privateTest privateTest = new privateTest("나는 이석환입니다.", 27);
        System.out.println(privateTest.privateSays());
   }
}

객체는 내부 구현을 public interface를 통해 노출하지 않는다.
이를 통해, 어떤 데이터를 가지고 있는지 알 수 없고 수정될 수도 없다.
오직 메세지를 주고 받을 뿐이다.

개인적으로 생각했을 때, 극단적으로 Getter를 쓰지 말라고 하는 것은 무리가 있다고 생각한다.
프리코스를 진행하며, Getter없이 개발하려면 오히려 코드가 복잡해지거나 테스트 코드가 더욱 복잡해지는 경우가 발생했기 때문이다.

이는 언제나 개발 상황에 맞춰서 본인이 판단하는 것이 중요할 것 같다.

References
https://www.codestates.com/blog/content/객체-지향-프로그래밍-특징
https://developer.mozilla.org/ko/docs/Glossary/Encapsulation
https://songkg7.github.io/posts/getter-and-setter/

profile
반갑습니다.

1개의 댓글

comment-user-thumbnail
2024년 2월 28일

본인이 뭔데 판단하죠?

답글 달기