Java에서 Getter와 Setter를 사용해야 하는 이유와 캡슐화 쉽게 이해하기

Java 개발자라면 누구나 한번쯤 getter와 setter 메서드를 작성해 보았을 것입니다. 하지만 정작 왜 이러한 패턴을 사용하는지 깊이 생각해 본 적이 있으신가요? 이 글에서는 객체지향 프로그래밍의 핵심 원칙인 캡슐화와 함께 getter/setter의 필요성과 장점을 알아보겠습니다.


객체지향 설계

// 잘못된 방식 (외부에서 직접 접근 가능)
public class Person {
public String name;
}

// 올바른 방식 (외부에서 getter setter를 통해서만 접근 가능)
public class Person {
private String name; // private로 선언하여 외부 접근 차단

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

캡슐화(Encapsulation)

객체지향의 4가지 특징 중 하나인 캡슐화는 데이터(속성)와 그 데이터를 다루는 코드(기능)를 하나로 묶고, 외부에서 데이터를 함부로 바꾸지 못하게 보호하는 것입니다.

위 예제에서 보면 name이라는 변수가 데이터라고 볼 수 있습니다. getName과 setName은 데이터를 다루는 코드라고 볼 수 있습니다. 데이터와 데이터를 다루는 코드가 하나로 묶여 있습니다. 변수의 접근제어자를 private으로 선언했기 때문에 외부에서 데이터를 함부로 바꿀 수 없습니다.

변수의 접근제어자를 private으로 바꾸고, getter/setter를 만드는 이 일반적인 방법은 이 캡슐화를 하기 위함입니다.

단일 책임 원칙 (SRP)

getter와 setter는 데이터 접근 및 수정 책임을 클래스 내부에 집중시켜, 외부 코드가 데이터 조작 로직을 알 필요 없게 합니다. 외부에서 직접 변수에 접근하면, 데이터 검증 로직을 추가할 때마다 모든 호출 코드를 수정해야 하지만, Setter를 사용하면 클래스 내부에서 한 번만 수정하면 됩니다.

개방 폐쇄 원칙 (OCP)

getter와 setter를 통해 내부 구현을 변경하더라도 외부 코드는 변경되지 않으므로, 확장에 열려 있고 수정에 닫힌 코드를 작성할 수 있습니다. Setter에 데이터 유효성 검사 로직을 추가해도 외부 코드는 영향을 받지 않습니다.


유지보수성

이 값을 참조하는 곳이 어딘지 찾고 싶으면 getName 메서드를 사용하는 부분을 추적하면 됩니다. 이 값을 변경하는 부분이 어딘지 찾고 싶으면 setName 메서드를 사용하는 부분을 추적하면 됩니다. 나중에 큰 도움이 될 수 있는 부분입니다. (IntelliJ 메서드 사용하는 부분을 추적 단축키는 Ctrl + B 입니다. 자주 사용되는 단축키입니다.)


세밀한 데이터 접근 제어

public class ImmutablePoint {
private final int x;

public ImmutablePoint(int x) {
this.x = x;
}

public int getX() {
return x;
}
// setter 없음 -> x는 변경 불가
}

위 예제와 같이 setter를 만들지 않고, 변수에 final을 추가함으로써 최초에 입력된 값이 변경되지 않는 불변 객체도 만들 수 있습니다.


디버깅/모니터링 용이성

getter나 setter에 브레이크 포인트를 걸거나 로깅을 추가해서 디버깅과 모니터링을 더 세밀하게 할 수 있습니다.


프레임워크 및 라이브러리 호환성

Spring, Hibernate, Jackson 등이 제대로 동작하지 않을 수 있습니다.

Spring 데이터 바인딩

@RestController
public class UserController {
@PostMapping("/users")
public String createUser(@ModelAttribute User user) {
// setter가 없으면 user.username 값이 null로 남아 있음
return "username: " + user.getUsername();
}
}

Spring에서의 JSON과 Java 객체 간 변환

@RestController
public class UserRestController {
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
User user = userService.findById(id);
// 반환 시 Jackson 라이브러리가 User 객체의 getter를 호출하여 JSON으로 변환
return user;
}

@PostMapping("/api/users")
public User createUser(@RequestBody User user) {
// JSON -> 객체 변환 시 setter 메서드 사용
return userService.save(user);
}
}


Lombok 활용

// Lombok 사용 예시
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Person {
private String name;
private int age;

// getter/setter 코드를 직접 작성할 필요 없음
}

Lombok을 활용하면 getter/setter를 직접 작성할 필요가 없습니다. 실무에서도 많이 사용되고 있으니 적극적으로 활용해보세요.


글을 마치며

지금까지 Java에서 getter/setter를 사용해야 하는 다양한 이유를 살펴보았습니다. 단순히 관례적으로 사용하는 것이 아니라, 객체지향 설계 원칙 준수, 유지보수성 향상, 세밀한 데이터 제어, 디버깅 용이성, 그리고 프레임워크 호환성 등 실질적인 이점을 제공합니다. getter/setter를 작성하는 것이 번거롭다면, lombok 라이브러리의 @Getter, @Setter 어노테이션을 활용하여 간편하게 코드를 작성할 수 있습니다. 이를 통해 보일러플레이트 코드를 줄이면서도 객체지향의 장점을 모두 취할 수 있습니다. 효과적인 캡슐화와 적절한 getter/setter 활용으로 더 견고하고 유지보수 하기 쉬운 코드를 작성하시길 바랍니다.

댓글

이 블로그의 인기 게시물

Spring Boot Initializr 사용법 상세 가이드

SQL 테이블 ALIAS 규칙: 가장 효과적인 방법과 장단점 비교

PostgreSQL 로컬 PC(윈도우)에 설치하기 - 17.4버전 기준