API 디자인에서“너무 많은 매개 변수”문제를 피하는 방법은 무엇입니까?


160

이 API 기능이 있습니다.

public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d, 
     string e, string f, out Guid code)

나는 그것을 좋아하지 않는다. 매개 변수 순서가 불필요하게 중요해지기 때문입니다. 새 필드를 추가하기가 더 어려워집니다. 전달되는 내용을 확인하기가 더 어렵습니다. 하위 함수의 모든 매개 변수를 전달하는 또 다른 오버 헤드를 생성하기 때문에 메소드를 더 작은 부분으로 리팩토링하기가 더 어렵습니다. 코드를 읽기가 더 어렵습니다.

가장 명확한 아이디어를 생각해 냈습니다. 데이터를 캡슐화하는 객체를 가지고 각 매개 변수를 하나씩 전달하는 대신 전달하십시오. 다음은 내가 생각해 낸 것입니다.

public class DoSomeActionParameters
{
    public string A;
    public string B;
    public DateTime C;
    public OtherEnum D;
    public string E;
    public string F;        
}

그것은 내 API 선언을 다음과 같이 줄였습니다.

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

좋은. 무고 해 보이지만 실제로는 큰 변화가있었습니다. 이전에 수행 한 작업은 실제로 익명의 불변 개체를 스택에 전달하는 것이 었습니다. 이제 우리는 매우 변경 가능한 새로운 클래스를 만들었습니다. 우리는 호출자 의 상태를 조작하는 기능을 만들었습니다 . 짜증나 이제 객체를 변경할 수 없게하려면 어떻게해야합니까?

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }        

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, 
     string e, string f)
    {
        this.A = a;
        this.B = b;
        // ... tears erased the text here
    }
}

보시다시피 실제로 원래 문제인 너무 많은 매개 변수를 다시 생성했습니다. 그것이가는 길이 아니라는 것은 명백합니다. 내가 무엇을 할까? 이러한 불변성을 달성하기위한 마지막 옵션은 다음과 같이 "읽기 전용"구조체를 사용하는 것입니다.

public struct DoSomeActionParameters
{
    public readonly string A;
    public readonly string B;
    public readonly DateTime C;
    public readonly OtherEnum D;
    public readonly string E;
    public readonly string F;        
}

이를 통해 매개 변수가 너무 많은 생성자를 피하고 불변성을 달성 할 수 있습니다. 실제로 모든 문제를 해결합니다 (매개 변수 순서 등). 아직:

그때 혼란스러워서이 질문을하기로 결정했을 때 : C #에서 변이성을 도입하지 않고 "너무 많은 매개 변수"문제를 피하는 가장 간단한 방법은 무엇입니까? 그 목적으로 읽기 전용 구조체를 사용할 수 있지만 API 디자인이 좋지 않습니까?

설명 :

  • 단일 책임 원칙을 위반하지 않는다고 가정하십시오. 원래의 경우 함수는 주어진 매개 변수를 단일 DB 레코드에 씁니다.
  • 주어진 기능에 대한 특정 솔루션을 찾고 있지 않습니다. 이러한 문제에 대한 일반적인 접근 방식을 찾고 있습니다. 나는 가변성이나 끔찍한 디자인을 도입하지 않고 "너무 많은 매개 변수"문제를 해결하는 데 특히 관심이 있습니다.

최신 정보

여기에 제공된 답변은 서로 다른 장점 / 단점이 있습니다. 따라서 이것을 커뮤니티 위키로 변환하고 싶습니다. 코드 샘플과 찬반 양론으로 각 대답은 앞으로 비슷한 문제에 대한 좋은 가이드를 만들 것이라고 생각합니다. 나는 지금 그것을하는 방법을 찾으려고 노력하고있다.


Clean Code : Robert C. Martin과 Martin Fowler의 리팩토링 책에 의한 Agile Software Craftsmanship 핸드북은이 내용을 다루고 있습니다.
Ian Ringrose

