본문 바로가기
우테코 자유테크 스터디/자유주제

[스터디] getter, setter 그래서 왜 쓰면 안 되는건데?🤔

by ♡˖GYURI˖♡ 2023. 11. 23.
// 이 글에서의 getter 메서드는 필드의 참조값을 그대로 넘기는 getXXX 형태의 public 메서드를 뜻함

public class TodoList {
   private final List<TodoItem> todos = new LinkedList<>();
   
   public List<TodoItem> getTodos() {
       return todos;
   }
}

 

 

우테코 프리코스를 진행하며 정말 많이 들었던 'getter 사용 지양'.

setter는 왜 쓰지 말라는 건지 대충 알겠지만... getter는 도대체 왜? 무엇이 문제일까?

 

잠깐 애초에 getter는 왜 쓰는거지? 그냥 필드 자체의 접근자를 public으로 하면 되잖아??

 

당연히 위와 같이 접근자를 public으로 해버리면 정보 은닉이고 캡슐화고 아무것도 지켜지지 않겠죠.

 

어떤 객체의 필드들이 public 접근자를 가지고 있다는 것은 결과적으로 객체 안에 어떤 필드가 있는지~ 그 필드는 어떻게 생겼는지~ 등등이 아주 무방비하게 외부에 노출되고 있는 것이고 이는 캡슐화를 지키지 못하는 것입니다.

 

그렇다면 이런 형태로 캡슐화가 깨지면 구체적으로 무엇이 잘못되었다는 것일까요?

 

부족한 캡슐화

객체가 사용자에게 구현 세부 사항을 공개하면 객체와 사용자 사이에 원하지 않는 결합력이 발생한다. 그러면 객체가 구현 세부 사항을 변경하려고 할 때마다 사용자에게 영향을 미칠 것이다.
- 소프트웨어 악취를 제거하는 리팩토링 中

 

 

출석부 객체를 가지고 설명해봅시다.

 

import java.util.ArrayList;
import java.util.List;

public class 출석부 {
    public List<Name> studentNames = new ArrayList<>();
    
    // studentName : 학생 이름 
    // Index : 리스트에 등록된 순서가 학번이 돼요
}

 

학생들의 이름을 담는 studentNames가 public으로 선언되어 있네요.

new 출석부() 로 인스턴스를 만들면 당연히 studentNames에도 바로 접근할 수 있겠네요.

 

출석부 출석부 = new 출석부();
출석부.studentNames; <<- 필드에 곧바로 접근 가능!

 

출석부를 활용할 비즈니스 로직을 만들어 봅시다.

 

public class StudentService {
    public 출석부 출석부 = new 출석부();

    /* 학생 이름을 등록해요 */
    public void registerStudent(Name 이름) {
        출석부.studentNames.add(이름);
    }

    /* 특정한 학번을 가진 학생을 등록해요 */
    public void registerStudentWithNumber(Name 이름, int 학번) {
        출석부.studentNames.add(학번, 이름);
    }

    /* 주어진 학번을 가진 학생을 제적해요 */
    public void removeStudentByNumber(int 학번) {
        출석부.studentNames.remove(학번);
    }

    /* n번째로 입학한 학생의 이름을 찾아요 */
    public String findStudentByIndex(int 입학순서) {
        return 출석부.studentNames.get(입학순서);
    }
}

 

출석부.studentNames를 호출해서 add(), remove(), get() 등을 할 수 있겠네요.

 

 

어느 날 교칙이 변경되어 더이상 입학 순서대로 학번을 정하는 것이 아니라 무작위로 된 6자리 숫자를 학번으로 쓰기로 하면서부터 문제가 발생합니다.

 

그럼 이제 List의 인덱스 숫자로 학번을 나타내면 안 되겠네요.

앞으로 학번 : 학생 이름으로 짝지어진 Map을 사용하기로 했습니다.

 

package exam2;

import java.util.HashMap;
import java.util.Map;

public class 출석부 {
    public Map<Integer, Name> studentNames = new HashMap<>();
    // Integer : 학번
    // Name : 학생 이름
}

 

자, 출석부의 필드 하나만 바뀌었는데 무슨 일이 생겼다는 걸까요?

 

 

위에서 작성했던 비즈니스 로직이 전부 작동되지 않겠죠.

 

