Getter대신 객체에 메시지 보내기

이진호·2022년 10월 13일
0
post-thumbnail

규칙 9: Getter/Setter/Property 사용 금지

자바 빈 설계 규약

자바 빈 설계 규약에 따르면 자바 빈 클래스 설계 시, 클래스의 멤버변수의 접근제어자는 private이며, 모든 멤버변수에 대해 get메소드와 set메소드가 존재해야 합니다. get메소드는 매개변수가 없어야 하며 set메소드는 하나 이상의 매개변수가 있어야 합니다.

상태값을 갖는 객체에서는 상태값을 외부에서 직접 접근해 변경하지 못하도록 메소드만 노출시켜야 합니다. 멤버변수(상태값)는 접근 제한자를 private으로 설정해 직접적인 접근을 막고, getter와 setter를 이용해서만 변수에 접근이 가능하도록 합니다.

private String name;

//setter name의 값을 변경시킨다.
public void setName(String name){
    this.name = name;
}

//getter name의 값을 호출한다.
public String getName(){
    return this.name;
}

무분별한 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())	// Car객체의 position = 자동차가 움직인 거리
                  .max(Integer::compareTo)
                  .get();
           
        return cars.stream()
                .filter(car -> car.getPosition() == maximum)
                .map(Car::getName)
                .collect(Collectors.toList());
    } 
         ...
}

위의 예시 findWinners 메소드에서는 Car 객체에서 getPosition() 을 사용해 position 상태값을 직접 꺼내 비교하고 있습니다. Car의 접근 제한자가 private인 멤버변수 position 값 끼리 비교하는 로직이기 때문에 이는 적절하지 않습니다. Car 객체 내부의 position 값을 비교 하는 것은 Car 객체에서 해야 하는 일인 것입니다.

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;
    }
         ...
}

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 IllegalArgumentException("차량 리스트가 비었습니다."));
    }

    private List<String> findSamePositionCar(Car maxPositionCar) {
        return cars.stream()
            .filter(maxPositionCar::isSamePosition)
            .map(Car::getName)
            .collect(Collectors.toList());
    }
}

위는 getPosition() 을 없애는 방향으로 리팩토링 한 코드 입니다. Car에서 Comparable을 상속받아 compareTo() 를 구현해 Car내에서 자동차끼리 비교를 할 수 있고, max를 통해 cars 중, 최대 길이의 position을 가진 Car를 찾을 수 있게 되었습니다. 또, isSamePosition() 을 구현해 Car 내에서 직접 position 값을 비교할 수 있게 되었습니다.

출처

0개의 댓글