1
선택적 매개 변수 가있는 C # 4를 사용하면 Builder 솔루션이 중복되지 않습니까?
khellang

1
어리석은 일이지만 DoSomeActionParameters메소드 호출 후 버릴 객체가 버림 객체 라는 것을 고려할 때 이것이 어떻게 문제인지 알지 못합니다 .
Vilx-

6
구조체에서 읽기 전용 필드를 피해야한다고 말하는 것은 아닙니다. 불변 구조체는 모범 사례이며 읽기 전용 필드는 자체 문서화 불변 구조체를 만드는 데 도움이됩니다. 내 요점은 읽기 전용 필드가 절대 변경되지 않는 것으로 관찰되는 읽기 전용 필드에 의존해서는 안된다는 것 입니다. 읽기 전용 필드가 구조체에서 제공한다는 보장은 아니기 때문입니다. 이것은 값 유형이 참조 유형 인 것처럼 취급해서는 안된다는보다 일반적인 조언의 특정 사례입니다. 그들은 매우 다른 동물입니다.
Eric Lippert

7
@ ssg : 나도 그것을 좋아합니다. 우리는 C #에 LINQ와 같은 불변성을 촉진하는 기능을 추가했으며 동시에 객체 초기화와 같은 변이를 촉진하는 기능을 추가했습니다. 불변의 유형을 홍보하는 더 좋은 구문을 갖는 것이 좋을 것입니다. 우리는 그것에 대해 열심히 생각하고 흥미로운 아이디어를 가지고 있지만 다음 버전에서는 그런 것을 기대하지 않습니다.
Eric Lippert

답변:


22

프레임 워크에 포함 된 한 스타일은 일반적으로 관련 매개 변수를 관련 클래스로 그룹화하는 것과 같습니다 (그러나 다시 가변성에 문제가 있음).

var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects

모든 문자열을 문자열 배열로 응축하면 모두 관련이있는 경우에만 의미가 있습니다. 논쟁의 순서에서, 그것들은 모두 완전히 관련되지 않은 것처럼 보입니다.
icktoofay

매개 변수와 동일한 유형이 많은 경우에만 작동합니다.
Timo Willemsen 2016 년

질문의 첫 번째 예와 어떻게 다른가요?
Sedat Kapanoglu 2016 년

이것은 .NET Framework에서도 일반적으로 수용되는 스타일이며, 첫 번째 예제는 약간의 변경 가능성 문제가 발생하더라도 (이 예제는 분명히 다른 라이브러리의 것이지만) 수행중인 작업에 매우 적합하다는 것을 나타냅니다. 그런데 좋은 질문입니다.
Teoman Soygul 2016 년

2
나는 시간이 지남에 따라이 스타일에 더 집중하고 있습니다. 논리적 그룹과 추상화를 통해 "너무 많은 매개 변수"문제를 상당 부분 해결할 수 있습니다. 결국 코드를 ​​더 읽기 쉽고 모듈화합니다.
Sedat Kapanoglu

87

빌더 및 도메인 별 언어 스타일 API--Fluent Interface의 조합을 사용하십시오. API는 조금 더 장황하지만 지능이 뛰어나 타이핑이 빠르고 이해하기 쉽습니다.

public class Param
{
        public string A { get; private set; }
        public string B { get; private set; }
        public string C { get; private set; }


  public class Builder
  {
        private string a;
        private string b;
        private string c;

        public Builder WithA(string value)
        {
              a = value;
              return this;
        }

        public Builder WithB(string value)
        {
              b = value;
              return this;
        }

        public Builder WithC(string value)
        {
              c = value;
              return this;
        }

        public Param Build()
        {
              return new Param { A = a, B = b, C = c };
        }
  }


