'유틸리티 함수'클래스 길들이기


15

Java 코드베이스에서 나는 다음과 같은 패턴을 계속보고 있습니다.

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

나를 귀찮게하는 FooUtil것은 테스트하기 때문에 모든 곳 의 인스턴스를 통과해야한다는 것 입니다.

  • FooUtil의 메소드를 정적으로 만들 수 없습니다 . 왜냐하면 FooUtil클라이언트 클래스와 클라이언트 클래스 모두를 테스트하기 위해 모의 할 수 없기 때문 입니다.
  • 테스트를 위해 그것을 조롱 할 수 없기 때문에 다시 FooUtil소비 장소에서 인스턴스를 만들 수 없습니다 new.

나는 내 최선의 방법은 주사를 사용하는 것이라고 가정하지만, 그것은 자신의 번거 로움을 추가합니다. 또한 여러 util 인스턴스를 전달하면 메서드 매개 변수 목록의 크기가 커집니다.

내가 보지 못하는 것을 더 잘 처리하는 방법이 있습니까?

업데이트 : 유틸리티 클래스는 상태가 없으므로 테스트를 위해 클래스의 기본 정적 필드를 업데이트하는 기능을 유지하면서 정적 싱글 톤 INSTANCE멤버 또는 정적 getInstance()메서드를 추가 할 수 있습니다 . 너무 깨끗해 보이지 않습니다.


4
그리고 단위 테스트를 효과적으로 수행하기 위해 모든 종속성을 조롱해야 한다는 가정 은 정확하지 않습니다 (나는 여전히 그 문제를 논의하는 질문을 찾고 있지만).
Telastyn

4
왜 이러한 메소드가 데이터를 조작하는 클래스의 멤버가 아닌가?
Jules

3
FooUtility 인 경우 정적 fns를 호출하는 별도의 Junit 세트를 보유하십시오. 그런 다음 조롱하지 않고 사용할 수 있습니다. 그것의 모의가 당신이 조롱해야한다고 생각한다면, 나는 단지 유틸리티가 아니라 IO 또는 비즈니스 로직을 수행한다고 생각하며 실제로 클래스 멤버 참조이어야하며 생성자, init 또는 setter 메소드를 통해 초기화되어야합니다.
tgkprog

2
Jules 댓글에 +1 유틸리티 클래스는 코드 냄새입니다. 시맨틱에 따라 더 나은 솔루션이 있어야합니다. 어쩌면 FooUtil메소드를 넣을 FooSomething수도 있고, 더 이상 메소드가 필요하지 FooSomething않도록 속성을 변경 / 확장해야 할 수도 있습니다 FooUtil. FooSomething이 질문에 대한 올바른 답변을 제공하는 데 도움이 될만한 구체적인 예를 알려 주십시오.
valenterry

13
@valenterry : util 클래스가 코드 냄새 인 유일한 이유는 Java가 독립형 함수를 갖는 좋은 방법을 제공하지 않기 때문입니다. 클래스에 특화되지 않은 함수를 갖는 데는 매우 좋은 이유가 있습니다.
Robert Harvey

답변:


16

우선 다른 문제에 대한 다른 프로그래밍 접근법이 있다고 말하겠습니다. 저는 직업과 유지 보수를위한 강력한 OO 디자인을 선호하며 제 대답은이를 반영합니다. 기능적 접근 방식은 매우 다른 답변을 갖습니다.

메소드가 있어야하는 객체가 없기 때문에 대부분의 유틸리티 클래스가 생성되는 경향이 있습니다. 이것은 거의 항상 두 가지 경우 중 하나에서 발생합니다. 누군가가 컬렉션을 전달하거나 누군가가 콩을 전달하고 있습니다 (이것은 종종 POJO라고 부릅니다. 그러나 나는 많은 setter와 getter가 bean으로 가장 잘 묘사되어 있다고 생각합니다).

전달할 콜렉션이있을 때 해당 콜렉션의 일부가 되려는 코드가 있어야하며 이는 시스템 전체에 뿌려지고 결국 유틸리티 클래스로 수집됩니다.

내 제안은 밀접하게 연결된 다른 데이터로 컬렉션 (또는 콩)을 새로운 클래스로 감싸고 실제 유틸리티에 "유틸리티"코드를 넣는 것입니다.

