Getter에 대해서
getter 이란?
멤버변수의 값을 호출하는 메소드이다.
setter 이란?
멤버변수의 값을 변경시키는 메소드이다.
자바 빈 설계 규약에 따르면, 자바 빈 클래스 설계 시, 클래스의 멤버변수의 접근 제어자는 private이며, 모든 멤버변수에 대해 get 메소드와 set메소드가 존재해야 한다. get 메소드는 매개 변수가 없어야 하며, set 메소드는 하나 이상의 매개 변수가 있어야 한다.
상태값을 갖는 객체에서는 상태값을 외부에서 직접 접근해 변경하지 못하도록 메소드만 노출시킨다.
private String name;
//setter: name의 값을 변경시킴.
public void setName(String name){ // 매개변수 존재
this.name = name;
}
//getter: name 값을 호출함.
public String getName(){
return this.name;
}
상태를 갖는 객체 는 getter를 통해 가져온 상태 값을 통해 로직을 수행하는 경우가 있다. 그러나 무조건적으로 모든 멤버변수를 호출하는 getter 메소드를 생성하는 것은 문제가 될 수 있다.
상태를 갖는 객체란?
이상한 나라의 앨리스로 예시를 들어보자.
앨리스가 음료를 마시는 것은 행위 (Behavior) 이고, 앨리스가 음료를 마셔서 키가 작아지는 것은 상태(Status) 이다.
즉, 앨리스의 행동에 따라 앨리스의 상태가 변하게 된다. 따라서 행동의 결과는 앨리스의 상태에 따라 의존적이다.
하지만, 앨리스가 키가 작던 크던 앨리스는 앨리스이다. 따라서 앨리스는 상태 변경과 무관한, 유일한 존재로 식별이 가능하다.
객체란 식별 가능한 개체 또는 사물이다. 객체는 자동차처럼 만질 수 있는 구체적인 사물일 수도 있고, 시간처럼 추상적인 개념일 수도 있다. 객체는 구별 가능한 식별자, 특징적인 행동, 변경 가능한 상태를 가진다. 소프트웨어 안에서 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다.
객체의 상태
객체의 상태는 특정 시점에 객체가 가지고 있는 정보의 집합으로 객체의 구조적 특징을 표현한다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티 값으로 구성된다. 객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크(Link)로 구분할 수 있다.
- 링크 Link: 객체와 객체 사이의 의미있는 연결. 링크가 존재해야만 요청을 보내고 받을 수 있다.
- 속성 attribute: 링크와 달리 객체를 구성하는 단순한 값이다.
- 링크는 객체가 다른 객체를 참조할 수 있다는 것을 의미하며, 객체가 다른 객체의 식별자를 알고 있다는 것으로 표현한다.
- 객체의 프로퍼티는 단순한 값인 속성과, 다른 객체를 가리키는 링크라는 두 가지 종류의 조합으로 표현될 수 있다.
public class Alice{
private Beverage beverage; // 링크: beverage 와 연결된 링크
private Long height; // 속성: 앨리스의 키
private int age; // 속성: 앨리스의 나이
}
객체의 행동
객체는 자율적인 존재이므로 다른 객체의 상태에 직접적인 접근과 상태 변경이 불가능하다. 따라서 우리는 간접적으로 객체의 상태를 변경하거나 조회할 수 있는 방법이 필요한데, 이 시점에서 필요한 것이 행동이다.
행동은 다른 객체로 하여금 간접적으로 객체의 상태를 변경하는 것을 가능하게 한다. 객체 지향의 기본 사상은 상태와 상태를 조작하기 위한 행동을 하나의 단위로 묶는 것이다.
무분별한 getter 보다는 객체에 메세지를 보내 객체가 로직을 수행하도록 한다.
객체는 캡슐화된 상태와 외부에 노출되어있는 행동을 갖고 있으며, 다른 객체와 메세지를 주고 받으면서 협력한다. 객체는 메세지를 받으면 객체 그에 따른 로직(행동) 을 수행하게 되고, 필요하다면 객체 스스로 내부의 상태값도 변경한다.
간단히 말하면, 객체 지향 프로그래밍은 객체가 스스로 일을 하도록 하는 프로그래밍이다.
모든 멤버 변수에 getter을 생성해두고 상태값을 꺼내 그 값으로 객체 외부에서 로직을 수행한다면, 객체가 로직(행동)을 갖고 있는 형태가 아니고, 메세지를 주고 받는 형태도 아니게 된다. 또한, 객체 스스로 상태값을 변경하는 것도 아니고, 외부에서 상태값을 변경할 수 있는 위험성도 생길 수 있다.
따라서 이는 객체가 객체스럽지 못하게 된다.
또한, getter를 남용하게 되면,디미터의 법칙을 위반할 가능성도 생기고, 가독성이 떨어질 수 있다.
상태를 가지는 객체를 추가했다면 객체가 제대로 된 역할을 하도록 구현해야 한다. 객체가 로직을 구현하도록 해야한다. 상태 데이터를 꺼내 로직을 처리하도록 구현하지 말고 객체에 메시지를 보내 일을 하도록 리팩토링한다.
예시) 자동차 경주 게임의 예시 코드
public class Cars {
public static final String DELIMITER = ",";
public static final int MINIMUM_TEAM = 2;
private List<Car> cars;
public Cars(String inputNames) {
String[] names = inputNames.split(DELIMITER, -1);
cars = Arrays.stream(names)
.map(name -> new Car(name.trim()))
.collect(Collectors.toList());
validateCarNames();
}
...
public List<String> findWinners() {
final int maximum = cars.stream()
.map(car -> car.getPosition())
.max(Integer::compareTo)
.get();
return cars.stream()
.filter(car -> car.getPosition() == maximum)
.map(Car::getName)
.collect(Collectors.toList());
}
...
}
여러 자동차들 중 position 값이 제일 큰 우승 자동차들을 구하는 findWinners() 메소드를 보면,
Car 객체에서 직접 getPosition()을 사용해, Position 상태 값을 직접 꺼내 비교한다.
public List<String> findWinners() {
final int maximum = cars.stream()
.map(car -> car.getPosition()) // Car객체의 position = 자동차가 움직인 거리
.max(Integer::compareTo)
.get();
return cars.stream()
.filter(car -> car.getPosition() == maximum)
.map(Car::getName)
.collect(Collectors.toList());
}
그러나, Cars 클래스에서 position 값을 비교하는 로직을 수행하는 것이 맞을까⁉️
Car의 접근 제한자가 private인 멤버변수 position 값끼리 비교하는 로직이다. 따라서, Car 객체에게 position 값을 비교할 또 다른 Car 객체를 넘겨주고, Car 끼리 position을 비교해야 한다. Cars가 아닌 Car에서 해야할 일인 것이다.
⭐️ 해결법 : Car 객체 내에서 같은 자동차끼리 position 값을 비교하고, Car 객체 내에서 maximum 과 일치하는지 비교하도록 Cars 의 로직을 Car 안으로 옮기자.
즉, Car 객체에게 position 값을 비교할 수 있도록 메세지를 보내고, Car 객체에게 maximum 값과 자신의 position 값이 같은지 물어보는 메세지를 보내서, getPosition() 메서드를 사용하지 않도록 해보자.
public class Car implements Comparable<Car>{
public boolean isSamePosition(Car other){
return other.position == this.position;
}
@Override
public int compareTo(Car other){
return this.position - other.position;
} //Car 객체 안에서 객체끼리 비교하는 로직(Comparable)
}
getPostition()을 없애는 방향으로 리팩토링 한 Car 객체이다.
Car 에서 Comparable을 상속받아 CompareTo()를 구현해 Car내에서 자동차리끼리 비교를 해준다. max를 통해 cars 중, 최대 길이의 position을 가진 Car를 찾을 수 있다.
public class Cars{
public List<String> findWinners(){
final Car maxPositionCar = findMaxPositionCar();
return findSamePositionCars(maxPositionCar);
}
private Car findMaxPositionCar(){
Car maxPositionCar = cars.stream()
.max(Car::compareTo)
.orElseThrow(()-> new IllegalArugumentException("차량 리스트가 비었습니다."));
}
private List<String> findSamePositionCar(Car maxPositionCar){
return cars.stream()
.filter(maxPositionCar::isSamePosition)
.map(Car::getName)
.collect(Collectors.toList());
}
}
Getter를 무조건 사용하지 말라는 것은 아니다.
당연히 getter를 무조건 사용하지 않고는 기능을 구현하기 힘들다.
출력을 위한 값.
순수 프로퍼티를 가져오기 위한 것.
의 어느정도의 getter은 허용된다.
그러나, Collection 인터페이스를 사용하는 경우, 외부에서 getter 메서드로 얻은 값을 통해 상태값을 변경할 수 있다.
public List<Car> getCars() {
return cars;
} (x)
public List<Car> getCars() {
return Collections.unmodifiableList(cars);
} (o)
이처럼 Collections.unmodifialbeList()와 같은 unmodifiable collection을 사용해 외부에서 변경하지 못하도록 하는 것이 좋다.
참고1) https://tecoble.techcourse.co.kr/post/2020-04-28-ask-instead-of-getter/
참고2) https://pengtory981.tistory.com/27
getter를 사용하는 대신 객체에 메시지를 보내자
getter는 멤버변수의 값을 호출하는 메소드이고, setter는 멤버변수의 값을 변경시키는 메소드이다. 자바 빈 설계 규약에 따르면 자바 빈 클래스 설계 시, 클래스의 멤버변수의 접근제어자는 private
tecoble.techcourse.co.kr
'ETC > 2023 우테코 프리코스' 카테고리의 다른 글
[1회차] 1회차 숫자 야구게임 리팩토링 (0) | 2023.12.05 |
---|---|
4주차 회고 (0) | 2023.11.29 |
3주차 기능 구현 (0) | 2023.11.08 |
2주차 회고 (0) | 2023.11.07 |
회고 작성 법 가이드라인 (0) | 2023.11.07 |