  DoSomeAction(new Param.Builder()
        .WithA("a")
        .WithB("b")
        .WithC("c")
        .Build());

+1-이런 종류의 접근 방식 (일반적으로 "유창한 인터페이스"라고 함)은 내가 생각한 것과 정확히 같습니다.
Daniel Pryden 2016 년

+1-이것이 같은 상황에서 끝난 것입니다. 단 하나의 클래스 만 DoSomeAction있었고 그 방법이있었습니다.
Vilx-

+1 나도 이것에 대해 생각했지만, 유창한 인터페이스를 처음 사용했기 때문에 이것이 여기에 완벽하게 맞는지 몰랐습니다. 내 직관을 확인해 주셔서 감사합니다.
Matt

나는이 질문을하기 전에 빌더 패턴에 대해 들어 보지 못했기 때문에 이것이 통찰력있는 경험이었습니다. 얼마나 흔한 지 궁금합니다. 패턴에 대해 듣지 못한 개발자는 문서없이 사용법을 이해하는 데 어려움을 겪을 수 있습니다.
Sedat Kapanoglu

1
@ ssg, 이러한 시나리오에서 일반적입니다. 예를 들어 BCL에는 연결 문자열 작성기 클래스가 있습니다.
Samuel Neff

10

당신이 가지고있는 것은 문제의 클래스 가 너무 많은 의존성을 가지고 있기 때문에 단일 책임 원칙을 위반하고 있음을 분명히 나타냅니다 . 이러한 종속성을 Facade Dependency의 클러스터로 리팩토링하는 방법을 찾으십시오 .


1
나는 여전히 하나 이상의 매개 변수 객체를 도입하여 리팩토링하지만 모든 인수를 단일 불변 유형으로 이동하면 아무것도 달성하지 못합니다. 트릭은 더 밀접한 관련이있는 인수 클러스터를 찾은 다음 각 클러스터를 별도의 매개 변수 객체로 리팩토링하는 것입니다.
Mark Seemann 2016 년

2
각 그룹 자체를 변경할 수없는 개체로 바꿀 수 있습니다. 각 객체는 몇 개의 매개 변수 만 가져야하므로 실제 인수 수는 동일하게 유지되지만 단일 생성자에 사용되는 인수 수는 줄어 듭니다.
Mark Seemann

2
+1 @ssg : 내가 이런 식으로 자신을 설득 할 때마다, 나는이 수준의 매개 변수를 요구하는 큰 방법에서 유용한 추상화를 이끌어 냄으로써 시간이 지남에 따라 나 자신을 잘못 증명했습니다. Evans의 DDD 책은 이것에 대해 어떻게 생각하는지에 대한 아이디어를 줄 수 있습니다 (시스템은 잠재적으로 관련 장소에서 그러한 패턴의 적용에 이르기까지 멀지 않은 것처럼 들립니다).
Ruben Bartelink

3
@Ruben : 제정신의 책은 "좋은 OO 디자인에서 클래스는 5 개 이상의 속성을 가져서는 안된다"고 말할 수 없습니다. 클래스는 논리적으로 그룹화되며 이러한 컨텍스트 화는 수량을 기반으로 할 수 없습니다. 그러나 C #의 불변성 지원은 좋은 OO 디자인 원칙을 위반 하기 전에 특정 수의 속성에서 문제를 일으키기 시작합니다.
Sedat Kapanoglu

3
@Ruben : 나는 당신의 지식, 태도 또는 성미를 판단하지 않았습니다. 나는 당신이 똑같이 할 것으로 기대합니다. 나는 당신의 추천이 좋지 않다고 말하지 않습니다. 내 문제는 가장 완벽한 디자인에서도 나타날 수 있으며 여기서 간과되는 것으로 보입니다. 숙련 된 사람들이 가장 일반적인 실수에 대해 근본적인 질문을하는 이유를 이해하지만 몇 번의 라운드 후에는 명확하게 설명하기가 쉽지 않습니다. 완벽한 디자인으로 그 문제를 겪는 것이 가능하다는 것을 다시 말해야합니다. 공감에 감사드립니다!
Sedat Kapanoglu

10

매개 변수 데이터 구조를 a에서 a class로 변경하기 만하면 struct됩니다.

public struct DoSomeActionParameters 
{
   public string A;
   public string B;
   public DateTime C;
   public OtherEnum D;
   public string E;
   public string F;
}

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code) 

