重构具有太多(6+)参数的方法的最佳方法是什么?

时间:2022-09-15 21:12:47

Occasionally I come across methods with an uncomfortable number of parameters. More often than not, they seem to be constructors. It seems like there ought to be a better way, but I can't see what it is.

偶尔我会遇到一些参数不舒服的方法。通常情况下,他们似乎是建设者。似乎应该有更好的方法,但我看不出它是什么。

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

I've thought of using structs to represent the list of parameters, but that just seems to shift the problem from one place to another, and create another type in the process.

我曾想过使用结构来表示参数列表,但这似乎只是将问题从一个地方转移到另一个地方,并在流程中创建另一个类型。

ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);

So that doesn't seem like an improvement. So what is the best approach?

所以这似乎不是一种改进。那么最好的方法是什么?

23 个解决方案

#1


The best way would be to find ways to group the arguments together. This assumes, and really only works if, you would end up with multiple "groupings" of arguments.

最好的方法是找到将参数组合在一起的方法。这假设,并且只有当你最终会有多个“分组”参数时才会起作用。

For instance, if you are passing the specification for a rectangle, you can pass x, y, width, and height or you could just pass a rectangle object that contains x, y, width, and height.

例如,如果要传递矩形的规范,则可以传递x,y,width和height,或者只传递包含x,y,width和height的矩形对象。

Look for things like this when refactoring to clean it up somewhat. If the arguments really can't be combined, start looking at whether you have a violation of the Single Responsibility Principle.

在进行重构以稍微清理时,请查找类似的内容。如果参数确实无法合并,请开始查看您是否违反了单一责任原则。

#2


I'm going to assume you mean C#. Some of these things apply to other languages, too.

我假设你的意思是C#。其中一些内容也适用于其他语言。

You have several options:

你有几个选择:

switch from constructor to property setters. This can make code more readable, because it's obvious to the reader which value corresponds to which parameters. Object Initializer syntax makes this look nice. It's also simple to implement, since you can just use auto-generated properties and skip writing the constructors.

从构造函数切换到属性setter。这可以使代码更具可读性,因为对于读者来说,哪个值对应于哪些参数是显而易见的。 Object Initializer语法使这看起来很漂亮。它实现起来也很简单,因为您可以使用自动生成的属性并跳过编写构造函数。

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

However, you lose immutability, and you lose the ability to ensure that the required values are set before using the object at compile time.

但是,您会失去不变性,并且在编译时使用对象之前,您将无法确保设置所需的值。

Builder Pattern.

Think about the relationship between string and StringBuilder. You can get this for your own classes. I like to implement it as a nested class, so class C has related class C.Builder. I also like a fluent interface on the builder. Done right, you can get syntax like this:

想想string和StringBuilder之间的关系。你可以为自己的课程获得这个。我喜欢将它实现为嵌套类,因此类C具有相关的类C.Builder。我也喜欢建设者的流畅界面。做得对,你可以得到这样的语法:

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

I have a PowerShell script that lets me generate the builder code to do all this, where the input looks like:

我有一个PowerShell脚本,让我生成构建器代码来完成所有这些,输入如下所示:

class C {
    field I X
    field string Y
}

So I can generate at compile time. partial classes let me extend both the main class and the builder without modifying the generated code.

所以我可以在编译时生成。使用partial类,我可以扩展主类和构建器,而无需修改生成的代码。

"Introduce Parameter Object" refactoring. See the Refactoring Catalog. The idea is that you take some of the parameters you're passing and put them in to a new type, and then pass an instance of that type instead. If you do this without thinking, you will end up back where you started:

“引入参数对象”重构。请参阅重构目录。我们的想法是,您将一些您传递的参数放入一个新类型,然后传递该类型的实例。如果你不假思索地这样做,你将最终回到你开始的地方:

new C(a, b, c, d);

becomes

new C(new D(a, b, c, d));

However, this approach has the greatest potential to make a positive impact on your code. So, continue by following these steps:

但是,这种方法最有可能对您的代码产生积极影响。因此,请继续执行以下步骤:

  1. Look for subsets of parameters that make sense together. Just mindlessly grouping all parameters of a function together doesn't get you much; the goal is to have groupings that make sense. You'll know you got it right when the name of the new type is obvious.

    寻找有意义的参数子集。只是盲目地将一个函数的所有参数组合在一起并不会让你感到太多;目标是让分组有意义。当新类型的名称显而易见时,你会知道你做对了。

  2. Look for other places where these values are used together, and use the new type there, too. Chances are, when you've found a good new type for a set of values that you already use all over the place, that new type will make sense in all those places, too.

    寻找一起使用这些值的其他地方,并在那里使用新类型。很有可能,当你为一组已经在整个地方使用的值找到一个好的新类型时,新类型在所有这些地方也会有意义。

  3. Look for functionality that is in the existing code, but belongs on the new type.

    查找现有代码中的功能,但属于新类型。

For example, maybe you see some code that looks like:

例如,您可能会看到一些代码如下:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

You could take the minSpeed and maxSpeed parameters and put them in a new type:

您可以获取minSpeed和maxSpeed参数并将它们放在一个新类型中:

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

This is better, but to really take advantage of the new type, move the comparisons into the new type:

这样做更好,但要真正利用新类型,请将比较移到新类型中:

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

And now we're getting somewhere: the implementation of SpeedIsAcceptable() now says what you mean, and you have a useful, reusable class. (The next obvious step is to make SpeedRange in to Range<Speed>.)

现在我们到了某个地方:SpeedIsAcceptable()的实现现在说出你的意思,并且你有一个有用的,可重复使用的类。 (下一个明显的步骤是使SpeedRange进入Range 。)

As you can see, Introduce Parameter Object was a good start, but its real value was that it helped us discover a useful type that has been missing from our model.

正如您所看到的,引入参数对象是一个良好的开端,但它的真正价值在于它帮助我们发现了模型中缺少的有用类型。

#3


If it's a constructor, particularly if there are multiple overloaded variants, you should look at the Builder pattern:

如果它是构造函数,特别是如果存在多个重载变体,则应该查看Builder模式:

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

If it's a normal method, you should think about the relationships between the values being passed, and perhaps create a Transfer Object.

如果这是一种常规方法,您应该考虑传递的值之间的关系,并且可能创建一个传输对象。

#4


This is quoted from Fowler and Beck book: "Refactoring"

这是引自福勒和贝克的书:“重构”

Long Parameter List

长参数列表

