인수 개수를 어떻게 낮게 유지하면서도 타사의 종속성을 개별적으로 유지합니까?


13

타사 라이브러리를 사용합니다. 그들은 우리의 의도와 목적을 위해 아마도 다음과 같이 구현 되는 POJO 를 전달 합니다.

public class OurData {
  private String foo;
  private String bar;
  private String baz;
  private String quux;
  // A lot more than this

  // IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
  OurData(/* I don't know what they do */) {
    // some stuff
  }

  public String getFoo() {
    return foo;
  }

  // etc.
}

API 캡슐화 및 단위 테스트 촉진을 포함한 여러 가지 이유로 인해 데이터를 래핑하고 싶습니다. 그러나 핵심 클래스가 데이터에 의존하기를 원하지 않습니다 (다시 테스트 목적으로)! 그래서 지금 나는 이와 같은 것을 가지고 있습니다 :

public class DataTypeOne implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

public class DataTypeTwo implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz, String quux) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
    this.quux = quux;
  }
}

그리고 이것은 :

public class ThirdPartyAdapter {
  public static makeMyData(OurData data) {
    if(data.getQuux() == null) {
      return new DataTypeOne(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
      );
    } else {
      return new DataTypeTwo(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
        data.getQuux();
      );
  }
}

이 어댑터 클래스는 타사 API에 대해 알아야하는 다른 몇 가지 클래스와 결합되어 내 시스템의 나머지 부분을 통해 퍼짐성을 제한합니다. 그러나 ...이 솔루션은 GROSS입니다! 클린 코드에서, 페이지 40 :

3 개 이상의 주장 (다항식)은 매우 특별한 타당성을 요구하므로 어쨌든 사용해서는 안됩니다.

내가 고려한 것들 :

  • 정적 헬퍼 메소드가 아닌 팩토리 오브젝트 작성
    • bajillion 인수를 갖는 문제를 해결하지 못함
  • 종속 생성자가있는 DataTypeOne 및 DataTypeTwo의 서브 클래스 작성
    • 여전히 polyadic protected 생성자가 있습니다
  • 동일한 인터페이스를 따르는 완전히 별도의 구현을 만듭니다.
  • 위의 여러 아이디어를 동시에

이 상황을 어떻게 처리해야합니까?


이것은 부패 방지 계층 상황 이 아닙니다 . API에는 아무런 문제가 없습니다. 문제는 다음과 같습니다.

  • 내 데이터 구조를 원하지 않습니다 import com.third.party.library.SomeDataStructure;
  • 테스트 사례에서 데이터 구조를 구성 할 수 없습니다
  • 내 현재 솔루션은 매우 높은 인수 수를 초래합니다. 데이터 구조를 전달하지 않고 인수 수를 낮추고 싶습니다.
  • 그 질문은 " 어떤 안티 손상 층인가?". 제 질문은 " 이 시나리오를 해결하기 위해 패턴, 패턴을 어떻게 사용할 수 있습니까?"입니다.

나는 코드를 요구하지 않고 (그렇지 않으면이 질문은 SO에있을 것입니다), 효과적으로 코드를 작성할 수 있도록 충분한 답변을 요구합니다 (그 질문은 제공하지 않습니다).


이러한 써드 파티 POJO가 여러 개인 경우, 일부 규칙 (예 : 키 이름을 int_bar로 지정)을 테스트 입력으로 사용하여 맵을 사용하는 사용자 정의 테스트 코드를 작성하는 것이 좋습니다. 또는 일부 사용자 정의 중개 코드와 함께 JSON 또는 XML을 사용하십시오. 실제로, com.thirdparty 테스트를위한 일종의 DSL입니다.
user949300

Clean Code의 전체 인용문 :The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Lilienthal

11
패턴 또는 프로그래밍 지침에 대한 맹목적인 준수는 자체 안티 패턴 입니다.
Lilienthal

2
"API를 캡슐화하고 단위 테스트를 용이하게"이와 같은 소리가 나에게 과도 테스트 및 / 또는 테스트로 인한 디자인 손상의 경우 일 수 있습니다. 스스로에게 물어보십시오 : 이것이 실제로 코드를 이해하고 변경하고 재사용하기 쉽게합니까? 나는 돈을 "아니오"에 넣었다. 이 라이브러리를 교체 할 가능성은 얼마나 현실적입니까? 아마별로. 스왑 아웃하면 실제로 완전히 다른 것을 쉽게 놓을 수 있습니까? 다시, 나는 "아니오"에 내기를 걸었습니다.
jpmc26

1
@JamesAnderson 나는 그것이 흥미 로웠 기 때문에 전체 인용문을 재현했지만 일반 함수 또는 생성자를 구체적으로 언급했는지 여부는 스 니펫에서 명확하지 않았습니다. 나는 그 주장을지지 할 의도가 없었으며, jpmc26이 말했듯이, 다음 의견에서는 내가 그렇게하지 않았다는 표시를해주어야한다. 나는 왜 당신이 학계를 공격 할 필요성을 느끼고 있는지 확신하지 못하지만, 다음 절을 사용한다고해서 누군가 구름 위의 상아탑에 자리 잡은 학문적 엘리트 주의자가되지는 않습니다.
Lilienthal

답변:


10

여러 초기화 매개 변수가있을 때 사용한 전략은 초기화 매개 변수 만 포함하는 형식을 만드는 것입니다.

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

그런 다음 DataTypeTwo의 생성자는 DataTypeTwoParameters 객체를 가져오고 DataTypeTwo는 다음을 통해 구성됩니다.

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

이를 통해 DataTypeTwo에 들어가는 모든 매개 변수와 그 의미를 명확하게 확인할 수 있습니다. 또한 DataTypeTwoParameters 생성자에서 적절한 기본값을 제공하여 설정해야하는 값만 API 소비자가 원하는 순서대로 수행 할 수 있습니다.


재미있는 접근법. 어디에 관련을 두 Integer.parseInt시겠습니까? 세터에서 또는 매개 변수 클래스 외부에서?
durron597

5
매개 변수 클래스 외부 parameters 클래스는 "dumb"객체 여야하며 필요한 입력과 해당 유형을 표현하는 것 이외의 다른 작업을 시도해서는 안됩니다. 다음과 같이 다른 곳에서 구문 분석을 수행해야합니다 p.bar = Integer.parseInt("4").
Erik

7
이것은 매개 변수 객체 패턴 처럼 들립니다
gnat

9
... 또는 안티 패턴.
Telastyn

1
... 또는로 이름 DataTypeTwoParameters을 바꿀 수 DataTypeTwo있습니다.
user253751

14

여기에는 API를 래핑하고 인수 수를 낮게 유지하는 두 가지 별도의 문제가 있습니다.

API를 래핑 할 때 요구 사항 외에는 아무것도 모르고 인터페이스를 처음부터 마치 디자인하는 것이 좋습니다. API에는 아무런 문제가 없다고 말한 다음 동일한 호흡 목록에 API에 대한 여러 가지 잘못된 점이 있습니다. 테스트 가능성, 구성 가능성, 한 객체에 너무 많은 매개 변수 등 원하는 API를 작성하십시오 . 하나가 아닌 여러 개체가 필요한 경우 그렇게하십시오. POJO 를 작성 하는 오브젝트에 한 레벨 높은 랩핑이 필요한 경우이를 수행하십시오.

그런 다음 원하는 API를 확보 한 후에는 매개 변수 수가 더 이상 문제가되지 않을 수 있습니다. 그렇다면, 고려해야 할 몇 가지 일반적인 패턴이 있습니다.