메소드는 이제 자체 구조 사본을 얻습니다. 인수 변수에 대한 변경 사항은 메소드에 의해 관찰 될 수 없으며, 변수에 대한 메소드 변경 사항은 호출자가 관찰 할 수 없습니다. 불변성없이 격리가 달성됩니다.

장점 :

  • 가장 쉬운 구현
  • 기초 역학에서의 행동의 최소 변화

단점 :

  • 불변성은 분명하지 않으며 개발자의주의가 필요합니다.
  • 불변성을 유지하기 위해 불필요한 복사
  • 점유 공간

+1 그건 사실이지만 "필드를 직접 노출시키는 것은 악하다"는 부분을 해결하지 못합니다. 해당 API의 속성 대신 필드를 사용하도록 선택하면 상황이 얼마나 나빠질 지 잘 모르겠습니다.
Sedat Kapanoglu

@ssg-필드 대신 공개 속성으로 만듭니다. 이것을 코드가없는 구조체로 취급하면 속성이나 필드를 사용하는지 여부에 큰 차이가 없습니다. 코드 (예 : 유효성 검사 또는 무언가)를 제공하기로 결정한 경우 속성을 지정해야합니다. 적어도 공공 장소에서는 아무도이 구조에 존재하는 불변에 대한 환상을 가질 수 없습니다. 매개 변수와 똑같은 방법으로 검증해야합니다.
Jeffrey L Whitledge

아 맞다. 대박! 감사.
Sedat Kapanoglu

8
노출 필드는 사악한 규칙이 객체 지향 디자인의 객체에 적용된다고 생각합니다. 내가 제안하는 구조체는 단순한 매개 변수 매개 변수 컨테이너입니다. 당신의 본능은 불변의 무언가와 함께 가야했기 때문에 그런 기본 컨테이너가이 경우에 적합하다고 생각합니다.
Jeffrey L Whitledge