In our early programming days we were taught to pass in as parameters everything needed by a routine. This was understandable because the alternative was global data, and global data is evil and usually painful. Objects change this situation because if you don't have something you need, you can always ask another object to get it for you. Thus with objects you don't pass in everything the method needs; instead you pass enough so that the method can get to everything it needs. A lot of what a method needs is available on the method's host class. In object-oriented programs parameter lists tend to be much smaller than in traditional programs. This is good because long parameter lists are hard to understand, because they become inconsistent and difficult to use, and because you are forever changing them as you need more data. Most changes are removed by passing objects because you are much more likely to need to make only a couple of requests to get at a new piece of data. Use Replace Parameter with Method when you can get the data in one parameter by making a request of an object you already know about. This object might be a field or it might be another parameter. Use Preserve Whole Object to take a bunch of data gleaned from an object and replace it with the object itself. If you have several data items with no logical object, use Introduce Parameter Object. There is one important exception to making these changes. This is when you explicitly do not want to create a dependency from the called object to the larger object. In those cases unpacking data and sending it along as parameters is reasonable, but pay attention to the pain involved. If the parameter list is too long or changes too often, you need to rethink your dependency structure.

在我们早期的编程时代,我们被教导将参数传递给例程所需的一切。这是可以理解的,因为替代方案是全球数据,全球数据是邪恶的,通常是痛苦的。对象改变了这种情况,因为如果你没有你需要的东西,你可以随时要求另一个对象为你获取它。因此,对于对象,您不会传递方法所需的所有内容;相反,你传递足够的方法,以便该方法可以达到它需要的一切。方法的主机类上有很多方法需要的东西。在面向对象的程序中,参数列表往往比传统程序小得多。这很好,因为长参数列表很难理解,因为它们变得不一致且难以使用,并且因为您需要更多数据而永远更改它们。通过传递对象可以消除大多数更改,因为您更有可能只需要生成几个请求来获取新数据。如果可以通过请求您已知的对象来获取一个参数中的数据,请使用“将参数替换为方法”。此对象可能是字段,也可能是其他参数。使用“保留整个对象”来获取从对象收集的大量数据,并将其替换为对象本身。如果您有多个没有逻辑对象的数据项,请使用Introduce Parameter Object。进行这些更改有一个重要的例外。这是当您明确不希望创建从被调用对象到较大对象的依赖项时。在这些情况下,解压缩数据并将其作为参数发送是合理的,但要注意所涉及的痛苦。如果参数列表太长或更改太频繁,则需要重新考虑依赖关系结构。

#5


The classic answer to this is to use a class to encapsulate some, or all, of the parameters. In theory that sounds great, but I'm the kind of guy who creates classes for concepts that have meaning in the domain, so it's not always easy to apply this advice.

对此的经典答案是使用类来封装部分或全部参数。理论上听起来很棒,但我是那种为在域中具有意义的概念创建类的人,所以应用这个建议并不总是那么容易。

E.g. instead of:

例如。代替:

driver.connect(host, user, pass)

You could use

你可以用

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV

#6


I don't want to sound like a wise-crack, but you should also check to make sure the data you are passing around really should be passed around: Passing stuff to a constructor (or method for that matter) smells a bit like to little emphasis on the behavior of an object.

我不想听起来像一个明智的裂缝,但你也应该检查以确保你传递的数据确实应该传递:将东西传递给构造函数(或者相关的方法)闻起来有点像很少强调对象的行为。

Don't get me wrong: Methods and constructors will have a lot of parameters sometimes. But when encountered, do try to consider encapsulating data with behavior instead.

不要误解我的意思:方法和构造函数有时会有很多参数。但遇到这种情况时,请尝试考虑使用行为封装数据。

This kind of smell (since we are talking about refactoring, this horrible word seems appropriate...) might also be detected for objects that have a lot (read: any) properties or getters/setters.

这种气味(因为我们正在谈论重构,这个可怕的词似乎合适......)也可能被检测到具有很多(读取:任何)属性或getter / setter的对象。

#7


When I see long parameter lists, my first question is whether this function or object is doing too much. Consider:

当我看到长参数列表时,我的第一个问题是这个函数或对象是否做得太多了。考虑:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId,
  lastCustomerId,
  orderNumber, productCode, lastFileUpdateDate,
  employeeOfTheMonthWinnerForLastMarch,
  yearMyHometownWasIncorporated, greatGrandmothersBloodType,
  planetName, planetSize, percentWater, ... etc ...);

Of course this example is deliberately ridiculous, but I've seen plenty of real programs with examples only slightly less ridiculous, where one class is used to hold many barely related or unrelated things, apparently just because the same calling program needs both or because the programmer happened to think of both at the same time. Sometimes the easy solution is to just break the class into multiple pieces each of which does its own thing.

当然这个例子是故意荒谬的,但是我看到很多真实的程序,其中的例子只是略显荒谬,其中一个类用于存放许多几乎没有相关或不相关的东西,显然只是因为同一个调用程序需要两个或者因为程序员碰巧同时想到了两者。有时,简单的解决方案就是将类分成多个部分,每个部分都有自己的功能。

Just slightly more complicated is when a class really does need to deal with multiple logical things, like both a customer order and general information about the customer. In these cases, crate a class for customer and a class for order, and let them talk to each other as necessary. So instead of:

稍微复杂的是,当一个班级确实需要处理多个逻辑事物时,例如客户订单和关于客户的一般信息。在这些情况下,为客户创建一个类,为订单创建一个类,并让它们在必要时相互通信。所以代替:

 Order order=new Order(customerName, customerAddress, customerCity,
   customerState, customerZip,
   orderNumber, orderType, orderDate, deliveryDate);

We could have:

我们可以有:

Customer customer=new Customer(customerName, customerAddress,
  customerCity, customerState, customerZip);
Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

While of course I prefer functions that take just 1 or 2 or 3 parameters, sometimes we have to accept that, realistically, this function takes a bunch, and that the number of itself does not really create complexity. For example:

虽然我当然更喜欢仅采用1或2或3个参数的函数,但有时我们必须接受,实际上,这个函数占用了很多,而且它本身的数量并没有真正产生复杂性。例如:

Employee employee=new Employee(employeeId, firstName, lastName,
  socialSecurityNumber,
  address, city, state, zip);

Yeah, it's a bunch of fields, but probably all we're going to do with them is save them to a database record or throw them on a screen or some such. There's not really a lot of processing here.

是的,它是一堆字段,但可能我们要用它们做的就是将它们保存到数据库记录中或将它们放在屏幕上或其他类似的记录中。这里没有太多的处理。

When my parameter lists do get long, I much prefer if I can give the fields different data types. Like when I see a function like:

当我的参数列表变长时,我更喜欢我可以为字段提供不同的数据类型。就像我看到一个像下面这样的函数:

void updateCustomer(String type, String status,
  int lastOrderNumber, int pastDue, int deliveryCode, int birthYear,
  int addressCode,
  boolean newCustomer, boolean taxExempt, boolean creditWatch,
  boolean foo, boolean bar);

And then I see it called with:

然后我看到它被称为:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

I get concerned. Looking at the call, it's not at all clear what all these cryptic numbers, codes, and flags mean. This is just asking for errors. A programmer might easily get confused about the order of the parameters and accidentally switch two, and if they're the same data type, the compiler would just accept it. I'd much rather have a signature where all these things are enums, so a call passes in things like Type.ACTIVE instead of "A" and CreditWatch.NO instead of "false", etc.

我很担心看看这个电话,所有这些神秘的数字,代码和标志的含义都不清楚。这只是要求错误。程序员可能很容易对参数的顺序感到困惑并意外地切换两个,如果它们是相同的数据类型,编译器就会接受它。我更喜欢签名所有这些东西都是枚举,所以调用会传递类似Type.ACTIVE而不是“A”和CreditWatch.NO而不是“false”等。

#8


If some of the constructor parameters are optional it makes sense to use a builder, which would get the required parameters in the constructor, and have methods for the optional ones, returning the builder, to be used like this:

如果某些构造函数参数是可选的,那么使用构建器可以获得所需参数的构建器是有意义的,并且为可选构件提供方法,返回构建器,如下所示:

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

The details of this are described in Effective Java, 2nd Ed., p. 11. For method arguments, the same book (p. 189) describes three approaches for shortening parameter lists:

有关详细信息,请参阅Effective Java,2nd Ed。,p。 11.对于方法参数,同一本书(p.189)描述了三种缩短参数列表的方法:

  • Break the method into multiple methods that take fewer arguments
  • 将方法分解为多个参数较少的方法

  • Create static helper member classes to represent groups of parameters, i.e. pass a DinoDonkey instead of dino and donkey
  • 创建静态助手成员类来表示参数组,即传递DinoDonkey而不是dino和donkey

  • If parameters are optional, the builder above can be adopted for methods, defining an object for all parameters, setting the required ones and then calling some execute method on it
  • 如果参数是可选的,则可以采用上面的构建器用于方法,为所有参数定义对象,设置所需参数,然后在其上调用一些execute方法

#9


I would use the default constructor and property settors. C# 3.0 has some nice syntax to do this automagically.

我会使用默认的构造函数和属性settors。 C#3.0有一些很好的语法可以自动完成。

return new Shniz { Foo = foo,
                   Bar = bar,
                   Baz = baz,
                   Quuz = quux,
                   Fred = fred,
                   Wilma = wilma,
                   Barney = barney,
                   Dino = dino,
                   Donkey = donkey
                 };

The code improvement comes in simplifying the constructor and not having to support multiple methods to support various combinations. The "calling" syntax is still a little "wordy", but not really any worse than calling the property settors manually.

代码改进来自简化构造函数,而不必支持多种方法来支持各种组合。 “调用”语法仍然有点“罗嗦”,但并不比手动调用属性设置器更糟糕。

#10


You haven't provided enough information to warrant a good answer. A long parameter list isn't inherently bad.

您没有提供足够的信息来保证一个好的答案。长参数列表本身并不坏。

Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

Shniz(foo,bar,baz,quux,fred,wilma,barney,dino,donkey)

could be interpreted as:

可以解释为:

void Shniz(int foo, int bar, int baz, int quux, int fred, 
           int wilma, int barney, int dino, int donkey) { ...

In this case you're far better off to create a class to encapsulate the parameters because you give meaning to the different parameters in a way that the compiler can check as well as visually making the code easier to read. It also makes it easier to read and refactor later.

在这种情况下,您最好创建一个类来封装参数,因为您可以通过编译器可以检查的方式为不同的参数赋予意义,也可以直观地使代码更容易阅读。它还使以后更容易阅读和重构。

// old way
Shniz(1,2,3,2,3,2,1,2);
Shniz(1,2,2,3,3,2,1,2); 

//versus
ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 };
Shniz(p);

Alternatively if you had:

或者,如果你有:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, 
           Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