여기서 잠깐 의존한다라는 용어의 뜻을 살펴봅시다.

서로 다른 필드와 메서드 또는 클래스를 직접! 사용하는 것을 의존한다라고 하는데요.

 

위에서 본 코드에서는 StudentService가 studentNames 필드에 의존하고 있었다고 볼 수 있겠네요.

 

public 클래스에서는 멤버의 접근 수준을 package-private에서 protected 이상으로 바꾸는 순간 그 멤버에 접근할 수 있는 대상 범위가 엄청나게 넓어진다. public 클래스에 접근할 수 있는 멤버는 공개 API이므로 그 모습 그대로 영원히 지원해야 한다.
- 이펙티브 자바 中

 

 

사실 문제는 이게 끝이 아닙니다.

 

도메인 규칙 무시

전역 변수와 자료 구조 공개는 데이터 멤버에 대한 관대한 접근 기능을 제공하는 것보다 더 문제가 심각하다. 프로그램에 있는 어떤 다른 객체든지 전역 변수에 접근하여 변경할 수 있으며. 서로 직접 의존해서는 안 되는 두 객체 사이에 은밀한 통신 채널을 생성하기 때문이다. 이렇게 전역으로 접근 가능한 변수와 자료 구조는 소프트웨어 시스템의 이해 가능성, 안정성, 테스트 가능성에 심각한 영향을 미칠 수 있다.
- 소프트웨어 악취를 제거하는 리팩토링 中

 

 

다시 처음으로 돌아가서 생각해봅시다.

 

import java.util.ArrayList;
import java.util.List;

public class 출석부 {
    public List<Name> studentNames = new ArrayList<>();
    
    // studentName : 학생 이름 
    // Index : 리스트에 등록된 순서가 학번이 돼요
}
public class StudentService {
    public 출석부 출석부 = new 출석부();

    /* 학생 이름을 등록해요 */
    public void registerStudent(Name 이름) {
        출석부.studentNames.add(이름);
    }

    /* 특정한 학번을 가진 학생을 등록해요 */
    public void registerStudentWithNumber(Name 이름, int 학번) {
        출석부.studentNames.add(학번, 이름);
    }

    /* 주어진 학번을 가진 학생을 제적해요 */
    public void removeStudentByNumber(int 학번) {
        출석부.studentNames.remove(학번);
    }

    /* n번째로 입학한 학생의 이름을 찾아요 */
    public String findStudentByIndex(int 입학순서) {
        return 출석부.studentNames.get(입학순서);
    }
}

 

이 출석부 코드를 가지고 사용해볼게요.

 

public class University {
    public static void main(String[] args) {
        StudentService service = new StudentService();

        service.registerStudent(Name.of("백여우"));
        service.registerStudent(Name.of("매튜"));

        학생이름출력하기(service.출석부);
    }

    private static void 학생이름출력하기(출석부 출석부) {
        for (Name name : 출석부.studentNames) {
            System.out.println(name);
        }
    }
}

 

정상적으로 작동될 것입니다.

 

(Name.of 생략)
service.registerStudent("백여우");
service.registerStudent("매튜");
service.registerStudent("Boxter");

 

이 또한 정상적으로 작동이 될 것입니다.

 

(Name.of 생략)
service.registerStudent("백여우");
service.registerStudent("매튜");
service.registerStudent("Boxter");
service.registerStudent("ぎっちゃん");
service.registerStudent("友主");
service.registerStudent("هونغجو");
service.registerStudent("#@#%@#!^^");

 

그리고 이 또한 정상적으로 작동이 될 것입니다.

 

어떤가요? 사람 이름이 아닌 다른 문자들도 등록해버릴 수 있게 되었네요. 

그래서 "한글과 영어만 입력 가능", "공백 불가" 라는 제약사항을 추가할 것입니다.

 

import java.util.ArrayList;
import java.util.List;

public class 출석부 {
    public List<Name> studentNames = new ArrayList<>();
    
    // studentName : 학생 이름 
    // Index : 리스트에 등록된 순서가 학번이 돼요
}

 

하지만 필드를 선언한 위 코드에는 어떤 제약사항과 관련된 코드를 적을 수가 없네요.

대신 registerStudent() 안에 구현해보겠습니다.

 

public class StudentService {
    public 출석부 출석부 = new 출석부();