1
@ JeffreyLWhitledge : 노출 된 필드 구조체가 악하다는 생각을 정말로 싫어합니다. 이러한 주장은 스크루 드라이버가 악하다고 말하고 스크루 드라이버의 포인트가 네일 헤드를 움켜 쥐기 때문에 사람들이 망치를 사용해야한다는 말과 같습니다. 손톱을 운전 해야하는 경우 망치를 사용해야하지만 나사를 운전 해야하는 경우 드라이버를 사용해야합니다. 노출 된 필드 구조체가 작업에 적합한 도구 인 경우가 많이 있습니다. 덧붙여, GET / SET 특성을 가진 구조체가 적합한 도구 (대부분의 경우 ... 정말 어디 훨씬 적은있다
supercat

6

데이터 클래스 내에 빌더 클래스를 작성하는 것은 어떻습니까. 데이터 클래스에는 모든 세터가 개인용으로 있으며 빌더 만 설정할 수 있습니다.

public class DoSomeActionParameters
    {
        public string A { get; private set; }
        public string B  { get; private set; }
        public DateTime C { get; private set; }
        public OtherEnum D  { get; private set; }
        public string E  { get; private set; }
        public string F  { get; private set; }

        public class Builder
        {
            DoSomeActionParameters obj = new DoSomeActionParameters();

            public string A
            {
                set { obj.A = value; }
            }
            public string B
            {
                set { obj.B = value; }
            }
            public DateTime C
            {
                set { obj.C = value; }
            }
            public OtherEnum D
            {
                set { obj.D = value; }
            }
            public string E
            {
                set { obj.E = value; }
            }
            public string F
            {
                set { obj.F = value; }
            }

            public DoSomeActionParameters Build()
            {
                return obj;
            }
        }
    }

    public class Example
    {

        private void DoSth()
        {
            var data = new DoSomeActionParameters.Builder()
            {
                A = "",
                B = "",
                C = DateTime.Now,
                D = testc,
                E = "",
                F = ""
            }.Build();
        }
    }

1
+1 완벽하게 유효한 솔루션이지만, 단순한 디자인 결정을 유지하기에는 비계가 너무 많다고 생각합니다. 특히 "읽기 전용 구조체"솔루션이 "매우"이상적 일 때.
Sedat Kapanoglu 2016 년

2
"너무 많은 매개 변수"문제를 어떻게 해결합니까? 구문이 다를 수 있지만 문제는 동일하게 보입니다. 이것은 비판이 아닙니다. 저는이 패턴에 익숙하지 않기 때문에 궁금합니다.
alexD

1
@alexD 이것은 너무 많은 함수 매개 변수를 가지고 있고 객체를 불변으로 유지하는 문제를 해결합니다. 빌더 클래스 만 개인 특성을 설정할 수 있으며 매개 변수 오브젝트를 확보 한 후에는 변경할 수 없습니다. 문제는 많은 스캐 폴딩 코드가 필요하다는 것입니다.
marto

5
솔루션의 매개 변수 개체는 변경할 수 없습니다. 도 구축 후 빌더 캔 편집 매개 변수를 저장 사람
astef

1
@astef는 현재 DoSomeActionParameters.Builder인스턴스가 작성되어 있으므로 정확히 하나의 DoSomeActionParameters인스턴스 를 만들고 구성하는 데 사용할 수 있습니다 . 의 속성 Build()에 대한 후속 변경 사항을 호출 한 후에 Builder원래 DoSomeActionParameters 인스턴스의 속성 이 계속 수정 되며 이후의 호출 Build()은 동일한 DoSomeActionParameters인스턴스를 계속 반환합니다 . 정말이어야합니다 public DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }.
BACON

6

나는 C # 프로그래머는 아니지만 C #이 명명 된 인수를 지원한다고 생각합니다. (F #은 C #은 주로 이런 종류의 기능에 적합합니다) http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342

따라서 원래 코드를 호출하면 다음과 같습니다.

public ResultEnum DoSomeAction( 
 e:"bar", 
 a: "foo", 
 c: today(), 
 b:"sad", 
 d: Red,
 f:"penguins")

이것은 더 이상 공간을 필요로하지 않으며 객체가 생성되고 모든 혜택을 누릴 수 있습니다. 왜냐하면 당신은 비 계측 시스템에서 일어나는 일을 전혀 변경하지 않았다는 사실입니다. 인수의 이름을 나타 내기 위해 아무것도 코딩 할 필요조차 없습니다.

편집 : 여기에 내가 찾은 예술이 있습니다. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ C # 4.0에서 명명 된 인수를 지원한다고 언급해야합니다. 3.0은 그렇지 않습니다.


실제로 개선. 그러나 이것은 코드 가독성을 해결하고 개발자가 명명 된 매개 변수를 사용하도록 선택할 때만 가능합니다. 이름 지정을 잊어 버리고 혜택을주는 것은 쉬운 일입니다. 코드 자체를 작성할 때는 도움이되지 않습니다. 예를 들어 함수를 더 작은 것으로 리팩토링하고 포함 된 단일 패킷으로 데이터를 전달할 때.
Sedat Kapanoglu 2016 년

6

왜 불변성을 강제하는 인터페이스를 만드는가 (즉, 게터 만)?

본질적으로 첫 번째 솔루션이지만 인터페이스를 사용하여 매개 변수에 액세스하도록 함수를 강제합니다.

public interface IDoSomeActionParameters
{
    string A { get; }
    string B { get; }
    DateTime C { get; }
    OtherEnum D { get; }
    string E { get; }
    string F { get; }              
}

public class DoSomeActionParameters: IDoSomeActionParameters
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }        
}