  • Erik의 답변 과 같은 매개 변수 객체 .
  • 빌더 패턴 별도의 빌더 객체를 생성 한 후 다음, 개별적으로 매개 변수를 설정 최종 객체를 생성하는 세터의 번호로 전화를 걸 수 있습니다.
  • 필드가 이미 내부적으로 설정된 상태에서 원하는 객체의 서브 클래스를 복제 하는 프로토 타입 패턴
  • 이미 익숙한 공장.
  • 위의 일부 조합.

이러한 생성 패턴은 종종 polyadic 생성자를 호출하게되므로 캡슐화 할 때 괜찮은 것으로 간주해야합니다. polyadic 생성자의 문제는 한 번만 호출하는 것이 아니라 개체를 생성해야 할 때마다 호출해야하는 경우입니다.

일반적으로 OurData내부를 다시 구현하지 않고 객체에 대한 참조를 저장 하고 메서드 호출을 전달하여 기본 API로 전달하는 것이 훨씬 쉽고 유지 관리가 쉽습니다 . 예를 들면 다음과 같습니다.

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}

이 답변의 전반부 : 훌륭하고 도움이되는 +1. 이 답변의 후반부 : " OurData객체에 대한 참조를 저장하여 기본 API로 전달하십시오. "-최소한 종속성이 없도록 기본 클래스에서 피하려고합니다.
durron597

1
그렇기 때문에 구현 중 하나에서만 수행하는 것입니다 DataInterface. 모의 객체에 대한 다른 구현을 만듭니다.
Karl Bielefeldt

@ durron597 : 그렇습니다.하지만 실제로 문제를 해결하는 방법을 이미 알고 있습니다.
Doc Brown

1

나는 당신이 밥 삼촌의 권고를 너무 엄격하게 해석하고 있다고 생각합니다. 논리와 메소드, 생성자 등을 포함한 일반 클래스의 경우 다항식 생성자는 실제로 코드 냄새와 매우 흡사합니다. 그러나 엄격하게 필드를 노출하는 데이터 컨테이너이며 본질적으로 팩토리 객체 인 것으로 생성 된 것이라면 너무 나쁘지 않다고 생각합니다.

당신은 할 수 코멘트에 제안입니다 래퍼 해당 지역의 데이터 유형이 무엇인지 당신을 위해이 생성자 매개 변수, 포장 수, 매개 변수 개체 패턴을 사용 이미 , 본질적으로 매개 변수 개체를. 모든 Parameter 객체는 매개 변수를 포장하고 (어떻게 만들까요? polyadic 생성자를 사용합니까?) 두 번째 나중에 거의 동일한 객체에 포장을 풉니 다.

필드에 대한 setter를 노출하고 호출하지 않으려면 잘 정의되고 캡슐화 된 팩토리 내의 polyadic 생성자를 고수하는 것이 좋습니다.


문제는 데이터 구조의 필드 수가 여러 번 변경되어 다시 변경 될 수 있다는 것입니다. 즉, 모든 테스트 사례에서 생성자를 리팩터링해야합니다. 합리적인 기본값을 가진 매개 변수 패턴은 더 나은 방법으로 들립니다. 변경 불가능한 형태로 저장되는 변경 가능한 버전이 있으면 여러 가지 방법으로 내 인생을 더 쉽게 만들 수 있습니다.
durron597
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.