    /* 학생 이름을 등록해요 */
    public void registerStudent(Name 이름) {
        if (빈문자열인가() || 한글또는영어가아닌가(name)) {
            throw new RuntimeException();
        } <<-- 제약사항 추가 !!
        출석부.studentNames.add(이름);
    }

    /* 특정한 학번을 가진 학생을 등록해요 */
    public void registerStudentWithNumber(Name 이름, int 학번) {
        출석부.studentNames.add(학번, 이름);
    }

    /* 주어진 학번을 가진 학생을 제적해요 */
    public void removeStudentByNumber(int 학번) {
        출석부.studentNames.remove(학번);
    }

    /* n번째로 입학한 학생의 이름을 찾아요 */
    public String findStudentByIndex(int 입학순서) {
        return 출석부.studentNames.get(입학순서);
    }
}

 

이제 registerStudent() 메서드가 호출되면 이름을 검증한 후 이를 통과하면 출석부에 등록할 것입니다.

하지만 StudentService의 다른 메서드들은 이 제약사항이 구현되어있지 않네요.

 

public으로 공개된 필드와 StudentService의 메서드들이 직접적으로 연결되어 있기 때문에 해당 메서드들을 만든 개발자에게 각각 '검증 코드 추가'를 부탁해야겠네요.

 

이게 과연 좋은 방법일까요?

 

도메인 객체를 사용할 때 지켜야 할 규칙들이 분명 있을텐데 외부의 사용자가 객체 내부의 필드를 직접 가져다 쓸 수 있게 되면 그 규칙들을 무시할 수 있기 때문에 코드의 안정성이 위험해집니다.

 

이런 문제가 발생하는 것은 도메인 객체의 필드를 외부의 사용자가 직접 접근할 수 있게 되어 필드를 조작할 때 지켜야 하는 규칙이나 제약사항을 지키도록 강제하지 못하는 잘못된 구조가 원인입니다.

 

객체 내부의 값을 조작할 때 지켜야 하는 규칙들이 도메인 객체 내부가 아니라 외부의 객체들에 흩어져 구현되어 있기 때문에 어떤 곳에서는 지켜지고, 어떤 곳에서는 안 지켜지고...

 

게다가 규칙을 수정하려면 모~든 코드를 뒤져가며 수정해야하는 최악의 유지보수성을 가지게 되겠죠...

 

 

 

그렇다면 public 필드만 아니라면 해결이 될까요?

 

이번엔 필드의 접근자는 private로 바꾸고, 객체의 값을 가져오는 공개 getter 메서드를 추가했습니다.

 

import java.util.ArrayList;
import java.util.List;

public class 출석부 {
    private List<Name> studentNames = new ArrayList<>(); <<- 필드는 private로!

    public List<Name> getStudentNames() {
        return studentNames;
    } <<- 값을 가져오는 getter 메소드!
}

 

이제 출석부 객체를 사용하려면 바로 접근할 수 없고 getStudentNames() 메서드를 통해서 가져와야 해요.

 

public class StudentService {
  public 출석부 출석부 = new 출석부();

  /* 학생 이름을 등록해요 */
  public void registerStudent(Name 이름) {
      if (빈문자열인가() || 한글또는영어가아닌가(name)) {
          throw new RuntimeException();
      }
      출석부.getStudentNames().add(이름);
  }

  /* 특정한 학번을 가진 학생을 등록해요 */
  public void registerStudentWithNumber(Name 이름, int 학번) {
      출석부.getStudentNames().add(학번, 이름);
  }

  /* 주어진 학번을 가진 학생을 제적해요 */
  public void removeStudentByNumber(int 학번) {
      출석부.getStudentNames().remove(학번);
  }

  /* n번째로 입학한 학생의 이름을 찾아요 */
  public String findStudentByIndex(int 입학순서) {
      return 출석부.getStudentNames().get(입학순서);
  }
}

 

흠... 근데 아까 위에서 봤던 문제점들이 해결이 된 걸까요?

이게 public 필드를 그냥 썼던 것과 무슨 차이가 있는 걸까요?

 

변수를 private로 선언하더라도 각 값마다 조회 함수와 설정 함수를 제공한다면 구현을 외부로 노출하는 셈이다. 변수 사이에 함수라는 게층을 넣는다고 구현이 저절로 감춰지지는 않는다.
- 클린 코드 中

 

