문제

다음 필드를 포함하는 Customer라는 클래스가 있다고 가정해 보겠습니다.

  • 사용자 이름
  • 이메일
  • 이름

또한 비즈니스 논리에 따라 모든 Customer 개체에 이러한 네 가지 속성이 정의되어 있어야 한다고 가정해 보겠습니다.

이제 생성자가 이러한 각 속성을 지정하도록 하면 매우 쉽게 이 작업을 수행할 수 있습니다.그러나 Customer 개체에 더 많은 필수 필드를 추가해야 할 때 이것이 어떻게 통제 불능 상태가 될 수 있는지는 매우 쉽게 알 수 있습니다.

생성자에 20개 이상의 인수를 사용하는 클래스를 본 적이 있는데, 이를 사용하는 것은 정말 고통스럽습니다.그러나 이러한 필드가 필요하지 않은 경우 정의되지 않은 정보가 발생할 위험이 있거나 더 나쁜 경우 호출 코드를 사용하여 이러한 속성을 지정하는 경우 개체 참조 오류가 발생할 수 있습니다.

이에 대한 대안이 있습니까? 아니면 X개의 생성자 인수가 너무 많아서 감당할 수 없을지 결정해야 합니까?

도움이 되었습니까?

해결책

고려해야 할 두 가지 설계 접근 방식

그만큼 본질 무늬

그만큼 유창한 인터페이스 무늬

중간 개체를 천천히 구축한 다음 단일 단계로 대상 개체를 생성한다는 점에서 둘 다 의도가 비슷합니다.

유창한 인터페이스의 예는 다음과 같습니다.

public class CustomerBuilder {
    String surname;
    String firstName;
    String ssn;
    public static CustomerBuilder customer() {
        return new CustomerBuilder();
    }
    public CustomerBuilder withSurname(String surname) {
        this.surname = surname; 
        return this; 
    }
    public CustomerBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this; 
    }
    public CustomerBuilder withSsn(String ssn) {
        this.ssn = ssn; 
        return this; 
    }
    // client doesn't get to instantiate Customer directly
    public Customer build() {
        return new Customer(this);            
    }
}

public class Customer {
    private final String firstName;
    private final String surname;
    private final String ssn;

    Customer(CustomerBuilder builder) {
        if (builder.firstName == null) throw new NullPointerException("firstName");
        if (builder.surname == null) throw new NullPointerException("surname");
        if (builder.ssn == null) throw new NullPointerException("ssn");
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.ssn = builder.ssn;
    }

    public String getFirstName() { return firstName;  }
    public String getSurname() { return surname; }
    public String getSsn() { return ssn; }    
}
import static com.acme.CustomerBuilder.customer;

public class Client {
    public void doSomething() {
        Customer customer = customer()
            .withSurname("Smith")
            .withFirstName("Fred")
            .withSsn("123XS1")
            .build();
    }
}

다른 팁

어떤 사람들은 상한선으로 7을 추천하는 것 같아요.분명히 사람들이 머리 속에 일곱 가지를 동시에 담을 수 있다는 것은 사실이 아닙니다.그들은 4개만 기억할 수 있습니다(Susan Weinschenk, 모든 디자이너가 사람에 대해 알아야 할 100가지, 48).그럼에도 불구하고 나는 4개가 높은 지구 궤도에 해당한다고 생각합니다.하지만 그것은 내 생각이 밥 마틴에 의해 바뀌었기 때문입니다.

~ 안에 클린 코드, Bob 삼촌은 매개변수 수에 대한 일반적인 상한선이 3개라고 주장합니다.그는 급진적인 주장을 한다(40).

함수의 이상적인 인수 수는 0(niladic)입니다.그 다음에는 하나(모나딕)가 오고 그 다음에는 두 개(다이딕)가 옵니다.가능하다면 세 가지 논증(3극)은 피해야 합니다.3개 이상이면(다중) 매우 특별한 정당화가 필요하므로 어쨌든 사용해서는 안 됩니다.

그는 가독성 때문에 이렇게 말합니다.뿐만 아니라 테스트 가능성 때문에:

모든 다양한 인수 조합이 제대로 작동하는지 확인하기 위해 모든 테스트 사례를 작성하는 것이 어렵다는 것을 상상해 보십시오.

나는 당신이 그의 책을 찾아 함수 인수에 대한 그의 전체 논의(40-43)를 읽어보기를 권한다.

나는 단일 책임 원칙을 언급한 사람들의 의견에 동의합니다.합리적인 기본값 없이 2~3개 이상의 값/객체가 필요한 클래스에는 실제로 하나의 책임만 있고 다른 클래스를 추출해도 더 나아지지 않을 것이라고 믿기 어렵습니다.

