Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20241124 유틸리티클래스는 악이다? 본문

개발Article

[TIL]20241124 유틸리티클래스는 악이다?

최밤빵 2024. 11. 24. 07:26

개발 중 공통적인 기능들을 제공하기 위해 유틸리티 클래스를 사용하는 것은 흔한 일이다. 하지만, "유틸리티 클래스는 악이다"라는 주장을 보게되고, 유틸리티 클래스가 가진 단점과 객체지향적인 접근 방식의 차이를 구체적으로 이해할 필요성을 느꼈다.


▶ 유틸리티 클래스

유틸리티 클래스는 여러 클래스에서 공통적으로 사용할 수 있는 메서드들을 정적(static)으로 구현한 클래스를 의미한다. 자바의 Math 클래스나 Collections 클래스가 대표적인 예이다. 유틸리티 클래스는 인스턴스를 생성하지 않고도 메서드를 호출할 수 있고, 상태를 가지지 않는다.

▷유틸리티 클래스

public class MathUtil {
    public static int add(int a, int b) {
        return a + b;
    }

    public static int multiply(int a, int b) {
        return a * b;
    }
}

▶ 유틸리티 클래스의 장점

  • 독립적인 기능 제공
    유틸리티 클래스는 특정 상태와 무관하게 독립적인 기능을 제공하기 때문에, 수학 계산이나 문자열 처리처럼 단순하고 재사용 가능한 기능을 구현하기에 적합하다.
  • 코드 간결성
    객체를 생성하지 않고 메서드 호출만으로 기능을 사용할 수 있기때문에 간결하고 직관적인 코드 작성이 가능하다.
  • 성능
    인스턴스를 생성하지 않기 때문에 메모리 사용량이 적고, 호출 속도 또한 빠르다.

▶유틸리티 클래스의 단점

  • 객체지향 철학 위배
    객체지향 프로그래밍은 데이터와 행동을 하나의 단위로 묶고, 그걸 바탕으로 설계를 진행한다. 하지만 유틸리티 클래스는 상태를 가지지 않고, 절차지향적인 방식으로 동작하기때문에 객체지향의 철학과 어긋난다. 특히, 유틸리티 클래스는 상태를 관리하지 않고, 모든 로직을 외부에서 호출하여 실행하는 방식으로 동작하기 때문에 절차지향적인 접근 방식에 가깝다. 객체 내부의 상태와 행동을 캡슐화하여 설계하는 객체지향 철학과 대조적이다.
  • 확장성과 재사용성 부족
    유틸리티 클래스는 상속을 통해 확장할 수 없고, 오버라이딩도 불가능하다. 새로운 요구사항이 발생하면 기존 코드를 수정해야 하고, 코드의 재사용성이 떨어진다.
  • 테스트 어려움
    정적 메서드는 의존성을 주입할 수 없기 때문에, 테스트 과정에서 Mock 객체로 대체하기 어렵다. 결과적으로 단위 테스트가 복잡해질 수 있다.
  • 결합도 증가
    유틸리티 클래스는 여러 클래스에서 공통적으로 사용되기 때문에, 하나의 변경 사항이 다수의 클래스에 영향을 미칠 수 있다. 코드의 결합도를 높이고, 유지보수성을 저하시킨다.

▶객체지향적인 설계와의 차이

객체지향 설계에서는 상태와 행동을 객체 내부에 캡슐화하고, 객체의 상태에 따라 동작이 달라질 수 있다. 

▷유틸리티 클래스

public class MathUtil {
    public static int add(int a, int b) {
        return a + b;
    }

    public static int multiply(int a, int b) {
        return a * b;
    }
}

 

▷객체지향 설계

public class Calculator {
    private int result;

    public void add(int value) {
        result += value;
    }

    public void multiply(int value) {
        result *= value;
    }

    public int getResult() {
        return result;
    }
}

→ 차이점

  • 유틸리티 클래스는 상태를 저장하지 않고, 메서드 호출 시 필요한 값들을 매번 전달해야 한다.
  • 객체지향 설계는 내부 상태를 관리하며, 연산 결과를 객체 내부에 저장하고, 이를 활용하여 연속적인 동작을 수행할 수 있다.

▶유틸리티 클래스와 객체지향 코드의 비교

같은 연산을 수행할 때, 유틸리티 클래스와 객체지향 코드.

▷유틸리티 클래스 사용

int num1 = 3;
int num2 = 2;
int num3 = 5;

int added = MathUtil.add(num1, num2);
int multiplied = MathUtil.multiply(added, num3);
System.out.println("Result: " + multiplied); // 출력: 25

▷객체지향 코드 사용

Calculator calculator = new Calculator();
calculator.add(3);       // 내부 상태 result = 3
calculator.add(2);       // 내부 상태 result = 5
calculator.multiply(5);  // 내부 상태 result = 25

System.out.println("Result: " + calculator.getResult()); // 출력: 25

→ 결과 비교

  • 유틸리티 클래스는 연산의 과정을 외부에서 모두 처리해야 하고, 중간 결과를 따로 관리해야 한다.
  • 객체지향 코드는 객체의 내부 상태를 관리하여, 중간 결과를 신경 쓰지 않고 필요한 동작만 나열하면 된다.

▶유틸리티 클래스의 대안

함수형 인터페이스를 활용하여, 유틸리티 클래스 대신 함수형 인터페이스를 사용할 수 있다.

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

public class Calculator {
    public int calculate(int a, int b, Operation operation) {
        return operation.apply(a, b);
    }
}

// 사용 예
Calculator calculator = new Calculator();
int result = calculator.calculate(3, 5, (x, y) -> x + y);

→ 의존성 주입을 통한 서비스 객체 활용: 유틸리티 클래스의 정적 메서드 대신, 서비스 객체를 의존성 주입으로 활용하면 테스트 가능성과 확장성을 높일 수 있다.


"유틸리티 클래스는 악이다"라는 표현은 유틸리티 클래스 자체를 부정하는 것이 아니라, 객체지향 프로그래밍 철학을 강조하기 위한 말이다. 유틸리티 클래스는 단순한 기능 제공에는 적합하지만, 복잡한 상태 관리나 확장성 있는 설계에는 적합하지 않다. 프로젝트의 요구사항에 따라 유틸리티 클래스와 객체지향 설계를 적절히 조합하여 사용하는 것이 중요하다. 단순한 계산 로직은 유틸리티 클래스로 처리하되, 상태 관리와 확장성이 요구되는 경우 객체지향 설계를 채택해야 한다.