출석부.getStudentNames()를 호출하면 무엇이 반환될까요? 출석부 객체 안에 있는 studentNames라는 이름의 List<Name>을 가져오겠죠. 그것도 참조값을요!

 

그래서 외부의 객체인 StudentService에서 출석부.getStudentNames()를 호출해 값을 가져온 후 무엇을 하나 봐보면...

 

출석부.getStudentNames().add(이름)
출석부.getStudentNames().remove(학번)
출석부.getStudentNames().get(입학순서)

 

아직도 List<Name>에 직접 의존하는 메서드들을 쓰고 있네요.

만약 Map으로 자료구조를 바꾼다면 또 줄줄이 에러가 뜨겠죠?

 

여전히 '부족한 캡슐화' 문제는 해결되지 않았네요!

 

게다가 출석부.getStudentNames()는 studentNames 필드의 참조, 즉 메모리 주소를 그대로 넘겨줍니다.

다른 말로 하면

출석부.getStudentNames().add("ぎっちゃん")

 

와 같은 코드를 통해 필드 내부를 마음대로 조작할 수 있다는 것이죠.

 

이 객체를 조작할 때 필요한 제약사항을 객체 내부의 getStudentNames()에 적어둘 방법이 없기 때문에 '도메인 규칙 무시'라는 문제 또한 계속 일어날 것입니다.

 

이제 드디어 왜 getter를 쓰지 말라는지 이해가 가네요.아니 그럼 어떻게 해야하는 걸까요?

 

 

설계를 고쳐보자

우리는 왜 getter를 써야하나요.

어떤 객체 안에 있는 필드 값이 필요해서겠지요.

 

그렇다면 그 필드의 값이 왜 필요한가요?

우리가 객체 안에 있는 내부 값을 꺼내올 때에는 그걸 활용해서 어떻게 하고싶다! 라는 목적이 있기 마련입니다.

 

우리의 출석부 객체를 사용하는 StudentService 클래스에는 출석부에 학생 이름을 등록하는 registerStudent()라는 메서드가 있는데

 

  /* 학생 이름을 등록해요 */
  public void registerStudent(Name 이름) {
      if (빈문자열인가() || 한글또는영어가아닌가(name)) {
          throw new RuntimeException();
      }
      출석부.getStudentNames().add(이름);
  }

 

이 메서드에서 '학생의 이름을 저장(등록)한다'라는 목적을 위해 getStudentNames()로 학생들의 이름을 담고 있는 필드를 가져온 후, add() 메서드를 사용하는 것이지요.

 

그렇다면 이 목적을 달성하기 위해서는 꼭 List를 직접 가져와야 할까요?

 

여기에서 객체지향적인 코드를 쓰기 위한 중요 개념인 행동, 메시지라는 용어가 등장합니다.

 

객체는 자율적인 존재라는 점을 명심하라. 객체지향의 세계에서 객체는 다른 객체의 상태에 직접적으로 접근할 수도, 상태를 변경할 수도 없다. 자율적인 객체는 스스로 자신의 상태를 책임져야 한다. 외부의 객체가 직접적으로 객체의 상태를 주무를 수 없다면 간접적으로 객체의 상태를 변경하거나 조회할 수 있는 방법이 필요하다.
이 시점에서 행동이 무대 위로 등장한다. 행동은 다른 객체로 하여금 간접적으로 객체의 상태를 변경하는 것을 가능하게 한다. 객체의 스스로의 행동에 의해서만 상태가 변경되는 것을 보함으로써 객체의 자율성을 보장한다.
- 객체지향의 사실과 오해 中

 

이를 적용하여 학생의 이름들을 관리하는 출석부 객체에 '학생의 이름을 저장(등록)한다'라는 행동을 요청하는 메시지를 보내면, 출석부 객체에서 메시지를 받고 알아서 행동하도록 설계하면 되지 않을까?

 

/* 학생 이름을 등록해요 */
public void registerStudent(Name 이름) {
	출석부.saveStudent(이름); <<- 객체에 메시지를 보낸다.
}
import java.util.ArrayList;
import java.util.List;

public class 출석부 {
  private List<String> studentNames = new ArrayList<>();

  public void saveStudent(Name 이름) {
      if (빈문자열인가() || 한글또는영어가아닌가(name)) {
          throw new RuntimeException();
      }
      studentNames.add(이름);
  } <<- 수신한 메시지를 처리하는 구체적인 방법은 객체 안에서만 다룬다.
}

 