이제 생성자를 통해 종속성을 주입하는 경우 생성자를 호출하는 것이 얼마나 쉬운지에 대한 Bob Martin의 주장은 그다지 적용되지 않습니다. 이를 수행하는 프레임워크가 있습니다.)그러나 단일 책임 원칙은 여전히 ​​유효합니다.클래스에 4개의 종속성이 있으면 그 클래스가 많은 양의 작업을 수행한다는 냄새가 나는 것으로 생각합니다.

그러나 컴퓨터 과학의 모든 것과 마찬가지로 생성자 매개변수가 많은 경우에는 의심할 여지 없이 유효한 경우가 있습니다.많은 수의 매개변수 사용을 피하기 위해 코드를 왜곡하지 마십시오.그러나 많은 수의 매개 변수를 사용하는 경우에는 코드가 이미 왜곡되었음을 의미할 수 있으므로 잠시 멈추고 생각해 보십시오.

귀하의 경우 생성자를 고수하십시오.정보는 Customer에 속하며 4개 필드는 괜찮습니다.

필수 필드와 선택 필드가 많은 경우 생성자는 최선의 솔루션이 아닙니다.@boojiboy가 말했듯이 읽기도 어렵고 클라이언트 코드 작성도 어렵습니다.

@contagious는 선택적 속성에 기본 패턴과 설정자를 사용할 것을 제안했습니다.이는 필드를 변경할 수 있도록 요구하지만 이는 사소한 문제입니다.

Effective Java 2의 Joshua Block은 이 경우 빌더를 고려해야 한다고 말합니다.책에서 가져온 예:

 public class NutritionFacts {  
   private final int servingSize;  
   private final int servings;  
   private final int calories;  
   private final int fat;  
   private final int sodium;  
   private final int carbohydrate;  

   public static class Builder {  
     // required parameters  
     private final int servingSize;  
     private final int servings;  

     // optional parameters  
     private int calories         = 0;  
     private int fat              = 0;  
     private int carbohydrate     = 0;  
     private int sodium           = 0;  

     public Builder(int servingSize, int servings) {  
      this.servingSize = servingSize;  
       this.servings = servings;  
    }  

     public Builder calories(int val)  
       { calories = val;       return this; }  
     public Builder fat(int val)  
       { fat = val;            return this; }  
     public Builder carbohydrate(int val)  
       { carbohydrate = val;   return this; }  
     public Builder sodium(int val)  
       { sodium = val;         return this; }  

     public NutritionFacts build() {  
       return new NutritionFacts(this);  
     }  
   }  

   private NutritionFacts(Builder builder) {  
     servingSize       = builder.servingSize;  
     servings          = builder.servings;  
     calories          = builder.calories;  
     fat               = builder.fat;  
     soduim            = builder.sodium;  
     carbohydrate      = builder.carbohydrate;  
   }  
}  

그런 다음 다음과 같이 사용하십시오.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
      calories(100).sodium(35).carbohydrate(27).build();

위의 예는 다음에서 가져온 것입니다. 효과적인 자바 2

그리고 이는 생성자에만 적용되는 것이 아닙니다.켄트 벡(Kent Beck) 인용 구현 패턴:

setOuterBounds(x, y, width, height);
setInnerBounds(x + 2, y + 2, width - 4, height - 4);

직사각형을 객체로 명시적으로 지정하면 코드가 더 잘 설명됩니다.

setOuterBounds(bounds);
setInnerBounds(bounds.expand(-2));

내 생각에 "순수 OOP" 대답은 특정 멤버가 초기화되지 않았을 때 클래스에 대한 작업이 유효하지 않은 경우 생성자가 이러한 멤버를 설정해야 한다는 것입니다.기본값을 사용할 수 있는 경우는 항상 있지만 해당 경우는 고려하지 않는다고 가정하겠습니다.이는 API가 수정된 경우 좋은 접근 방식입니다. API가 공개된 후 허용되는 단일 생성자를 변경하는 것은 귀하와 코드의 모든 사용자에게 악몽이 될 것이기 때문입니다.

C#에서 디자인 지침에 대해 제가 이해한 바는 이것이 반드시 상황을 처리하는 유일한 방법은 아니라는 것입니다.특히 WPF 개체의 경우 .NET 클래스는 매개 변수가 없는 생성자를 선호하는 경향이 있으며 메서드를 호출하기 전에 데이터가 원하는 상태로 초기화되지 않은 경우 예외를 발생시킵니다.이는 아마도 주로 구성요소 기반 설계에만 해당될 것입니다.이런 방식으로 동작하는 .NET 클래스의 구체적인 예를 생각해낼 수 없습니다.귀하의 경우 속성의 유효성이 검사되지 않으면 클래스가 데이터 저장소에 저장되지 않도록 테스트하는 데 확실히 부담이 커질 것입니다.솔직히 말해서 API가 확실하게 설정되어 있거나 공개되지 않은 경우 "생성자가 필수 속성을 설정"하는 접근 방식을 선호합니다.

