객체지향 특징 - 캡슐화, 정보 은닉

2024. 1. 11. 14:41·Tip
728x90

객체지향의 특징
LM2001020230_프로그래밍+언어+응용.pdf
10.06MB

 

인터넷에서는 4가지만 나와있는데, NCS에서는 5가지를 정의하고 있다.

보통 '정보 은닉'은 캡슐화의 부차적인 기능 정도로 생각했는데, 이를 직접적으로 명시한 의도는 무엇일까?

2번 항목을 보고 아래의 내용이 떠올라서 책을 다시 폈다.

로버트 마틴의 '클린 코드'에서는 이런 내용이 있다.

변수를 private로 정의하는 이유가 있다.
남들이 변수에 의존하지 않게 만들고 싶어서다.
충동이든 변덕이든, 변수 타입이나 구현을 맘대로 바꾸고 싶어서다.
그렇다면 어째서 수많은 프로그래머가 getter/setter로 비공개 변수를 외부에 노출할까?

 

만약 어떤 클래스가 생성 시에 참조하는 모든 클래스의 인스턴스를 매개변수로 넣어주어야 한다면,

또 공개된 메서드에는 매개변수도 많고, 오버로딩에 따른 차이가 극명하다면...

과연 해당 클래스를 사용하고 싶어질까?

심지어 개발 시, 유닛 테스트 작성에도 자유도가 지나치게 높아 불편할 것이다.

이 관점에서 1번 캡슐화는 단순화와 연관이 있다고 생각한다.

위와 같은 문제를 해결하기 위해 SRP로 함수가 하나의 책임만 담당하도록 만들어준다.

 

A라는 클래스는 getter와 setter를 제공하여 필드에 대한 접근/수정 기능을 제공한다.

B와 C 클래스는 A 인스턴스에 대한 getter를 참조하여 자신의 메서드를 구현한다.

B와 C 모두 동일한 A의 인스턴스를 참조한다고 했을 때,
만약 D라는 클래스가 해당 인스턴스의 setter를 호출하여 부적절한 값으로 변경하면 어떻게 될까?

또 A에서 해당 필드의 타입을 변경하거나, 필드를 삭제하고 싶다면 어떻게 될까?

이 관점에서 2번 정보 은닉은 의존 관계와도 간접적인 연관이 있다고 생각한다.

위와 같은 문제를 해결하기 위해 IoC(Inversion of Control)를 적용하여 최소화된 인터페이스를 제공하고,

OCP(Open-Closed Principle)와 DI(Dependency Injection)로 자유로운 확장이 가능하도록 만들어야 한다.

 

나는 이러한 내용을 '의존성 꼬리 자르기'라 부른다.

팀 프로젝트를 진행할 때, 나의 코드에 의존하는 다른 코드에 영향 없이 나의 코드를 수정할 수 있도록 만드는 것이다.


의존성 꼬리 자르기

클래스 A와 B가 있다.

A가 가진 어떠한 long 값을 참조하여 출력하는 기능을 B에 구현하려고 한다.

리팩토링이 필요해보인다.

class A {
    public Long value;
}

class B {
    public static void printA(A a) {
        long res = a.value;
        System.out.println(res);
    }
}

public class Main {
    public static void main(String args[]) {
        var a = new A();
        a.value = 1L;
        B.printA(a);
    }
}

A가 수정되면, B도 수정해야 하는 경우들

  1. A의 클래스명을 바꿨을 때
  2. 필드 value의 접근 제어자를 바꿨을 때
  3. 필드 value의 타입을 바꿨을 때
  4. 필드 value의 필드명을 바꿨을 때

물론 1, 4 변수명과 클래스명 수정은 IDE의 도움을 받아 손쉽게 처리할 수 있다.

그러나 2, 3은 그렇지 않다.

개선
1. 참조할 자료형에 대한 인터페이스를 만든다.
2. 인터페이스를 구현하는 자료형을 만든다.
3. value의 타입을 바꾸는 기능은 구현체에서 제공한다.

interface Foo {
    Long value();

    long valueAsLong();
}

record FooImpl(Long value) implements Foo {
    public long valueAsLong() {
        return (long) value;
    }
}

class A {
    private Foo foo;