결과는 이전과 같으나, StudentService는 이제 'ㅇㅇ이라는 이름의 학생을 등록해주세요' 라는 메시지만 던지면 됩니다.

메시지를 받은 출석부가 알아서 등록해주기 때문에 출석부 객체 안에서 학생 이름을 어떻게 관리하는지 어떤 자료구조에 담고 있는지 알 필요가 없어집니다.

 

자 이제 다시 studentNames 자료구조를 List에서 Map 으로 변경해봅시다.

 

import java.util.HashMap;
import java.util.Map;

public class 출석부 {
  private Map<Name, Integer> studentNames = new HashMap<>();

  public void saveStudent(Name 이름) {
      if (빈문자열인가() || 한글또는영어가아닌가(name)) {
          throw new RuntimeException();
      }
      studentNames.put(이름, [랜덤하게 생성된 학번]);
  }
}

 

saveStudent() 메서드가 약~간 바뀌었죠.

 

/* 학생 이름을 등록해요 */
public void registerStudent(Name 이름) {
    출석부.saveStudent(이름); <<- 객체에 메시지를 보낸다.
}

 

하지만 StudentService는? 전~~~혀 바뀔 필요가 없습니다.

객체 내부의 필드와 외부 객체 사이의 의존성이 자연스럽게 떨어지고 getter 메서드는 그 과정에서 사라지게 되는 것입니다.

 

 

getter 메서드를 당연스레 사용하고 있다면 사용하는 이유를 한 번 생각해봅시다!

 

 

그런데도 저는 조회를 위해 필드 값을 꼭! 가져와야겠어요!

0 - 어떤 형태로 값을 가져와야 할까요?

 

출석부에서 학생들의 이름을 가져와야 한다면 꼭 List 형태로 가져와야할까요?

Iterator는 어떨까요?

 

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class 출석부 {
  private List<Name> studentNames = new ArrayList<>();

  public Iterator<Name> getStudentNames() {
      return studentNames.iterator();
  }
}
/* 학생들 이름을 형식에 맞게 출력해요. */
public void printStudentNames() {
    Iterator<Name> studentNames = 출석부.getStudentNames();
    while (studentNames.hasNext()) {
        System.out.printf("%s입니다.", studentNames.next());
    }
}

 

Iterator는 다음 값이 있는지 여부를 반환하는 hasNext(), 다음 값을 가져오는 next() 두 가지 뿐이기에 조회 목적에 더 알맞는 인터페이스입니다!

 

그 목적을 달성하는데에 최적화된 타입 또는 자료구조의 형태로 값을 가져오는 전략을 사용해봅시다!

 

 

하지만... 순차적으로 값을 조회하는 Iterator 만으로는 해결할 수 없는 작업이 정말 많기에 결국 List나 Map 같은 자료구조에 필드 값들을 담아서 가져올 때가 많습니다.

 

이 경우 다음 두 가지 문제가 생길 수 있습니다.

  1. 필드의 값을 갖고 있는 자료구조가 add, remove, set 등의 메서드를 지원한다면 조회를 위해 가져온 값들이 사용 과정에서 변질되면서 실제 원본 값들의 상태와 달라질 수 있다는 것
  2. 가져온 값이 객체의 필드 원본을 가리키는 참조값을 가리키고 있기 때문에 이 값을 조작하면 객체의 원본 값도 함께 조작된다는 것

 

조회용으로 가져온 값과 객쳉 저장된 원본 필드 사이의 관계가 완전히 끊어져 있어야 진정한 캡슐화가 이루어지는 것입니다!

 

 

1 - 자료구조 수정 못하게 막기

 

전달할 값이 전달 과정에서 훼손되지 않게 하려면 값을 더하고 빼거나 수정하는 등의 메서드가 없거나 작동하지 않도록 해야 하는데... 그것이 바로! ImmutableCollection(불변컬렉션)입니다!

 

ImmutableList, ImmutableSet, ImmutableMap은 자료구조 내부를 수정하는 어떤 메서드를 호출하면 수정 작업을 하지 않고! 무조건 예외가 발생하도록 구현되어 있습니다

 

자료구조를 불변으로 변환하는 방법은 대표적으로 세 가지가 있는데요.

 

