자동차 브랜드 목록을 갖고 있는 Enum 클래스가 있다. Car는 Brand와 최고 속도를 필드 변수로 갖고 있고
public enum Brand {
Audi, BMW, KIA;
Brand() {
}
}
public class Car {
private final Brand brand;
private final int maxSpeed;
public Car(Brand brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public Brand getBrand() {
return this.brand;
}
public int getSpeed() {
return this.maxSpeed;
}
}
자동차 브랜드 목록을 갖고 있는 Enum 클래스가 있다. Car Class는 Brand와 최고 속도를 필드 변수로 갖고 있다.
public static List<Car> filterBMWCars(List<Car> inventory) {
List<Car> result = new ArrayList<>();
for (Car car : inventory) {
if (Brand.BMW.equals(car.getBrand())) {
result.add(car);
}
}
return result;
}
filterBMWCars(List inventory) 메서드는 Car 객체를 갖는 List를 파라미터로 받아 BMW 차만 선택해 리스트에 추가 후 반환하는 메서드이다. 이후에 요구사항으로 Audi도 필터링해달라는 요청이 들어왔다. 여기서 filterAudiCars()라는 메서드를 추가로 만들 수 있지만 더 많은 변화를 생각하면 좋은 방법은 아니기에 코드를 반복 사용하지 않고 요청사항을 대응해보자
요구사항에 맞추기 위해 필터링하려는 브랜드를 파라미터로 받아 반환하는 메서드를 만들어보자
public static List<Car> filterBMWCars(List<Car> inventory,Brand brand) {
List<Car> result = new ArrayList<>();
for (Car car : inventory) {
if (brand.equals(car.getBrand())) {
result.add(car);
}
}
return result;
}
위 코드처럼 파라미터에 요구사항을 추가하여 유연하게 대응하는 코드를 만들 수 있다. 하지만 여기에서도 브랜드뿐만 아니라 차의 최고속도로 필터링할 수 있는 메서드를 만들어달라는 요구사항이 추가되었다. 코드를 복사 붙여 넣기 하여 파라미터 부분만 교체할 수 있지만 코드의 중복이 많아져 하나의 메서드에 파라미터를 추가하기로 했다.
세 번째 시도로 모든 속성을 파라미터 화하여 필터링하면
public static List<Car> filterBMWCars(List<Car> inventory,Brand brand,int maxSpeed, boolean flag) {
List<Car> result = new ArrayList<>();
for (Car car : inventory) {
if (flag&&brand.equals(car.getBrand()) || (!flag&& car.getSpeed() > 150)){
result.add(car);
}
}
return result;
}
List<Car> List = filterBMWCars(carList, Brand.BMW,150,true);
List<Car> List = filterBMWCars(carList, Brand.BMW,200,false);
위 메서드처럼 사용할 수 있지만.. 가독성이 떨어지고 필터링해야 할게 늘어나거나 변경해야 할 때 유연하게 대응하기 힘들어지는 단점이 있다.
이제 파라미터를 추가하는 방법이 아닌 요구사항에 유연하게 대응할 수 있는 다른 방법을 찾아보자 Predicate 인터페이스를 이용해 선택 조건을 결정하는 인터페이스를 정의해서 나타내 보자
public interface CarPredicate {
boolean test(Car car);
}
public class CarAudiBrandPredicate implements CarPredicate{
@Override
public boolean test(Car car) {
return Brand.Audi.equals(car.getBrand());
}
}
public class CarBMWBrandPredicate implements CarPredicate{
@Override
public boolean test(Car car) {
return Brand.BMW.equals(car.getBrand());
}
}
CarPredicate 함수형 인터페이스를 구현하는 클래스를 만들어 fliter메서드에서 CarPredicate 객체를 받아 조건을 검사하도록 메서드를 설계한다. 메서드가 동작(전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
public static List<Car> filterCars(List<Car> inventory,CarPredicate carp){
List<Car> result = new ArrayList<>();
for(Car car : inventory){
if(carp.test(car)){
result.add(car);
}
}
return result;
}
이렇게 하면 앞선 코드들에 비해 더 유연한 코드를 얻을 수 있고 가독성 또한 좋아졌다. 요구사항이 늘어난다 해도 CarPredicate를 구현하는 클래스만 만들면 된다. 하지만 이것 또한 요구사항이 많아지면 CarPredicate를 구현하는 많은 클래스들을 생성해야 한다는 단점이 존재한다.
이제 CarPredicate를 구현하는 클래스 없이 요구사항을 충족하는 방법을 찾아보자. 우선 첫 번째로 익명 클래스를 사용할 수 있다. 익명 클래스는 inner class로, 이름이 말 그대로 이름이 없는 클래스를 말한다. 클래스를 생성함과 동시에 정의하여 사용하는 클래스로 프로그램 내에서 한 번만 사용될 때 굳이 정의할 필요 없이 한 번만 정의하여 사용하는 클래스를 말한다.
List<Car> result = filterCars(carList, new CarPredicate() {
@Override
public boolean test(Car car) {
return Brand.KIA.equals(car.getBrand());
}
});
위에서 조금 더 발전시켜서 구현부를 줄여보자. 자바 8의 람다 표현식을 이용해서 위 예제 코드를 간단하게 재 구현하면
List<Car> result = filterCars(carList, (Car car) -> Brand.Audi.equals(car.getBrand()));
이제 한 줄로 표현할 수 있을 만큼 간단해졌다 한눈에 코드가 들어오면서 가독성이 올라가게 되었다.
여기서 형식 파라미터 제네릭으로 바꿔주면
public static <T> List<T> filterCars(List<T> inventory,CarPredicate<T> carp){
List<T> result = new ArrayList<>();
for(T e : inventory){
if(carp.test(e))
result.add(e);
}
return result;
}
으로 filterCars메서드를 변경할 수 있고 메서드를 CarLIst 뿐만 아니라 다른 객체의 리스트 또한 추가할 수 있다.
List<Sneck> result = filterCars(sneckList, (Sneck sneck) -> 150 > (sneck.getPrice()));
위에처럼 도 사용 가능해진다.
'Study > 모던 자바 인 액션' 카테고리의 다른 글
스트림 활용 -1 (0) | 2022.09.14 |
---|---|
3장 람다 표현식 (0) | 2022.08.17 |