함수 선언은 다음과 같습니다.

public ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)

장점 :

  • struct솔루션 과 같은 스택 공간 문제가 없습니다.
  • 언어 시맨틱을 사용한 자연스러운 솔루션
  • 불변성은 명백하다
  • 융통성 (소비자가 원하면 다른 클래스를 사용할 수 있음)

단점 :

  • 일부 반복적 인 작업 (두 개의 다른 엔티티에서 동일한 선언)
  • 개발자는 DoSomeActionParameters그것이 매핑 될 수있는 클래스라고 추측해야 합니다.IDoSomeActionParameters

+1 왜 그런 생각을하지 않았습니까? :) 객체가 여전히 너무 많은 매개 변수를 가진 생성자로 고통받을 것이라고 생각하지만 그렇지 않습니다. 예, 매우 유효한 솔루션입니다. 내가 생각할 수있는 유일한 문제는 API 사용자가 주어진 인터페이스를 지원하는 올바른 클래스 이름을 찾는 것이 실제로 간단하지 않다는 것입니다. 최소한의 문서를 요구하는 솔루션이 더 좋습니다.
Sedat Kapanoglu 2016 년

나는 이것을 좋아한다. 맵의 복제와 지식은 resharper로 처리되며, 구체적인 클래스의 기본 생성자를 사용하여 기본값을 제공 할 수있다
Anthony Johnston

2
나는 그 접근법을 좋아하지 않습니다. 임의의 구현에 IDoSomeActionParameters대한 참조가 있고 그 안에있는 값을 캡처하려는 사람은 참조를 보유하는 것이 충분한 지 또는 다른 오브젝트에 값을 복사해야하는지 알 방법이 없습니다. 읽을 수있는 인터페이스는 일부 상황에서는 유용하지만 상황을 변경할 수없는 수단으로 사용할 수는 없습니다.
supercat

"IDoSomeActionParameters 매개 변수"는 DoSomeActionParameters로 캐스트되고 변경 될 수 있습니다. 개발자는 심지어 그들이 매개 변수를 imutable 만들기 위해 우회되어 있다는 사실을 인식하지 못할 수도있다
조셉 심슨

3

나는 이것이 오래된 질문이라는 것을 알고 있지만 방금 동일한 문제를 해결해야했기 때문에 내 제안에 빠져 들었다고 생각했다. 자, 사용자가이 객체를 스스로 구성 할 수 없도록하는 추가 요구 사항이 있었기 때문에 내 문제는 약간 다릅니다. (데이터의 모든 수화는 데이터베이스에서 왔기 때문에 내부에서 모든 구성을 탈옥 할 수있었습니다). 이를 통해 개인 생성자와 다음 패턴을 사용할 수있었습니다.

    public class ExampleClass
    {
        //create properties like this...
        private readonly int _exampleProperty;
        public int ExampleProperty { get { return _exampleProperty; } }

        //Private constructor, prohibiting construction outside of this class
        private ExampleClass(ExampleClassParams parameters)
        {                
            _exampleProperty = parameters.ExampleProperty;
            //and so on... 
        }

        //The object returned from here will be immutable
        public ExampleClass GetFromDatabase(DBConnection conn, int id)
        {
            //do database stuff here (ommitted from example)
            ExampleClassParams parameters = new ExampleClassParams()
            {
                ExampleProperty = 1,
                ExampleProperty2 = 2
            };

            //Danger here as parameters object is mutable

            return new ExampleClass(parameters);    

            //Danger is now over ;)
        }

        //Private struct representing the parameters, nested within class that uses it.
        //This is mutable, but the fact that it is private means that all potential 
        //"damage" is limited to this class only.
        private struct ExampleClassParams
        {
            public int ExampleProperty { get; set; }
            public int AnotherExampleProperty { get; set; }
            public int ExampleProperty2 { get; set; }
            public int AnotherExampleProperty2 { get; set; }
            public int ExampleProperty3 { get; set; }
            public int AnotherExampleProperty3 { get; set; }
            public int ExampleProperty4 { get; set; }
            public int AnotherExampleProperty4 { get; set; } 
        }
    }