public void exam() {
    List<Name> studentNames = 출석부.getStudentNames();

    List<Name> immutableStudentNames0 = List.copyOf(studentNames);
    List<Name> immutableStudentNames1 = Collections.unmodifiableList(studentNames);
    List<Name> immutableStudentNames2 = studentNames.stream().collect(Collectors.toUnmodifiableList());
}
  1. List.copyOf(리스트) 를 통해
  2. Collections.unmodifiableList(리스트) 를 통해
  3. List immutableStudentNames2 = 리스트.stream().collect(Collectors.toUnmodifiableList()); 를 통해

 

어떤 방법을 써도 불변으로 만들어줍니다!

 

 

하지만... 여전히 주요한 문제가 있는데, 바로 원본과의 연결 끊기!

 

2 - 원본과의 연결 끊기

 

이를 위해서는 getter 메서드를 호출하여 복사한 조회용 값이 원본의 메모리 주소를 향하지 않게, 즉 원본과 똑같지만 완전히 새로운 복사체를 만들어 가져와야 합니다.

 

(1) 내부 값까지 전부 복사 : 깊은 복사

public List<Name> getStudentNames() {
    return Collections.unmodifiableList(studentNames);
}

 

또는

  public List<Name> getStudentNames() {
       return studentNames.stream()
               .map(name -> new Name(name.getName()))
               .collect(Collectors.toUnmodifiableList());
   }

 

이렇게 해주면 원본에는 전~혀 영향이 가지 않으니 getter 메서드 호출 때문에 캡슐화가 망가질 일이 없습니다!

 

 

이러한 객체 간의 상호작용을 통해 외부 객체는 객체의 내부 구현에 대한 의존성을 낮출 수 있습니다. 또, 필드 값을 가져와 작업하는 코드를 객체의 내부로 이동시키는 것만으로도 객체의 내부 구조와의 의존성을 줄일 수 있구요.

 

결국 유연한 설계로 이어져 유지보수가 쉽고,  확장 가능한 시스템을 만드는데 도움이 되는 것이지요.

 

 

 

추가 : setter는 왜 쓰면 안 되는데?

setter는 값을 바꾸는 이유를 드러내지 않습니다.

class Account {
    private long balance;
    
    public Account(long balance) {
        this.balance = balance;
    }
    
    public void setBalance(long balance) {
        this.balance = balance;
    }
}
 
Account myAccount = new Account(500);
myAccount.setBalance(1000);

 

이 코드를 보면 myAccount 잔액이 1000이라는 것을 바로 알 수 있죠?

하지만 맨 마지막 줄, setBalance(1000)만 본다면 인출을 한건지, 입금을 한건지 바로 알 수가 없을 겁니다.

 

이렇게 setter를 쓰면 값을 바꾼 이유를 명확히 알 수가 없어요.

 

또, setter를 쓰면 해당 객체가 해야 할 일을 다른 객체가 해야할 수도 있습니다.

 

@Service
public class AccountService {
    ...
    
    public void withdraw(long id, long amount) {
        Account account = accountRepository.findById(id).orElseThrow();
        long newBalance = account.getBalance() - amount;
        
        if (newBalance < 0) {
            throw new IllegalArgumentException("잔액이 부족합니다.");
        }
        
        account.setBalance(newBalance);
    }
    
    ...
}

 

원래라면 Account 객체에서 잔고 관리 책임을 가져야하지만, AccountService가 잔고 확인을 하고 있네요.

Account가 책임을 다 하지 않았기 때문에 AccountService에서 Account의 일을 대신 해주고 있는 것입니다.

 

이렇게 어떤 객체가 해야 할 일을 다른 객체가 대신 해주면 어떻게 될까요?

 

아까처럼 객체 구조가 변경된다면 그 객체의 일을 해주는 다른 코드들을 전~부 고쳐야하는 것입니다...

 

그렇다면 setter를 안 쓰면 어떻게 해야할까?

바로 Account에게 책임을 지도록 시키면 된다!

 

class Account {
    private long balance;
    
    public void withdraw(long amount) {
        if (amount > balance) {
            throw new IllegalArgumentException("잔액이 부족합니다.");
        }
        
        balance -= amount;
    }
}
 
@Service
public class AccountService {
    ...
    
