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

前端 未结 13 1584
别那么骄傲
别那么骄傲 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:51

    I'm not a C# programmer but I believe C# supports named arguments: (F# does and C# is largely feature compatable for that sort of thing) It does: http://msdn.microsoft.com/en-us/library/dd264739.aspx#Y342

    So calling your original code becomes:

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

    this takes no more space/thought that your object creation and has all the benifits, of the fact that you haven't changed what is happening in the unerlying system at all. You don't even have to recode anything to indicate the arguments are named

    Edit: here is a artical i found about it. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ I should mention C# 4.0 supports named arguments, 3.0 did not

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

    What you have there is a pretty sure indication that the class in question is violating the Single Responsibility Principle because it has too many dependencies. Look for ways to refactor those dependencies into clusters of Facade Dependencies.

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

    here is a slightly different one from Mikeys but what I am trying to do is make the whole thing as little to write as possible

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

    The DoSomeActionParameters is immutable as it can be and cannot be created directly as its default constructor is private

    The initializer is not immutable, but only a transport

    The usage takes advantage of the initializer on the Initializer (if you get my drift) And I can have defaults in the Initializer default constructor

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

    The parameters will be optional here, if you want some to be required you could put them in the Initializer default constructor

    And validation could go in the Create method

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

    So now it looks like

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

    Still a little kooki I know, but going to try it anyway

    Edit: moving the create method to a static in the parameters object and adding a delegate which passes the initializer takes some of the kookieness out of the call

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

    So the call now looks like this

    DoSomeAction(
            DoSomeActionParameters.Create(
                i => {
                    i.A = "Hello";
                })
            );
    
    0 讨论(0)
  • 2020-11-27 09:53

    One style embraced in the frameworks is usually like grouping related parameters into related classes (but yet again problematic with mutability):

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

    Use a combination of builder and domain-specific-language style API--Fluent Interface. The API is a little more verbose but with intellisense it's very quick to type out and easy to understand.

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

    I know this is an old question but I thought I'd wade in with my suggestion as I've just had to solve the same problem. Now, admittadly my problem was slightly different to yours as I had the additional requirement of not wanting users to be able to construct this object themselves (all hydration of the data came from the database, so I could jail off all construction internally). This allowed me to use a private constructor and the following pattern;

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