콜렉션을 확장하고 메소드를 추가 할 수는 있지만 오브젝트의 상태를 제어 할 수 없게됩니다. 완전히 캡슐화하고 필요한 비즈니스 로직 메소드 만 노출하는 것이 좋습니다 ( "오브젝트에 데이터를 요청하지 마십시오. 중요한 OO 테넌트 인 객체에 명령을 내리고 개인 데이터에 대해 행동하게하십시오 "라고 생각할 때 여기에서 제안하는 접근 방식이 필요합니다).

이 "래핑"은 데이터를 보유하고 있지만 코드를 추가 할 수없는 범용 라이브러리 객체에 유용합니다. 데이터를 조작하여 코드를 퍼팅하는 것은 OO 디자인의 또 다른 주요 개념입니다.

이 줄 바꿈 개념이 좋지 않은 코딩 스타일이 많이 있습니다. 누가 한 번만 스크립트 또는 테스트를 구현할 때 전체 클래스를 작성하고 싶습니까? 그러나 본인이 코드를 추가 할 수없는 기본 유형 / 컬렉션의 캡슐화는 OO에 필수적이라고 생각합니다.


이 답변은 정말 대단합니다. 나는이 문제를 (명백하게) 가지고 있었고, 의심의 여지 없이이 대답을 읽었으며 되돌아 가서 내 코드를 살펴보고 "이 메소드를 가져야하는 객체가 무엇인지"라고 물었다. Lo와 보라, 우아한 솔루션을 발견하고 객체 모델 간격이 채워졌습니다.
GreenAsJade


@Deduplicator 40 년 동안의 프로그래밍에서 나는 너무 많은 수준의 간접적 인 인터페이스 (인터페이스가 아닌 구현으로 점프하기 위해 IDE와 가끔 싸우는 것을 제외하고)를 막는 경우를 거의 보지 못했습니다 (그러나 결코?). 종종 (Very 종종) 사람들이 명확하게 필요한 수업을 만들기보다는 끔찍한 코드를 작성하는 경우를 보았습니다. 비록 간접적 인 지시가 너무 많은 사람들을 물리 칠 수 있다고 생각하지만, 각 반이 한 가지 일을 잘 수행하는 것과 같은 적절한 OO 원칙, 적절한 격리 등의 측면에서 오류가 있습니다.
Bill K

@BillK 일반적으로 좋은 생각입니다. 그러나 탈출구가 없으면 캡슐화가 실제로 방해가 될 수 있습니다. 그리고 엄격한 OO 언어에서는 물론 OO 이외의 것은 매우 어색하지만 원하는 모든 유형에 맞는 알고리즘을 무료로 사용할 수있는 아름다움이 있습니다.
중복 제거기

@Deduplicator '유틸리티'를 작성할 때 특정 비즈니스 요구 사항을 넘어서 작성해야하는 기능이 맞습니다. 함수로 가득한 유틸리티 클래스 (예 : 콜렉션)는 데이터와 연관되지 않기 때문에 OO에서 어색합니다. 이것들은 OO에 익숙하지 않은 사람들이 사용하는 "탈출 해치"입니다 (툴 벤더는 매우 일반적인 기능을 위해 이것을 사용해야합니다). 위의 제안은 비즈니스 로직을 작업 할 때 이러한 해킹을 클래스로 감싸서 코드를 데이터와 다시 연결할 수 있다는 것입니다.
Bill K

1

당신은 제공자 패턴을 사용하여 주입 할 수 FooSomethingFooUtilProvider객체 (; 번만 생성자에있을 수 있습니다).

FooUtilProvider한 가지 방법 만 있습니다 : FooUtils get();. 그런 다음 클래스는 FooUtils제공 하는 모든 인스턴스 를 사용 합니다.

이제 DI 코인 테이너를 두 번만 연결해야하는 경우 의존성 주입 프레임 워크를 사용하지 않아도됩니다. 프로덕션 코드 및 테스트 스위트 용입니다. FooUtils인터페이스를 RealFooUtils또는에 바인딩 MockedFooUtils하면 나머지는 자동으로 발생합니다. 에 의존하는 모든 객체 FooUtilsProvider는 올바른 버전 을 가져옵니다 FooUtils.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.