2

DoSomeAction메소드 의 복잡성에 따라 빌더 스타일 접근 방식을 사용할 수 있지만 터치 헤비급 일 수 있습니다. 이 라인을 따라 뭔가 :

public class DoSomeActionParametersBuilder
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }

    public DoSomeActionParameters Build()
    {
        return new DoSomeActionParameters(A, B, C, D, E, F);
    }
}

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, string e, string f)
    {
        A = a;
        // etc.
    }
}

// usage
var actionParams = new DoSomeActionParametersBuilder
{
    A = "value for A",
    C = DateTime.Now,
    F = "I don't care for B, D and E"
}.Build();

result = foo.DoSomeAction(actionParams, out code);

아, 마르 토는 나를 건축업자의 제안으로 이겼다!
크리스 화이트

2

manji 응답 외에도 하나의 작업을 여러 개의 작은 작업으로 분할 할 수도 있습니다. 비교:

 BOOL WINAPI CreateProcess(
   __in_opt     LPCTSTR lpApplicationName,
   __inout_opt  LPTSTR lpCommandLine,
   __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   __in         BOOL bInheritHandles,
   __in         DWORD dwCreationFlags,
   __in_opt     LPVOID lpEnvironment,
   __in_opt     LPCTSTR lpCurrentDirectory,
   __in         LPSTARTUPINFO lpStartupInfo,
   __out        LPPROCESS_INFORMATION lpProcessInformation
 );

 pid_t fork()
 int execvpe(const char *file, char *const argv[], char *const envp[])
 ...

POSIX를 모르는 사람은 다음과 같이 자식을 쉽게 만들 수 있습니다.

pid_t child = fork();
if (child == 0) {
    execl("/bin/echo", "Hello world from child", NULL);
} else if (child != 0) {
    handle_error();
}

각 디자인 선택은 수행 할 수있는 작업과의 균형을 나타냅니다.

추신. 예-빌더와 비슷합니다 (반대자 (발신자 대신 수신자 측)). 이 특정 경우 빌더보다 더 좋을 수도 있고 그렇지 않을 수도 있습니다.


작은 작업이지만 단일 책임 원칙을 위반하는 사람에게는 권장 사항입니다. 내 질문은 다른 모든 디자인 개선이 완료된 직후에 발생합니다.
Sedat Kapanoglu 2016 년

업. 죄송합니다. 수정하기 전에 질문을 작성하고 나중에 게시했습니다. 따라서 눈치 채지 못했습니다.
Maciej Piechotka 2016 년

2

여기에 Mikeys와 약간 다른 것이 있지만 내가하려고하는 것은 가능한 한 모든 것을 최대한 작게 작성하는 것입니다.

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }

    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }

        public DoSomeActionParameters Create()
        {
            return new DoSomeActionParameters(this);
        }
    }
}

DoSomeActionParameters는 기본 생성자가 개인이므로 직접 작성하거나 작성할 수 없으므로 변경할 수 없습니다.

이니셜 라이저는 변경할 수 없지만 전송 만 가능합니다

사용법은 이니셜 라이저에서 이니셜 라이저를 활용합니다 (드리프트가있는 경우). 이니셜 라이저 기본 생성자에서 기본값을 사용할 수 있습니다

DoSomeAction(new DoSomeActionParameters.Initializer
            {
                A = "Hello",
                B = 42
            }
            .Create());

여기서 매개 변수는 선택 사항입니다. 일부를 원하면 Initializer 기본 생성자에 넣을 수 있습니다.

그리고 검증은 Create 메소드로 갈 수 있습니다.

public class Initializer
{
    public Initializer(int b)
    {
        A = "(unknown)";
        B = b;
    }

    public string A { get; set; }
    public int B { get; private set; }