    public void withdraw(long id, long amount) {
        Account account = accountRepository.findById(id).orElseThrow();
        account.withdraw(amount);
    }
    
    ...
}

 

코드도 간결하고, 이제는 출금이라는 것도 파악할 수 있게 되었다. 만약 Account 내의 객체 구조가 변경되어 영향이 간다면, AccountService를 고칠 필요 없이 withdraw()만 고치면 된다.

 

 


 

https://velog.io/@backfox/getter-%EC%93%B0%EC%A7%80-%EB%A7%90%EB%9D%BC%EA%B3%A0%EB%A7%8C-%ED%95%98%EA%B3%A0-%EA%B0%80%EB%B2%84%EB%A6%AC%EB%A9%B4-%EC%96%B4%EB%96%A1%ED%95%B4%EC%9A%94

 

getter 쓰지 말라고만 하고 가버리면 어떡해요

설명 좀 해주고 가요

velog.io

https://bestinu.tistory.com/40#article-1-0-2--%EB%82%B4-%ED%8F%AC%EC%8A%A4%ED%8C%85%EC%97%90-%EB%B9%84%ED%95%B4-getter/setter%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4-%EC%95%88%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0%EC%97%90-%EB%8C%80%ED%95%B4-%EC%9E%98-%EC%A0%95%EB%A6%AC%EB%90%9C-%EA%B8%80

 

public 대신 getter와 setter를 사용하는 이유는 무엇일까

getter와 setter를 사용해야 하는 이유 java 공부를 하면서, getter와 setter를 만들어 사용했었는데 문득 어차피 이 객체의 속성을 외부에서 읽거나 쓸 필요가 있다면 그냥 public으로 선언하면 될텐데 왜

bestinu.tistory.com

https://msmk530.tistory.com/72

 

[자바] getter메소드 사용을 지양하자

기존의 나는 코딩을할때 상태값이 있는 클래스에는 무조건 getter와 setter를 기본적으로 정의해주곤 했다. 최근 OOP스터디를 하던중, getter메소드 사용을 지양해야한다는 피드백을 들었는데, 그 이

msmk530.tistory.com

https://tecoble.techcourse.co.kr/post/2020-04-28-ask-instead-of-getter/

 

getter를 사용하는 대신 객체에 메시지를 보내자

getter는 멤버변수의 값을 호출하는 메소드이고, setter는 멤버변수의 값을 변경시키는 메소드이다. 자바 빈 설계 규약에 따르면 자바 빈 클래스 설계 시, 클래스의 멤버변수의 접근제어자는 private

tecoble.techcourse.co.kr

https://rok93.tistory.com/entry/%EC%96%95%EC%9D%80%EB%B3%B5%EC%82%AC-VS-%EA%B9%8A%EC%9D%80%EB%B3%B5%EC%82%AC

 

얕은복사 VS 깊은복사

자바로 개발을 하다보면 객체를 복사할 일이 있다. 이럴 때 나오는 개념이 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy) 개념인데, 두 개념의 차이를 간단하게 말하면 얕은 복사는 객체의 참조값(주

rok93.tistory.com

https://trashyou.tistory.com/157

 

getter 사용

getter 쓰지 말라고요? 이유 1: 객체의 내부 구조를 외부에서 직접 조작하게 되면 캡슐화, 모듈화가 깨지면서 코드의 안정성이 심각하게 무너진다. 이유 2: 객체의 필드가 public이라면, 필드를 private

trashyou.tistory.com

https://colabear754.tistory.com/173

 

[OOP] Getter와 Setter는 지양하는게 좋다

목차 들어가기 전에 얼마 전 사내에서 Getter와 Setter를 함부로 사용하면 안되는 이유에 대한 세미나가 있었다. Setter에 대한 이야기는 워낙 많이 알려져있었지만 Getter에 대한 이야기는 잘 하지 않

colabear754.tistory.com

https://velog.io/@sezeom/Getter-Setter-%EC%A7%80%EC%96%91%ED%95%98%EA%B8%B0

 

Getter, Setter 지양하기

객체의 상태가 변경되는 것은 객체 스스로의 행동에 의해서야 한다.이렇게 설계될 때 객체는 자율적인 객체가 되고 외부의 영향을 받지 않음으로써 느슨한 결합과 유연한 협력을 이룰 수 있다.g

velog.io