    public A(Foo foo) {
        this.foo = foo;
    }

    public long value() {
        return foo.valueAsLong();
    }
}

class B {
    public static void printA(A a) {
        long res = a.value();
        System.out.println(res);
    }
}

public class Main {
    public static void main(String args[]) {
        Foo foo = new FooImpl(1L);
        var a = new A(foo);
        B.printA(a); // 1
    }
}

 

이렇게 하면 코드의 길이는 길어졌지만

Foo 인터페이스의 Long을 String으로 바꾸더라도
valueAsLong을 참조하는 클래스 A, B는 변경할 필요가 없다.  (OCP: Open-Closed Principle)


바꾼 결과는 다음과 같다.

interface Foo {
    String value();

    long valueAsLong();
}

record FooImpl(String value) implements Foo {
    public long valueAsLong() {
        return Long.parseLong(value);
    }
}

// A와 B는 변하지 않음

public class Main {
    public static void main(String args[]) {
        Foo foo = new FooImpl("1"); // 1L -> "1"
        var a = new A(foo);
        B.printA(a); // 1
    }
}

 

Foo::valueAsLong 로직이 여러 가지라면?

인터페이스의 구현체를 추가로 만들고 A의 생성자로 전달하면 된다. (DI: Dependency Injection)

여전히 A와 B는 수정할 필요가 없다.

// Foo는 수정하지 않았다.

record FooOnce(String value) implements Foo {
    public long valueAsLong() {
        return Long.parseLong(value);
    }
}

record FooTwice(String value) implements Foo {
    public long valueAsLong() {
        return Long.parseLong(value + value);
    }
}

// A와 B는 수정하지 않았다.

public class Main {
    public static void main(String args[]) {
        {
            Foo foo = new FooOnce("1");
            B.printA(new A(foo)); // 1
        }
        {
            Foo foo = new FooTwice("1");
            B.printA(new A(foo)); // 11
        }
    }
}

 

* 위 코드는 디자인 패턴 중 Strategy 패턴과 유사하다.


Reference

[SOLID] 의존 관계 역전 규칙(DIP), 의존성 주입(DI), 제어의 역전(IoC) · NSKG (yoojin99.github.io)

개인 노션 정리: DIP vs DI vs IoC

728x90
저작자표시 비영리 동일조건 (새창열림)

'Tip' 카테고리의 다른 글

이력서 작성 요령  (2) 2024.01.08
[Python] BOJ 1874번: 스택 수열  (0) 2023.07.21
[JavaScript] IntersectionObserver API 활용 이벤트 처리  (0) 2023.07.17
[Python/pandas/scikit-learn] 빅데이터 분석기사 실기 유형2 풀이 샘플  (0) 2023.07.17
[Python] BOJ 10815번: 숫자 카드  (0) 2023.07.16
'Tip' 카테고리의 다른 글
  • 이력서 작성 요령
  • [Python] BOJ 1874번: 스택 수열
  • [JavaScript] IntersectionObserver API 활용 이벤트 처리
  • [Python/pandas/scikit-learn] 빅데이터 분석기사 실기 유형2 풀이 샘플
ooMia
ooMia
  • ooMia
    데이터 과학자의 꿈
    ooMia
  • 전체
    오늘
    어제
    • 분류 전체보기 (116)
      • Insight (24)
        • 서평 (20)
      • Learn (43)
        • UPSIDE (22)
        • ASAC (4)
        • 우테코 (4)
      • Tip (24)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Velog.io
    • GitHub
    • Linked-in
    • Instagram
    • Solved.ac
  • 공지사항

  • 인기 글

  • 태그

    한빛미디어
    매우 쉬움
    한빛미디어서평단
    DAPP
    C언어
    for 반복문
    가이드
    국비지원교육
    내일배움카드
    업사이드
    패스트캠퍼스
    업사이드 아카데미
    우테코
    티스토리챌린지
    오블완
    web3
    나도 할 수 있는 Java&Spring 웹 개발 종합반
    K디지털기초역량훈련
    쉬움
    SQL 데이터 분석 첫걸음
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
ooMia
객체지향 특징 - 캡슐화, 정보 은닉
상단으로

티스토리툴바