This is a far different case because all the objects are different (and aren't likely to be muddled up). Agreed that if all objects are necessary, and they're all different, it makes little sense to create a parameter class.

这是一个非常不同的情况,因为所有对象都是不同的(并且不太可能混淆)。同意如果所有对象都是必需的,并且它们都是不同的,那么创建参数类就没有意义了。

Additionally, are some parameters optional? Are there method override's (same method name, but different method signatures?) These sorts of details all matter as to what the best answer is.

另外,一些参数是可选的吗?是否有方法覆盖(相同的方法名称,但不同的方法签名?)这些类型的细节都与最佳答案有关。

* A property bag can be useful as well, but not specifically better given that there is no background given.

*一个属性包也是有用的,但没有特别好,因为没有给出背景。

As you can see, there is more than 1 correct answer to this question. Take your pick.

如您所见,这个问题有1个以上的正确答案。随便挑选。

#11


You can try to group your parameter into multiples meaningful struct/class (if possible).

您可以尝试将参数分组为多个有意义的struct / class(如果可能)。

#12


I would generally lean towards the structs approach - presumably the majority of these parameters are related in some way and represent the state of some element that is relevant to your method.

我通常倾向于结构方法 - 大概这些参数中的大多数以某种方式相关并且表示与您的方法相关的某个元素的状态。

If the set of parameters can't be made into a meaningful object, that's probably a sign that Shniz is doing too much, and the refactoring should involve breaking the method down into separate concerns.

如果参数集不能成为有意义的对象,那可能是Shniz做得太多的一个迹象,重构应该包括将方法分解为单独的问题。

#13


If your language supports it, use named parameters and make as many optional (with reasonable defaults) as possible.

如果您的语言支持它,请使用命名参数并尽可能多地生成可选(具有合理的默认值)。

#14


I think the method you described is the way to go. When I find a method with a lot of parameters and/or one that is likely to need more in the future, I usually create a ShnizParams object to pass through, like you describe.

我认为你描述的方法是要走的路。当我找到一个包含大量参数和/或将来可能需要更多参数的方法时,我通常会创建一个ShnizParams对象来传递,就像你描述的那样。

#15


How about not setting it in all at once at the constructors but doing it via properties/setters? I have seen some .NET classes that utilize this approach such as Process class:

如何不在构造函数中同时设置它,而是通过属性/ setter来完成它?我见过一些使用这种方法的.NET类,比如Process类:

        Process p = new Process();

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.FileName = "cmd";
        p.StartInfo.Arguments = "/c dir";
        p.Start();

#16


You can trade complexity for source code lines. If the method itself does too much (Swiss knife) try to halve its tasks by creating another method. If the method is simple only it needs too many parameters then the so called parameter objects are the way to go.

您可以交换源代码行的复杂性。如果方法本身做得太多(瑞士刀)尝试通过创建另一种方法将其任务减半。如果方法很简单,只需要太多参数,那么所谓的参数对象就是最佳选择。

#17


I concur with the approach of moving the parameters into a parameter object (struct). Rather than just sticking them all in one object though, review if other functions use similar groups of parameters. A paramater object is more valuable if its used with multiple functions where you expect that set of parameters to change consistently across those functions. It may be that you only put some of the parameters into the new parameter object.

我同意将参数移动到参数对象(struct)的方法。不过只是将它们全部放在一个对象中,而是检查其他函数是否使用相似的参数组。如果paramater对象与多个函数一起使用,并且您希望这些参数集在这些函数中一致地更改,则它更有价值。可能是您只将一些参数放入新参数对象中。

#18


If you have that many parameters, chances are that the method is doing too much, so address this first by splitting the method into several smaller methods. If you still have too many parameters after this try grouping the arguments or turning some of the parameters into instance members.

如果你有那么多参数,很可能该方法做得太多,所以首先通过将方法分成几个较小的方法来解决这个问题。如果此后仍有太多参数,请尝试对参数进行分组或将某些参数转换为实例成员。

Prefer small classes/methods over large. Remember the single responsibility principle.

喜欢小型课程/大型课程。记住单一责任原则。

#19


Named arguments are a good option (presuming a language which supports them) for disambiguating long (or even short!) parameter lists while also allowing (in the case of constructors) the class's properties to be immutable without imposing a requirement for allowing it to exist in a partially-constructed state.

命名参数是一个很好的选择(假设支持它们的语言),用于消除长(或甚至短!)参数列表的歧义,同时还允许(在构造函数的情况下)类的属性是不可变的,而不强制要求允许它存在在一个部分建构的状态。

The other option I would look for in doing this sort of refactor would be groups of related parameters which might be better handled as an independent object. Using the Rectangle class from an earlier answer as an example, the constructor which takes parameters for x, y, height, and width could factor x and y out into a Point object, allowing you to pass three parameters to the Rectangle's constructor. Or go a little further and make it two parameters (UpperLeftPoint, LowerRightPoint), but that would be a more radical refactoring.

我在做这种重构时会寻找的另一个选项是相关参数组,这些参数可能更好地作为独立对象处理。使用前面答案中的Rectangle类作为示例,获取x,y,height和width参数的构造函数可以将x和y分解为Point对象,允许您将三个参数传递给Rectangle的构造函数。或者更进一步,使它成为两个参数(UpperLeftPoint,LowerRightPoint),但这将是一个更激进的重构。

#20


It depends on what kind of arguments you have, but if they are a lot of boolean values/options maybe you could use a Flag Enum?

这取决于你有什么样的参数,但如果它们是很多布尔值/选项,你可以使用Flag Enum吗?

#21


I think that problem is deeply tied to the domain of the problem you're trying to solve with the class.

我认为这个问题与你试图用类解决的问题的领域密切相关。

In some cases, a 7-parameter constructor may indicate a bad class hierarchy: in that case, the helper struct/class suggested above is usually a good approach, but then you also tend to end up with loads of structs which are just property bags and don't do anything useful. The 8-argument constructor might also indicate that your class is too generic / too all-purpose so it needs a lot of options to be really useful. In that case you can either refactor the class or implement static constructors that hide the real complex constructors: eg. Shniz.NewBaz (foo, bar) could actually call the real constructor passing the right parameters.

在某些情况下,7参数构造函数可能表示一个错误的类层次结构:在这种情况下,上面建议的辅助结构/类通常是一个很好的方法,但是你也倾向于最终得到大量的结构,这些结构只是属性包并且不做任何有用的事情。 8参数构造函数也可能表明您的类太通用/太通用,因此它需要很多选项才能真正有用。在这种情况下,您可以重构类或实现隐藏真正复杂构造函数的静态构造函数:例如。 Shniz.NewBaz(foo,bar)实际上可以调用真正的构造函数传递正确的参数。

#22


One consideration is which of the values would be read-only once the object is created?

一个考虑因素是,一旦创建了对象,哪些值是只读的?

Publicly writable properties could perhaps be assigned after construction.

可以在施工后指定公开可写的属性。

Where ultimately do the values come from? Perhaps some values are truely external where as others are really from some configuration or global data that is maintained by the library.

价值最终来自哪里?也许某些值确实是外部的,而其他值实际上来自某些配置或库维护的全局数据。

In this case you could conceal the constructor from external use and provide a Create function for it. The create function takes the truely external values and constructs the object, then uses accessors only avaiable to the library to complete the creation of the object.

在这种情况下,您可以隐藏构造函数以免外部使用,并为其提供Create函数。 create函数接受真正的外部值并构造对象,然后使用仅可用于库的访问器来完成对象的创建。

It would be really strange to have an object that requires 7 or more parameters to give the object a complete state and all truely being external in nature.

拥有一个需要7个或更多参数的对象来赋予对象一个完整的状态并且所有这些都是真正的外在性将是非常奇怪的。

#23


When a clas has a constructor that takes too many arguments, it is usually a sign that it has too many responsibilities. It can probably be broken into separate classes that cooperate to give the same functionalities.

当一个clas有一个带有太多参数的构造函数时,通常表明它有太多的责任。它可以分成单独的类,它们合作以提供相同的功能。

In case you really need that many arguments to a constructor, the Builder pattern can help you. The goal is to still pass all the arguments to the constructor, so its state is initialized from the start and you can still make the class immutable if needed.

如果你真的需要构造函数的那么多参数,那么Builder模式可以帮助你。目标是仍然将所有参数传递给构造函数,因此它的状态从一开始就被初始化,如果需要,您仍然可以使该类不可变。

See below :

见下文 :

public class Toto {
    private final String state0;
    private final String state1;
    private final String state2;
    private final String state3;

    public Toto(String arg0, String arg1, String arg2, String arg3) {
        this.state0 = arg0;
        this.state1 = arg1;
        this.state2 = arg2;
        this.state3 = arg3;
    }

    public static class TotoBuilder {
        private String arg0;
        private String arg1;
        private String arg2;
        private String arg3;

        public TotoBuilder addArg0(String arg) {
            this.arg0 = arg;
            return this;
        }
        public TotoBuilder addArg1(String arg) {
            this.arg1 = arg;
            return this;
        }
        public TotoBuilder addArg2(String arg) {
            this.arg2 = arg;
            return this;
        }
        public TotoBuilder addArg3(String arg) {
            this.arg3 = arg;
            return this;
        }

        public Toto newInstance() {
            // maybe add some validation ...
            return new Toto(this.arg0, this.arg1, this.arg2, this.arg3);
        }
    }

    public static void main(String[] args) {
        Toto toto = new TotoBuilder()
            .addArg0("0")
            .addArg1("1")
            .addArg2("2")
            .addArg3("3")
            .newInstance();
    }

}

#1


The best way would be to find ways to group the arguments together. This assumes, and really only works if, you would end up with multiple "groupings" of arguments.

最好的方法是找到将参数组合在一起的方法。这假设,并且只有当你最终会有多个“分组”参数时才会起作用。

For instance, if you are passing the specification for a rectangle, you can pass x, y, width, and height or you could just pass a rectangle object that contains x, y, width, and height.

例如,如果要传递矩形的规范,则可以传递x,y,width和height,或者只传递包含x,y,width和height的矩形对象。

Look for things like this when refactoring to clean it up somewhat. If the arguments really can't be combined, start looking at whether you have a violation of the Single Responsibility Principle.

在进行重构以稍微清理时,请查找类似的内容。如果参数确实无法合并,请开始查看您是否违反了单一责任原则。

#2


I'm going to assume you mean C#. Some of these things apply to other languages, too.

我假设你的意思是C#。其中一些内容也适用于其他语言。

You have several options:

你有几个选择:

switch from constructor to property setters. This can make code more readable, because it's obvious to the reader which value corresponds to which parameters. Object Initializer syntax makes this look nice. It's also simple to implement, since you can just use auto-generated properties and skip writing the constructors.

从构造函数切换到属性setter。这可以使代码更具可读性,因为对于读者来说,哪个值对应于哪些参数是显而易见的。 Object Initializer语法使这看起来很漂亮。它实现起来也很简单,因为您可以使用自动生成的属性并跳过编写构造函数。

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

However, you lose immutability, and you lose the ability to ensure that the required values are set before using the object at compile time.

但是,您会失去不变性,并且在编译时使用对象之前,您将无法确保设置所需的值。

Builder Pattern.

Think about the relationship between string and StringBuilder. You can get this for your own classes. I like to implement it as a nested class, so class C has related class C.Builder. I also like a fluent interface on the builder. Done right, you can get syntax like this:

想想string和StringBuilder之间的关系。你可以为自己的课程获得这个。我喜欢将它实现为嵌套类,因此类C具有相关的类C.Builder。我也喜欢建设者的流畅界面。做得对,你可以得到这样的语法:

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

I have a PowerShell script that lets me generate the builder code to do all this, where the input looks like:

我有一个PowerShell脚本,让我生成构建器代码来完成所有这些,输入如下所示:

class C {
    field I X
    field string Y
}

So I can generate at compile time. partial classes let me extend both the main class and the builder without modifying the generated code.

所以我可以在编译时生成。使用partial类,我可以扩展主类和构建器,而无需修改生成的代码。

"Introduce Parameter Object" refactoring. See the Refactoring Catalog. The idea is that you take some of the parameters you're passing and put them in to a new type, and then pass an instance of that type instead. If you do this without thinking, you will end up back where you started:

“引入参数对象”重构。请参阅重构目录。我们的想法是,您将一些您传递的参数放入一个新类型,然后传递该类型的实例。如果你不假思索地这样做,你将最终回到你开始的地方:

new C(a, b, c, d);

becomes

new C(new D(a, b, c, d));

However, this approach has the greatest potential to make a positive impact on your code. So, continue by following these steps:

但是,这种方法最有可能对您的代码产生积极影响。因此,请继续执行以下步骤:

  1. Look for subsets of parameters that make sense together. Just mindlessly grouping all parameters of a function together doesn't get you much; the goal is to have groupings that make sense. You'll know you got it right when the name of the new type is obvious.

    寻找有意义的参数子集。只是盲目地将一个函数的所有参数组合在一起并不会让你感到太多;目标是让分组有意义。当新类型的名称显而易见时,你会知道你做对了。

  2. Look for other places where these values are used together, and use the new type there, too. Chances are, when you've found a good new type for a set of values that you already use all over the place, that new type will make sense in all those places, too.

    寻找一起使用这些值的其他地方,并在那里使用新类型。很有可能,当你为一组已经在整个地方使用的值找到一个好的新类型时,新类型在所有这些地方也会有意义。

  3. Look for functionality that is in the existing code, but belongs on the new type.

    查找现有代码中的功能,但属于新类型。

For example, maybe you see some code that looks like:

例如,您可能会看到一些代码如下:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

You could take the minSpeed and maxSpeed parameters and put them in a new type:

您可以获取minSpeed和maxSpeed参数并将它们放在一个新类型中:

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

This is better, but to really take advantage of the new type, move the comparisons into the new type:

这样做更好,但要真正利用新类型,请将比较移到新类型中:

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

And now we're getting somewhere: the implementation of SpeedIsAcceptable() now says what you mean, and you have a useful, reusable class. (The next obvious step is to make SpeedRange in to Range<Speed>.)

现在我们到了某个地方:SpeedIsAcceptable()的实现现在说出你的意思,并且你有一个有用的,可重复使用的类。 (下一个明显的步骤是使SpeedRange进入Range 。)

As you can see, Introduce Parameter Object was a good start, but its real value was that it helped us discover a useful type that has been missing from our model.

正如您所看到的,引入参数对象是一个良好的开端,但它的真正价值在于它帮助我们发现了模型中缺少的有用类型。

#3


If it's a constructor, particularly if there are multiple overloaded variants, you should look at the Builder pattern:

如果它是构造函数,特别是如果存在多个重载变体,则应该查看Builder模式:

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

If it's a normal method, you should think about the relationships between the values being passed, and perhaps create a Transfer Object.

如果这是一种常规方法,您应该考虑传递的值之间的关系,并且可能创建一个传输对象。

#4


This is quoted from Fowler and Beck book: "Refactoring"

这是引自福勒和贝克的书:“重构”

Long Parameter List

长参数列表

In our early programming days we were taught to pass in as parameters everything needed by a routine. This was understandable because the alternative was global data, and global data is evil and usually painful. Objects change this situation because if you don't have something you need, you can always ask another object to get it for you. Thus with objects you don't pass in everything the method needs; instead you pass enough so that the method can get to everything it needs. A lot of what a method needs is available on the method's host class. In object-oriented programs parameter lists tend to be much smaller than in traditional programs. This is good because long parameter lists are hard to understand, because they become inconsistent and difficult to use, and because you are forever changing them as you need more data. Most changes are removed by passing objects because you are much more likely to need to make only a couple of requests to get at a new piece of data. Use Replace Parameter with Method when you can get the data in one parameter by making a request of an object you already know about. This object might be a field or it might be another parameter. Use Preserve Whole Object to take a bunch of data gleaned from an object and replace it with the object itself. If you have several data items with no logical object, use Introduce Parameter Object. There is one important exception to making these changes. This is when you explicitly do not want to create a dependency from the called object to the larger object. In those cases unpacking data and sending it along as parameters is reasonable, but pay attention to the pain involved. If the parameter list is too long or changes too often, you need to rethink your dependency structure.

在我们早期的编程时代,我们被教导将参数传递给例程所需的一切。这是可以理解的,因为替代方案是全球数据,全球数据是邪恶的,通常是痛苦的。对象改变了这种情况,因为如果你没有你需要的东西,你可以随时要求另一个对象为你获取它。因此,对于对象,您不会传递方法所需的所有内容;相反,你传递足够的方法,以便该方法可以达到它需要的一切。方法的主机类上有很多方法需要的东西。在面向对象的程序中,参数列表往往比传统程序小得多。这很好,因为长参数列表很难理解,因为它们变得不一致且难以使用,并且因为您需要更多数据而永远更改它们。通过传递对象可以消除大多数更改,因为您更有可能只需要生成几个请求来获取新数据。如果可以通过请求您已知的对象来获取一个参数中的数据,请使用“将参数替换为方法”。此对象可能是字段,也可能是其他参数。使用“保留整个对象”来获取从对象收集的大量数据,并将其替换为对象本身。如果您有多个没有逻辑对象的数据项,请使用Introduce Parameter Object。进行这些更改有一个重要的例外。这是当您明确不希望创建从被调用对象到较大对象的依赖项时。在这些情况下,解压缩数据并将其作为参数发送是合理的,但要注意所涉及的痛苦。如果参数列表太长或更改太频繁,则需要重新考虑依赖关系结构。

#5


The classic answer to this is to use a class to encapsulate some, or all, of the parameters. In theory that sounds great, but I'm the kind of guy who creates classes for concepts that have meaning in the domain, so it's not always easy to apply this advice.

对此的经典答案是使用类来封装部分或全部参数。理论上听起来很棒,但我是那种为在域中具有意义的概念创建类的人,所以应用这个建议并不总是那么容易。

E.g. instead of:

例如。代替:

driver.connect(host, user, pass)

You could use

你可以用

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV

#6


I don't want to sound like a wise-crack, but you should also check to make sure the data you are passing around really should be passed around: Passing stuff to a constructor (or method for that matter) smells a bit like to little emphasis on the behavior of an object.

我不想听起来像一个明智的裂缝,但你也应该检查以确保你传递的数据确实应该传递:将东西传递给构造函数(或者相关的方法)闻起来有点像很少强调对象的行为。

Don't get me wrong: Methods and constructors will have a lot of parameters sometimes. But when encountered, do try to consider encapsulating data with behavior instead.

不要误解我的意思:方法和构造函数有时会有很多参数。但遇到这种情况时,请尝试考虑使用行为封装数据。

This kind of smell (since we are talking about refactoring, this horrible word seems appropriate...) might also be detected for objects that have a lot (read: any) properties or getters/setters.

这种气味(因为我们正在谈论重构,这个可怕的词似乎合适......)也可能被检测到具有很多(读取:任何)属性或getter / setter的对象。

#7


When I see long parameter lists, my first question is whether this function or object is doing too much. Consider:

当我看到长参数列表时,我的第一个问题是这个函数或对象是否做得太多了。考虑:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId,
  lastCustomerId,
  orderNumber, productCode, lastFileUpdateDate,
  employeeOfTheMonthWinnerForLastMarch,
  yearMyHometownWasIncorporated, greatGrandmothersBloodType,
  planetName, planetSize, percentWater, ... etc ...);

