How to avoid “too many parameters” problem in API design?

前端 未结 13 1617
别那么骄傲
别那么骄傲 2020-11-27 09:01

I have this API function:

public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d, 
     string e, string f, out Guid code)
相关标签:
13条回答
  • 2020-11-27 09:33

    A variant of Samuel's answer that I used in my project when I had the same problem:

    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
        {
        }
    }
    

    And to use:

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

    In my case the parameters were intentionally modifiable, because the setter methods didn't allow for all possible combinations, and just exposed common combinations of them. That's because some of my parameters were pretty complex and writing methods for all possible cases would have been difficult and unnecessary (crazy combinations are rarely used).

    0 讨论(0)
  • 2020-11-27 09:35

    Use the structure, but instead of public fields, have public properties:

    •Everybody (including FXCop & Jon Skeet) agree that exposing public fields are bad.

    Jon and FXCop will be satisified because you are exposing properites not fields.

    •Eric Lippert et al say relying on readonly fields for immutability is a lie.

    Eric will be satisifed because using properties, you can ensure that the value is only set once.

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

    One semi-immutable object (value can be set but not changed). Works for value and Reference types.

    0 讨论(0)
  • You could use a Builder-style approach, though depending on the complexity of your DoSomeAction method, this might be a touch heavyweight. Something along these lines:

    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);
    
    0 讨论(0)
  • 2020-11-27 09:45

    In addition to manji response - you may also want to split one operation into several smaller ones. Compare:

     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
     );
    

    and

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

    For those who don't know POSIX the creation of child can be as easy as:

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

    Each design choice represent trade-off over what operations it may do.

    PS. Yes - it is similar to builder - only in reverse (i.e. on callee side instead of caller). It may or may not be better then builder in this specific case.

    0 讨论(0)
  • 2020-11-27 09:50

    Just change your parameter data structure from a class to a struct and you’re good to go.

    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) 
    

    The method will now get its own copy of the structure. Changes made to the argument variable cannot be observed by the method, and changes the method makes to the variable can not be observed by the caller. Isolation is achieved without immutability.

    Pros:

    • Easiest to implement
    • Least change of behavior in underlying mechanics

    Cons:

    • Immutability is not obvious, requires developer attention.
    • Unnecessary copying to maintain immutability
    • Occupies stack space
    0 讨论(0)
  • 2020-11-27 09:51

    How about creating a builder class inside your data class. The data class will have all the setters as private and only the builder will be able to set them.

    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();
            }
        }
    
    0 讨论(0)
提交回复
热议问题