내가 한 가지 ~이다 확실한 것은 이 문제를 해결할 수 있는 방법론이 셀 수 없을 정도로 많으며, 각 방법론마다 고유한 문제가 있다는 것입니다.가장 좋은 방법은 가능한 한 많은 패턴을 배우고 작업에 가장 적합한 패턴을 선택하는 것입니다.(그렇게 대답을 회피하는 것이 아닌가요?)

귀하의 질문은 생성자의 인수 수에 관한 것보다 클래스 디자인에 관한 것 같습니다.객체를 성공적으로 초기화하기 위해 20개의 데이터(인수)가 필요하다면 클래스를 분할하는 것을 고려할 것입니다.

Steve McConnell은 Code Complete에서 사람들이 한 번에 7가지 이상을 머릿속에 유지하는 데 어려움을 겪는다고 썼습니다. 그래서 저는 그 숫자를 유지하려고 노력합니다.

인수가 너무 많으면 구조체/POD 클래스로 함께 패키징하고, 생성 중인 클래스의 내부 클래스로 선언하는 것이 좋습니다.이렇게 하면 생성자를 호출하는 코드를 합리적으로 읽을 수 있게 만들면서 필드를 계속 요구할 수 있습니다.

나는 모든 것이 상황에 달려 있다고 생각합니다.귀하의 예와 같은 고객 클래스의 경우 필요할 때 해당 데이터가 정의되지 않을 위험이 없습니다.반대로, 구조체를 전달하면 인수 목록이 지워지지만 구조체에 정의할 항목은 여전히 ​​많습니다.

가장 쉬운 방법은 각 값에 대해 허용 가능한 기본값을 찾는 것이라고 생각합니다.이 경우 각 필드는 구성이 필요한 것처럼 보이므로 호출에 정의되지 않은 항목이 있으면 기본값으로 설정하도록 함수 호출을 오버로드할 수 있습니다.

그런 다음 기본값을 변경할 수 있도록 각 속성에 대해 getter 및 setter 함수를 만듭니다.

자바 구현:

public static void setEmail(String newEmail){
    this.email = newEmail;
}

public static String getEmail(){
    return this.email;
}

이는 전역 변수를 안전하게 유지하는 좋은 습관이기도 합니다.

스타일은 매우 중요하며, 인수가 20개 이상인 생성자가 있는 경우 디자인을 변경해야 하는 것 같습니다.합리적인 기본값을 제공하십시오.

나는 Boojiboy가 언급한 7개 항목 제한에 동의합니다.그 외에도 익명(또는 특수) 유형, IDictionary 또는 기본 키를 통해 다른 데이터 소스에 대한 간접 참조를 살펴보는 것이 좋습니다.

나는 자체 구성/검증 논리를 사용하여 유사한 필드를 자체 객체로 캡슐화합니다.

예를 들어,

  • 비즈니스 용 폰
  • 사업장 주소
  • 집 전화
  • 집 주소

"집" 또는 "회사" 전화/주소를 지정하는 태그와 함께 전화 및 주소를 저장하는 클래스를 만들겠습니다.그런 다음 4개 필드를 단순한 배열로 줄입니다.

ContactInfo cinfos = new ContactInfo[] {
    new ContactInfo("home", "+123456789", "123 ABC Avenue"),
    new ContactInfo("biz", "+987654321", "789 ZYX Avenue")
};

Customer c = new Customer("john", "doe", cinfos);

그러면 덜 스파게티처럼 보일 것입니다.

확실히 필드가 많다면 자체적으로 멋진 기능 단위를 만들 수 있는 패턴을 추출할 수 있어야 합니다.그리고 더 읽기 쉬운 코드도 만드세요.

또한 다음과 같은 해결 방법도 가능합니다.

  • 단일 클래스에 저장하는 대신 유효성 검사 논리를 분산시킵니다.사용자가 입력하면 유효성을 검사한 다음 데이터베이스 계층 등에서 다시 유효성을 검사합니다.
  • 을 만들다 CustomerFactory 내가 구성하는 데 도움이 되는 수업 Customer에스
  • @marcio의 솔루션도 흥미 롭습니다 ...

그냥 기본 인수를 사용하세요.기본 메서드 인수(예: PHP)를 지원하는 언어에서는 메서드 시그니처에서 이 작업을 수행할 수 있습니다.

public function doSomethingWith($this = val1, $this = val2, $this = val3)

메소드 오버로딩을 지원하는 언어와 같이 기본값을 생성하는 다른 방법이 있습니다.

물론 적절하다고 판단되면 필드를 선언할 때 기본값을 설정할 수도 있습니다.

실제로는 이러한 기본값을 설정하는 것이 적절한지 여부 또는 객체를 구성할 때 항상 지정해야 하는지 여부가 결정됩니다.그것은 정말로 당신만이 내릴 수 있는 결정입니다.

인수가 2개 이상인 경우를 제외하고는 항상 배열이나 객체를 생성자 매개변수로 사용하고 오류 검사를 통해 필수 매개변수가 있는지 확인합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top