Of course this example is deliberately ridiculous, but I've seen plenty of real programs with examples only slightly less ridiculous, where one class is used to hold many barely related or unrelated things, apparently just because the same calling program needs both or because the programmer happened to think of both at the same time. Sometimes the easy solution is to just break the class into multiple pieces each of which does its own thing.

当然这个例子是故意荒谬的,但是我看到很多真实的程序,其中的例子只是略显荒谬,其中一个类用于存放许多几乎没有相关或不相关的东西,显然只是因为同一个调用程序需要两个或者因为程序员碰巧同时想到了两者。有时,简单的解决方案就是将类分成多个部分,每个部分都有自己的功能。

Just slightly more complicated is when a class really does need to deal with multiple logical things, like both a customer order and general information about the customer. In these cases, crate a class for customer and a class for order, and let them talk to each other as necessary. So instead of:

稍微复杂的是,当一个班级确实需要处理多个逻辑事物时,例如客户订单和关于客户的一般信息。在这些情况下,为客户创建一个类,为订单创建一个类,并让它们在必要时相互通信。所以代替:

 Order order=new Order(customerName, customerAddress, customerCity,
   customerState, customerZip,
   orderNumber, orderType, orderDate, deliveryDate);

We could have:

我们可以有:

Customer customer=new Customer(customerName, customerAddress,
  customerCity, customerState, customerZip);
Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

