21.필드 자체 캡슐화 Self Encapsulate Field

클래스를 구현할 때 개인 필드로 직접 참조하는 것이 일반적입니다.
필드에 대한 모든 내부 참조가 간접적이며 적합한 get/set 메소드를 진행하는 또 다른 스타일이 있습니다.
이를 자체 캡슐화라고 합니다.

[ 특징 ]

■ 간적 필드를 사용함에 Getter/Setter는 캡슐화를 구현하는 방법 중에서 가장 기본적인 형태
■ 직접 필드 액세스보다 읽기가 약간 더 어렵다.
■ 서브 클래스가 get/set 메소드를 오버라이드할 수 있도록 해, 필드의 동작 방법에 관한 독자적인 정의를 제공할 수 있다.

직접 필드를 간접 필드로 써라

  • 솔직히 꼭 그렇게 해야될 필요가 있을까에 대한 의문을 가집니다.

그러니까 간단하게 직접 필드에 그대로 할당하고, 그대로 갖다 쓴다고라고 하지만
어느날 불변 규칙(Immutable Pattern)을 넣도록 하겠습니다. (구현에 따른 분할/병합 불변성과같은 규칙 추가 등 포함) 하면 .....리팩토링...
결국 setter/getter로 한번 감싸는 것이 좋다.

그래서 처음부터 일관된 모양(getter/setter)으로 되어 있는 것이 좋다.

객체지향 언어에서는 객체의 의미를 최대한 살려서 객체와의 관계 안에서 프로그래밍을 하는 것이 바람직합니다.
그런 점에서 객체의 상태는 오로지 그 객체의 동작에 의해서만 접근/변경이 가능하도록 해야하는 것이 맞습니다.

그러면 필드에서 선언할 수 있는 접근 제한자 중에서도 public이나 (package)는 빼고 private, protected만 쓸 수 있도록 해야하지 않을까 생각해봅니다.

객체 지향 내용을 읊어보면

  1. 객체의 모든 필드(전역변수)는 Private해야한다.
  2. 한 객체가 다른 객체 필드 변경이 필요한 경우 메세지(public method)를 통해 객체의 상태(필드의값)를 변경 해야한다.
  3. 외부에서 조작이 가능한 접근제어자(public method)는 최대한 최소화한다.

그런데 정보은닉 및 캡슐화를 위함이 아닐까 생각이 듭니다.
이를테면 덧셈을 하는 클래스를 만든다고 할 때 사용자에게 값만 던져라... 나머지는 하겠다고 하고 덧셈에 대한 연산방법은 신경쓰지 안도록 하고자 한다면(설계자가 그런 의도라면...) 당연히 getter/setter가 필요할 것입니다.

캡슐화란

돈을(필드에) 빌려가고 싶으면(접근하고 싶으면) 나한테(객체의) 얘기를 하고 달라고 해야지(메서드를 통해 접근해야지),  주머니에서 마음대로 지갑을 뒤져서(외부에서 객체의 내부 상태에 까지 직접 접근해서) 돈을 꺼내가면(변경/할당을 하면)  되지 않습니까?
Before Refactoring:
class A {
    private int f = 10;    
}
After Refactoring:
class A {
  private int f = 10; 
  public void setF(int f) {
   this.f = f;
  }
  public int getF() {
   return f;
  }
}
객체의 불변규칙(Invariant Rule) 적용
// 규칙1 :  string  field1은 절대 null이 되어서는 안된닙니다  적어도""입니다.
private String field1 = "";

public String getField1() {
    return field1;
}
public void setField1(String field1) {
    Objects.requireNonNull(field1);
    this.field1 = field1;
}
Immutable Pattern을 적용할 때
// Immutable Pattern: 객체의 생명 주기동안 내부의 상태가 절대 변경되지 않도록 강제하는 방법
// Immutable Pattern을 적용한 객체는 필드에 값을 할당하는 것이 생성자(Constructor)를 통해서만 이루어지고, 필드에 접근을 하는 Getter만 사용하게 됩니다.
class SomeClass {
    
    private final List<String> field2;
    
    public List<String> getField2() {
        return field2;
    }
    
    /**
     * 생성자
     */
    public SomeClass(List<String> field2) {
        this.field2 = field2;
    }
}