如何避免“参数过多” API设计有问题吗?

发布于 2024-11-13 22:26:12 字数 2413 浏览 0 评论 0原文

我有这个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 设计?

澄清:

  • 请假设没有违反单一责任原则。在我原来的情况下,该函数只是将给定的参数写入单个数据库记录。
  • 我并不是在寻找给定函数的特定解决方案。我正在寻找解决此类问题的通用方法。我特别感兴趣的是在不引入可变性或糟糕设计的情况下解决“参数太多”问题。

更新

此处提供的答案有不同的优点/缺点。因此我想将其转换为社区维基。我认为每个带有代码示例和优点/缺点的答案都将为将来的类似问题提供很好的指导。我现在正在尝试找出如何做到这一点。

I have this API function:

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

I don't like it. Because parameter order becomes unnecessarily significant. It becomes harder to add new fields. It's harder to see what's being passed around. It's harder to refactor method into smaller parts because it creates another overhead of passing all the parameters in sub functions. Code is harder to read.

I came up with the most obvious idea: have an object encapsulating the data and pass it around instead of passing each parameter one by one. Here is what I came up with:

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

That reduced my API declaration to:

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

Nice. Looks very innocent but we actually introduced a huge change: we introduced mutability. Because what we previously had been doing was actually to pass an anonymous immutable object: function parameters on stack. Now we created a new class which is very mutable. We created the ability to manipulate the state of the caller. That sucks. Now I want my object immutable, what do I do?

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

As you can see I actually re-created my original problem: too many parameters. It's obvious that that's not the way to go. What am I going to do? The last option to achieve such immutability is to use a "readonly" struct like this:

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

That allows us to avoid constructors with too many parameters and achieve immutability. Actually it fixes all the problems (parameter ordering etc). Yet:

That's when I got confused and decided to write this question: What's the most straightforward way in C# to avoid "too many parameters" problem without introducing mutability? Is it possible to use a readonly struct for that purpose and yet not have a bad API design?

CLARIFICATIONS:

  • Please assume there is no violation of single responsibiltiy principle. In my original case the function just writes given parameters to a single DB record.
  • I'm not seeking a specific solution to the given function. I'm seeking a generalized approach to such problems. I'm specifically interested in solving "too many parameters" problem without introducing mutability or a terrible design.

UPDATE

The answers provided here have different advantages/disadvantages. Therefore I'd like to convert this to a community wiki. I think each answer with code sample and Pros/Cons would make a good guide for similar problems in the future. I'm now trying to find out how to do it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

原野 2024-11-20 22:26:12

使用构建器和特定领域语言风格 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());

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());
橘寄 2024-11-20 22:26:12

框架中采用的一种风格通常是将相关参数分组到相关类中(但又存在可变性问题):

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

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
倾`听者〃 2024-11-20 22:26:12

只需将参数数据结构从 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) 

该方法现在将获得其自己的结构副本。该方法无法观察到对参数变量所做的更改,并且调用者也无法观察到该方法对变量所做的更改。隔离是在没有不变性的情况下实现的。

优点:

  • 最容易实现,
  • 底层机制的行为变化最少。

缺点:

  • 不变性不明显,需要开发人员注意。
  • 不必要的复制以保持不变性
  • 占用堆栈空间

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
风情万种。 2024-11-20 22:26:12

您所拥有的内容非常明确地表明,相关类违反了单一职责原则,因为它有太多的依赖性。寻找将这些依赖项重构为外观依赖项集群的方法。

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.

心的位置 2024-11-20 22:26:12

我不是 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 不支持

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

病毒体 2024-11-20 22:26:12

为什么不直接创建一个强制不变性的接口(即只有 getter)?

这本质上是您的第一个解决方案,但您强制函数使用接口来访问参数。

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 的类

Why not just make an interface that enforces immutability (i.e. only getters)?

It's essentially your first solution, but you force the function to use the interface to access the parameter.

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

and the function declaration becomes:

public ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)

Pros:

  • Doesn't have stack space problem like struct solution
  • Natural solution using language semantics
  • Immutability is obvious
  • Flexible (consumer can use a different class if he wants)

Cons:

  • Some repetitive work (same declarations in two different entities)
  • Developer has to guess that DoSomeActionParameters is a class that could be mapped to IDoSomeActionParameters
错爱 2024-11-20 22:26:12

如何在数据类中创建一个构建器类?数据类将所有设置器设为私有,并且只有构建器才能设置它们。

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

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();
        }
    }
橘虞初梦 2024-11-20 22:26:12

我知道这是一个老问题,但我想我应该提出我的建议,因为我刚刚必须解决同样的问题。现在,诚然,我的问题与您的问题略有不同,因为我有额外的要求,即不希望用户能够自己构造这个对象(所有数据的水合作用都来自数据库,因此我可以在内部禁止所有构造)。这允许我使用私有构造函数和以下模式;

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

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; } 
        }
    }
墟烟 2024-11-20 22:26:12

您可以使用构建器风格的方法,尽管根据 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);

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);
梦回梦里 2024-11-20 22:26:12

除了 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();
}

每个设计选择都代表着对其可能执行的操作的权衡。

附言。是的 - 它类似于构建器 - 只是相反(即在被调用方而不是调用方)。在这种特定情况下,它可能会或可能不会比构建器更好。

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.

难得心□动 2024-11-20 22:26:12

这是与 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());

参数在这里是可选的,如果您想要一些参数,您可以将它们放在初始化程序默认构造函数中

并且验证可以在 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 方法移动到参数对象中的静态并添加传递初始化程序的委托会消除调用中的一些怪异

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

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";
            })
        );
君勿笑 2024-11-20 22:26:12

使用结构体,但不使用公共字段,而使用公共属性:

•每个人(包括 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;
        }
    }

一个半不可变对象(值可以设置但不能更改)。适用于值和引用类型。

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.

草莓酥 2024-11-20 22:26:12

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

在我的情况下,参数是有意修改的,因为设置方法不允许所有可能的组合,并且只暴露了它们的常见组合。这是因为我的一些参数非常复杂,并且为所有可能的情况编写方法将是困难且不必要的(很少使用疯狂的组合)。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文