API 디자인에서 "너무 많은 매개 변수"문제를 피하는 방법은 무엇입니까?
이 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;
}
이를 통해 매개 변수가 너무 많은 생성자를 피하고 불변성을 달성 할 수 있습니다. 실제로 모든 문제를 해결합니다 (매개 변수 순서 등). 아직:
- FXCop 및 Jon Skeet을 포함한 모든 사람 은 공공 장소 노출이 나쁘다는 데 동의합니다 .
- Eric Lippert 등 은 불변성을 위해 읽기 전용 필드에 의존하는 것이 거짓말 이라고 말합니다 .
그때 혼란스러워서이 질문을하기로 결정했을 때 : C #에서 변이성을 도입하지 않고 "너무 많은 매개 변수"문제를 피하는 가장 간단한 방법은 무엇입니까? 그 목적으로 읽기 전용 구조체를 사용할 수 있지만 API 디자인이 좋지 않습니까?
설명 :
- 단일 책임 원칙을 위반하지 않는다고 가정하십시오. 원래의 경우 함수는 주어진 매개 변수를 단일 DB 레코드에 씁니다.
- 주어진 기능에 대한 특정 솔루션을 찾고 있지 않습니다. 이러한 문제에 대한 일반적인 접근 방식을 찾고 있습니다. 나는 가변성이나 끔찍한 디자인을 도입하지 않고 "너무 많은 매개 변수"문제를 해결하는 데 특히 관심이 있습니다.
최신 정보
여기에 제공된 답변은 서로 다른 장점 / 단점이 있습니다. 따라서 이것을 커뮤니티 위키로 변환하고 싶습니다. 코드 샘플과 찬반 양론으로 각 대답은 앞으로 비슷한 문제에 대한 좋은 가이드를 만들 것이라고 생각합니다. 나는 지금 그것을하는 방법을 찾으려고 노력하고있다.
빌더 및 도메인 별 언어 스타일 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());
프레임 워크에 포함 된 한 스타일은 일반적으로 관련 매개 변수를 관련 클래스로 그룹화하는 것과 같습니다 (그러나 다시 가변성에 문제가 있음).
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
당신이 가지고있는 것은 문제의 클래스 가 너무 많은 의존성을 가지고 있기 때문에 단일 책임 원칙을 위반하고 있음을 분명히 나타냅니다 . 이러한 종속성을 Facade Dependency의 클러스터로 리팩토링하는 방법을 찾으십시오 .
매개 변수 데이터 구조를 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)
메소드는 이제 자체 구조 사본을 얻습니다. 인수 변수에 대한 변경 사항은 메소드에 의해 관찰 될 수 없으며, 변수에 대한 메소드 변경 사항은 호출자가 관찰 할 수 없습니다. 불변성없이 격리가 달성됩니다.
장점 :
- 가장 쉬운 구현
- 기초 역학에서의 행동의 최소 변화
단점 :
- 불변성은 분명하지 않으며 개발자의주의가 필요합니다.
- 불변성을 유지하기 위해 불필요한 복사
- 점유 공간
데이터 클래스 내에 빌더 클래스를 작성하는 것은 어떻습니까. 데이터 클래스에는 모든 세터가 개인용으로 있으며 빌더 만 설정할 수 있습니다.
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();
}
}
나는 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은 그렇지 않습니다.
왜 불변성을 강제하는 인터페이스를 만드는가 (즉, 게터 만)?
본질적으로 첫 번째 솔루션이지만 인터페이스를 사용하여 매개 변수에 액세스하도록 함수를 강제합니다.
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
나는 이것이 오래된 질문이라는 것을 알고 있지만 방금 동일한 문제를 해결해야했기 때문에 내 제안에 빠져 들었다고 생각했다. 자, 사용자가이 객체를 스스로 구성 할 수 없도록하는 추가 요구 사항이 있었기 때문에 내 문제는 약간 다릅니다. (데이터의 모든 수화는 데이터베이스에서 왔기 때문에 내부에서 모든 구성을 탈옥 할 수있었습니다). 이를 통해 개인 생성자와 다음 패턴을 사용할 수있었습니다.
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; }
}
}
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);
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();
}
각 디자인 선택은 수행 할 수있는 작업과의 균형을 나타냅니다.
추신. 예-빌더와 비슷합니다 (반대자 (발신자 대신 수신자 측)). 이 특정 경우 빌더보다 더 좋을 수도 있고 그렇지 않을 수도 있습니다.
여기에 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());
그래도 조금은 알고 있지만 어쨌든 시도해 볼 것입니다.
편집 : 매개 변수 객체에서 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";
})
);
공용 필드 대신 구조를 사용하지만 공용 속성이 있습니다.
• 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 개 (값은 설정할 수 있지만 변경할 수는 없음) 값 및 참조 유형에 적용됩니다.
같은 문제가 발생했을 때 프로젝트에서 사용한 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();
필자의 경우 세터 메소드가 가능한 모든 조합을 허용하지 않고 공통 조합을 노출했기 때문에 매개 변수를 의도적으로 수정할 수있었습니다. 내 매개 변수 중 일부는 매우 복잡하고 가능한 모든 경우에 대한 작성 방법이 어렵고 불필요했기 때문입니다 (미친 조합은 거의 사용되지 않음).
참고 URL : https://stackoverflow.com/questions/6239373/how-to-avoid-too-many-parameters-problem-in-api-design
'development' 카테고리의 다른 글
두 가지 일반 유형으로 하나의 인터페이스를 구현하는 Java 클래스를 작성하는 방법은 무엇입니까? (0) | 2020.06.08 |
---|---|
왜 요구가 필요한가? (0) | 2020.06.08 |
PDF를 만드는 최고의 C # API (0) | 2020.06.08 |
값 초기화에 대한 나의 시도는 함수 선언으로 해석되며 왜 A a (()); (0) | 2020.06.08 |
.xib 파일과 .storyboard의 차이점은 무엇입니까? (0) | 2020.06.08 |