While of course I prefer functions that take just 1 or 2 or 3 parameters, sometimes we have to accept that, realistically, this function takes a bunch, and that the number of itself does not really create complexity. For example:

虽然我当然更喜欢仅采用1或2或3个参数的函数,但有时我们必须接受,实际上,这个函数占用了很多,而且它本身的数量并没有真正产生复杂性。例如:

Employee employee=new Employee(employeeId, firstName, lastName,
  socialSecurityNumber,
  address, city, state, zip);

Yeah, it's a bunch of fields, but probably all we're going to do with them is save them to a database record or throw them on a screen or some such. There's not really a lot of processing here.

是的,它是一堆字段,但可能我们要用它们做的就是将它们保存到数据库记录中或将它们放在屏幕上或其他类似的记录中。这里没有太多的处理。

When my parameter lists do get long, I much prefer if I can give the fields different data types. Like when I see a function like:

当我的参数列表变长时,我更喜欢我可以为字段提供不同的数据类型。就像我看到一个像下面这样的函数:

void updateCustomer(String type, String status,
  int lastOrderNumber, int pastDue, int deliveryCode, int birthYear,
  int addressCode,
  boolean newCustomer, boolean taxExempt, boolean creditWatch,
  boolean foo, boolean bar);

And then I see it called with:

然后我看到它被称为:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