    public DoSomeActionParameters Create()
    {
        if (B < 50) throw new ArgumentOutOfRangeException("B");

        return new DoSomeActionParameters(this);
    }
}

이제는

DoSomeAction(new DoSomeActionParameters.Initializer
            (b: 42)
            {
                A = "Hello"
            }
            .Create());

그래도 약간의 kooki를 알고 있지만 어쨌든 시도해 볼 것입니다.

편집 : 매개 변수 객체에서 create 메소드를 정적으로 이동하고 초기화자를 전달하는 대리자를 추가하면 호출에서 일부 kookieness가 발생합니다.

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }
    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }
    }

    public static DoSomeActionParameters Create(Action<Initializer> assign)
    {
        var i = new Initializer();
        assign(i)

        return new DoSomeActionParameters(i);
    }
}

이제 전화는 다음과 같습니다

DoSomeAction(
        DoSomeActionParameters.Create(
            i => {
                i.A = "Hello";
            })
        );

1

공용 필드 대신 구조를 사용하지만 공용 속성이 있습니다.

• FXCop 및 Jon Skeet을 포함한 모든 사람은 공공 장소 노출이 나쁘다는 데 동의합니다.

Jon과 FXCop은 필드가 아닌 적절한 항목을 노출하므로 만족할 것입니다.

• 에릭 리퍼 (Eric Lippert) 등은 불변성을 위해 읽기 전용 필드에 의존하는 것이 거짓말이라고 말합니다.

속성을 사용하면 Eric이 만족할 것입니다. 값을 한 번만 설정할 수 있습니다.

    private bool propC_set=false;
    private date pC;
    public date C {
        get{
            return pC;
        }
        set{
            if (!propC_set) {
               pC = value;
            }
            propC_set = true;
        }
    }

반 불변 개체 1 개 (값은 설정할 수 있지만 변경할 수는 없음) 값 및 참조 유형에 적용됩니다.


+1 비공개 읽기 전용 필드와 공개 getter 전용 속성을 구조체에 결합하여 덜 장황한 솔루션을 허용 할 수 있습니다.
Sedat Kapanoglu 2016 년

변경하는 구조의 공개 읽기-쓰기 속성은 this공개 필드보다 훨씬 더 악합니다. 왜 한 번만 값을 설정하면 "괜찮아"라고 생각하는지 잘 모르겠습니다. 구조체의 기본 인스턴스로 시작하면 this-mutating 속성 과 관련된 문제 가 여전히 존재합니다.
supercat

0

같은 문제가 있었을 때 프로젝트에서 사용한 Samuel의 대답 변형 :

class MagicPerformer
{
    public int Param1 { get; set; }
    public string Param2 { get; set; }
    public DateTime Param3 { get; set; }

    public MagicPerformer SetParam1(int value) { this.Param1 = value; return this; }
    public MagicPerformer SetParam2(string value) { this.Param2 = value; return this; }
    public MagicPerformer SetParam4(DateTime value) { this.Param3 = value; return this; }

    public void DoMagic() // Uses all the parameters and does the magic
    {
    }
}

그리고 사용하려면 :

new MagicPerformer().SeParam1(10).SetParam2("Yo!").DoMagic();

필자의 경우 세터 메소드가 가능한 모든 조합을 허용하지 않고 공통 조합을 노출했기 때문에 매개 변수를 의도적으로 수정할 수있었습니다. 내 매개 변수 중 일부는 매우 복잡하고 가능한 모든 경우에 대한 작성 방법이 어렵고 불필요했기 때문입니다 (미친 조합은 거의 사용되지 않음).


이것은 상관없이 변경 가능한 클래스입니다.
Sedat Kapanoglu

@ ssg-글쎄요. 는 DoMagic이 객체를 수정하지 않습니다하지만 그 계약에 있습니다. 그러나 우발적 인 수정으로부터 보호하지 못한다고 생각합니다.
Vilx-
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.