I get concerned. Looking at the call, it's not at all clear what all these cryptic numbers, codes, and flags mean. This is just asking for errors. A programmer might easily get confused about the order of the parameters and accidentally switch two, and if they're the same data type, the compiler would just accept it. I'd much rather have a signature where all these things are enums, so a call passes in things like Type.ACTIVE instead of "A" and CreditWatch.NO instead of "false", etc.

我很担心看看这个电话,所有这些神秘的数字,代码和标志的含义都不清楚。这只是要求错误。程序员可能很容易对参数的顺序感到困惑并意外地切换两个,如果它们是相同的数据类型,编译器就会接受它。我更喜欢签名所有这些东西都是枚举,所以调用会传递类似Type.ACTIVE而不是“A”和CreditWatch.NO而不是“false”等。

#8


If some of the constructor parameters are optional it makes sense to use a builder, which would get the required parameters in the constructor, and have methods for the optional ones, returning the builder, to be used like this:

如果某些构造函数参数是可选的,那么使用构建器可以获得所需参数的构建器是有意义的,并且为可选构件提供方法,返回构建器,如下所示:

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

The details of this are described in Effective Java, 2nd Ed., p. 11. For method arguments, the same book (p. 189) describes three approaches for shortening parameter lists:

有关详细信息,请参阅Effective Java,2nd Ed。,p。 11.对于方法参数,同一本书(p.189)描述了三种缩短参数列表的方法:

  • Break the method into multiple methods that take fewer arguments
  • 将方法分解为多个参数较少的方法

  • Create static helper member classes to represent groups of parameters, i.e. pass a DinoDonkey instead of dino and donkey
  • 创建静态助手成员类来表示参数组,即传递DinoDonkey而不是dino和donkey

  • If parameters are optional, the builder above can be adopted for methods, defining an object for all parameters, setting the required ones and then calling some execute method on it
  • 如果参数是可选的,则可以采用上面的构建器用于方法,为所有参数定义对象,设置所需参数,然后在其上调用一些execute方法

#9


I would use the default constructor and property settors. C# 3.0 has some nice syntax to do this automagically.

我会使用默认的构造函数和属性settors。 C#3.0有一些很好的语法可以自动完成。

return new Shniz { Foo = foo,
                   Bar = bar,
                   Baz = baz,
                   Quuz = quux,
                   Fred = fred,
                   Wilma = wilma,
                   Barney = barney,
                   Dino = dino,
                   Donkey = donkey
                 };

The code improvement comes in simplifying the constructor and not having to support multiple methods to support various combinations. The "calling" syntax is still a little "wordy", but not really any worse than calling the property settors manually.

代码改进来自简化构造函数,而不必支持多种方法来支持各种组合。 “调用”语法仍然有点“罗嗦”,但并不比手动调用属性设置器更糟糕。

#10


You haven't provided enough information to warrant a good answer. A long parameter list isn't inherently bad.

您没有提供足够的信息来保证一个好的答案。长参数列表本身并不坏。

Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

Shniz(foo,bar,baz,quux,fred,wilma,barney,dino,donkey)

could be interpreted as:

可以解释为:

void Shniz(int foo, int bar, int baz, int quux, int fred, 
           int wilma, int barney, int dino, int donkey) { ...

In this case you're far better off to create a class to encapsulate the parameters because you give meaning to the different parameters in a way that the compiler can check as well as visually making the code easier to read. It also makes it easier to read and refactor later.

在这种情况下,您最好创建一个类来封装参数,因为您可以通过编译器可以检查的方式为不同的参数赋予意义,也可以直观地使代码更容易阅读。它还使以后更容易阅读和重构。

// old way
Shniz(1,2,3,2,3,2,1,2);
Shniz(1,2,2,3,3,2,1,2); 

//versus
ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 };
Shniz(p);

Alternatively if you had:

或者,如果你有:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, 
           Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

This is a far different case because all the objects are different (and aren't likely to be muddled up). Agreed that if all objects are necessary, and they're all different, it makes little sense to create a parameter class.

这是一个非常不同的情况,因为所有对象都是不同的(并且不太可能混淆)。同意如果所有对象都是必需的,并且它们都是不同的,那么创建参数类就没有意义了。

Additionally, are some parameters optional? Are there method override's (same method name, but different method signatures?) These sorts of details all matter as to what the best answer is.

另外,一些参数是可选的吗?是否有方法覆盖(相同的方法名称,但不同的方法签名?)这些类型的细节都与最佳答案有关。

* A property bag can be useful as well, but not specifically better given that there is no background given.

*一个属性包也是有用的,但没有特别好,因为没有给出背景。

As you can see, there is more than 1 correct answer to this question. Take your pick.

如您所见,这个问题有1个以上的正确答案。随便挑选。

#11


You can try to group your parameter into multiples meaningful struct/class (if possible).

您可以尝试将参数分组为多个有意义的struct / class(如果可能)。

#12


I would generally lean towards the structs approach - presumably the majority of these parameters are related in some way and represent the state of some element that is relevant to your method.

我通常倾向于结构方法 - 大概这些参数中的大多数以某种方式相关并且表示与您的方法相关的某个元素的状态。

If the set of parameters can't be made into a meaningful object, that's probably a sign that Shniz is doing too much, and the refactoring should involve breaking the method down into separate concerns.

如果参数集不能成为有意义的对象,那可能是Shniz做得太多的一个迹象,重构应该包括将方法分解为单独的问题。

#13


If your language supports it, use named parameters and make as many optional (with reasonable defaults) as possible.

如果您的语言支持它,请使用命名参数并尽可能多地生成可选(具有合理的默认值)。

#14


I think the method you described is the way to go. When I find a method with a lot of parameters and/or one that is likely to need more in the future, I usually create a ShnizParams object to pass through, like you describe.

我认为你描述的方法是要走的路。当我找到一个包含大量参数和/或将来可能需要更多参数的方法时,我通常会创建一个ShnizParams对象来传递,就像你描述的那样。

#15


How about not setting it in all at once at the constructors but doing it via properties/setters? I have seen some .NET classes that utilize this approach such as Process class:

如何不在构造函数中同时设置它,而是通过属性/ setter来完成它?我见过一些使用这种方法的.NET类,比如Process类:

        Process p = new Process();

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.FileName = "cmd";
        p.StartInfo.Arguments = "/c dir";
        p.Start();

#16


You can trade complexity for source code lines. If the method itself does too much (Swiss knife) try to halve its tasks by creating another method. If the method is simple only it needs too many parameters then the so called parameter objects are the way to go.

您可以交换源代码行的复杂性。如果方法本身做得太多(瑞士刀)尝试通过创建另一种方法将其任务减半。如果方法很简单,只需要太多参数,那么所谓的参数对象就是最佳选择。

#17


I concur with the approach of moving the parameters into a parameter object (struct). Rather than just sticking them all in one object though, review if other functions use similar groups of parameters. A paramater object is more valuable if its used with multiple functions where you expect that set of parameters to change consistently across those functions. It may be that you only put some of the parameters into the new parameter object.

我同意将参数移动到参数对象(struct)的方法。不过只是将它们全部放在一个对象中,而是检查其他函数是否使用相似的参数组。如果paramater对象与多个函数一起使用,并且您希望这些参数集在这些函数中一致地更改,则它更有价值。可能是您只将一些参数放入新参数对象中。

#18


If you have that many parameters, chances are that the method is doing too much, so address this first by splitting the method into several smaller methods. If you still have too many parameters after this try grouping the arguments or turning some of the parameters into instance members.

如果你有那么多参数,很可能该方法做得太多,所以首先通过将方法分成几个较小的方法来解决这个问题。如果此后仍有太多参数,请尝试对参数进行分组或将某些参数转换为实例成员。

Prefer small classes/methods over large. Remember the single responsibility principle.

喜欢小型课程/大型课程。记住单一责任原则。

#19


Named arguments are a good option (presuming a language which supports them) for disambiguating long (or even short!) parameter lists while also allowing (in the case of constructors) the class's properties to be immutable without imposing a requirement for allowing it to exist in a partially-constructed state.

命名参数是一个很好的选择(假设支持它们的语言),用于消除长(或甚至短!)参数列表的歧义,同时还允许(在构造函数的情况下)类的属性是不可变的,而不强制要求允许它存在在一个部分建构的状态。

The other option I would look for in doing this sort of refactor would be groups of related parameters which might be better handled as an independent object. Using the Rectangle class from an earlier answer as an example, the constructor which takes parameters for x, y, height, and width could factor x and y out into a Point object, allowing you to pass three parameters to the Rectangle's constructor. Or go a little further and make it two parameters (UpperLeftPoint, LowerRightPoint), but that would be a more radical refactoring.

我在做这种重构时会寻找的另一个选项是相关参数组,这些参数可能更好地作为独立对象处理。使用前面答案中的Rectangle类作为示例,获取x,y,height和width参数的构造函数可以将x和y分解为Point对象,允许您将三个参数传递给Rectangle的构造函数。或者更进一步,使它成为两个参数(UpperLeftPoint,LowerRightPoint),但这将是一个更激进的重构。

#20


It depends on what kind of arguments you have, but if they are a lot of boolean values/options maybe you could use a Flag Enum?

这取决于你有什么样的参数,但如果它们是很多布尔值/选项,你可以使用Flag Enum吗?

#21


I think that problem is deeply tied to the domain of the problem you're trying to solve with the class.

我认为这个问题与你试图用类解决的问题的领域密切相关。

In some cases, a 7-parameter constructor may indicate a bad class hierarchy: in that case, the helper struct/class suggested above is usually a good approach, but then you also tend to end up with loads of structs which are just property bags and don't do anything useful. The 8-argument constructor might also indicate that your class is too generic / too all-purpose so it needs a lot of options to be really useful. In that case you can either refactor the class or implement static constructors that hide the real complex constructors: eg. Shniz.NewBaz (foo, bar) could actually call the real constructor passing the right parameters.

在某些情况下,7参数构造函数可能表示一个错误的类层次结构:在这种情况下,上面建议的辅助结构/类通常是一个很好的方法,但是你也倾向于最终得到大量的结构,这些结构只是属性包并且不做任何有用的事情。 8参数构造函数也可能表明您的类太通用/太通用,因此它需要很多选项才能真正有用。在这种情况下,您可以重构类或实现隐藏真正复杂构造函数的静态构造函数:例如。 Shniz.NewBaz(foo,bar)实际上可以调用真正的构造函数传递正确的参数。

#22


One consideration is which of the values would be read-only once the object is created?

一个考虑因素是,一旦创建了对象,哪些值是只读的?

Publicly writable properties could perhaps be assigned after construction.

可以在施工后指定公开可写的属性。

Where ultimately do the values come from? Perhaps some values are truely external where as others are really from some configuration or global data that is maintained by the library.

价值最终来自哪里?也许某些值确实是外部的,而其他值实际上来自某些配置或库维护的全局数据。

In this case you could conceal the constructor from external use and provide a Create function for it. The create function takes the truely external values and constructs the object, then uses accessors only avaiable to the library to complete the creation of the object.

在这种情况下,您可以隐藏构造函数以免外部使用,并为其提供Create函数。 create函数接受真正的外部值并构造对象,然后使用仅可用于库的访问器来完成对象的创建。

It would be really strange to have an object that requires 7 or more parameters to give the object a complete state and all truely being external in nature.

拥有一个需要7个或更多参数的对象来赋予对象一个完整的状态并且所有这些都是真正的外在性将是非常奇怪的。

#23


When a clas has a constructor that takes too many arguments, it is usually a sign that it has too many responsibilities. It can probably be broken into separate classes that cooperate to give the same functionalities.

当一个clas有一个带有太多参数的构造函数时,通常表明它有太多的责任。它可以分成单独的类,它们合作以提供相同的功能。

In case you really need that many arguments to a constructor, the Builder pattern can help you. The goal is to still pass all the arguments to the constructor, so its state is initialized from the start and you can still make the class immutable if needed.

如果你真的需要构造函数的那么多参数,那么Builder模式可以帮助你。目标是仍然将所有参数传递给构造函数,因此它的状态从一开始就被初始化,如果需要,您仍然可以使该类不可变。

See below :

见下文 :

public class Toto {
    private final String state0;
    private final String state1;
    private final String state2;
    private final String state3;

    public Toto(String arg0, String arg1, String arg2, String arg3) {
        this.state0 = arg0;
        this.state1 = arg1;
        this.state2 = arg2;
        this.state3 = arg3;
    }

    public static class TotoBuilder {
        private String arg0;
        private String arg1;
        private String arg2;
        private String arg3;

        public TotoBuilder addArg0(String arg) {
            this.arg0 = arg;
            return this;
        }
        public TotoBuilder addArg1(String arg) {
            this.arg1 = arg;
            return this;
        }
        public TotoBuilder addArg2(String arg) {
            this.arg2 = arg;
            return this;
        }
        public TotoBuilder addArg3(String arg) {
            this.arg3 = arg;
            return this;
        }

        public Toto newInstance() {
            // maybe add some validation ...
            return new Toto(this.arg0, this.arg1, this.arg2, this.arg3);
        }
    }

    public static void main(String[] args) {
        Toto toto = new TotoBuilder()
            .addArg0("0")
            .addArg1("1")
            .addArg2("2")
            .addArg3("3")
            .newInstance();
    }

}