单一方法上课-最佳方法?


172

说我有一个旨在执行单个功能的类。执行该功能后,可以将其销毁。是否有任何理由偏爱其中一种方法?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

我故意对细节含糊不清,以尝试获取针对不同情况的指导。但是我真的没有想到像Math.random()这样的简单库函数。我在想更多的类来执行一些特定的,复杂的任务,但只需要一个(公共)方法即可。

Answers:


264

我曾经喜欢用静态方法填充的实用程序类。他们对辅助方法进行了很好的合并,否则将导致冗余和维护麻烦。它们非常易于使用,无需实例化,无需处理,只是忘了忘了。我想这是我第一次尝试创建面向服务的体系结构-许多无状态服务只是完成了工作,而没有其他。但是,随着系统的发展,巨龙将会来临。

多态性
假设我们有方法UtilityClass.SomeMethod,它很高兴地嗡嗡作响。突然,我们需要稍微更改功能。大多数功能是相同的,但是我们仍然必须更改几个部分。如果它不是静态方法,我们可以制作一个派生类并根据需要更改方法的内容。由于它是静态方法,因此不能。当然,如果我们只需要在旧方法之前或之后添加功能,我们可以创建一个新类并在其中调用旧类-但这很麻烦。

接口问题
由于逻辑原因,无法通过接口定义静态方法。而且由于无法覆盖静态方法,因此当需要通过它们的接口传递静态类时,静态类是无用的。这使我们无法将静态类用作策略模式的一部分。我们可以通过传递委托而不是接口来修补一些问题。

测试
这基本上与上面提到的界面问题息息相关。由于我们互换实现的能力非常有限,因此我们也很难用测试代码替换生产代码。同样,我们可以将它们包装起来,但是这要求我们更改代码的大部分内容,以便能够接受包装器而不是实际的对象。

福斯特斑点
为静态方法通常被用作实用方法和实用方法通常都会有不同的目的,我们将很快结束了充满了非相干功能的一大类-理想情况下,每个类应该有系统内一个单一的目的。只要他们的目的得到明确定义,我宁愿选择五倍的课程。

参数蠕变
首先,这个可爱又天真的静态方法可能只需要一个参数。随着功能的增长,添加了两个新参数。不久便添加了可选的其他参数,因此我们创建了该方法的重载(或仅添加默认值(使用支持它们的语言))。不久,我们有了一个采用10个参数的方法。实际上只需要前三个,参数4-7是可选的。但是,如果指定了参数6,则还必须填写7-9 ...如果我们仅出于执行此静态方法的目的而创建了一个类,则可以通过在构造函数,并允许用户通过属性或方法设置可选值,以同时设置多个相互依赖的值。同样,如果一种方法已经发展到如此复杂的程度,

要求消费者无缘无故地创建类的实例
最常见的论据之一是,为什么要求我们的类的使用者创建一个实例来调用该单一方法,而之后却不再使用该实例呢?在大多数语言中,创建类的实例是非常便宜的操作,因此速度不是问题。向消费者添加额外的代码行是为将来奠定更易于维护的解决方案奠定基础的低成本。最后,如果要避免创建实例,只需创建类的单例包装即可,以方便重用-尽管这确实要求类是无状态的。如果不是无状态的,您仍然可以创建处理所有内容的静态包装器方法,而从长远来看仍然可以为您带来所有好处。最后,

只有Sith可以处理绝对值
当然,我对静态方法的不喜欢也有例外。对于静态方法而言,不会造成任何膨胀风险的真正实用程序类是绝佳的案例-以System.Convert为例。如果您的项目是一次性的,对将来的维护没有要求,那么总体架构真的不是很重要-静态还是非静态,都没关系-但是开发速度确实如此。

标准,标准,标准!
使用实例方法并不妨碍您也使用静态方法,反之亦然。只要差异化背后有其合理性并被标准化即可。没有什么比查看遍布着不同实现方法的业务层更糟糕的了。


正如Mark所说的,多态性,能够将方法作为“策略”参数进行传递以及能够配置/设置选项都是使用实例的原因。例如:一个ListDelimiter或DelimiterParser可以配置,以什么分隔符的使用/接受,是否从解析令牌装饰的空白,是否支架名单,如何看待一个空/空列表..等等
托马斯W¯¯

4
您重构了“随着系统的增长……”?
罗德尼·吉茨

3
@ user3667089该类在逻辑上存在是必需的常规参数,这些参数我将在构造函数中传递。这些通常会在大多数/所有方法中使用。我将在特定方法中传递的方法特定参数。
马克·拉斯穆森

1
确实,您使用静态类所做的事情要回到原始的,非面向对象的C语言(尽管由内存管理)-所有函数都在全局空间中,没有this,您必须管理全局状态等。如果您喜欢过程式编程,请执行此操作。但是请注意,您将失去OO的许多结构优势。
工程师

1
这些原因大多数与不良的代码管理有关,而不是与使用静态方法有关。在F#和其他函数式语言中,我们到处都使用基本上是静态的方法(没有实例状态的函数),而不会出现这些问题。也就是说,F#为使用函数提供了更好的范例(函数是一流的,可以进行函数化和部分应用),这使它们比C#更可行。使用类的更大原因是因为这是C#的目的。.NET Core中的所有依赖注入构造都围绕带有deps的实例类。
贾斯汀·斯塔克

89

我更喜欢静态方式。由于类不表示对象,因此没有必要创建该对象的实例。

仅为其方法存在的类应保持静态。


19
-1“仅为其方法存在的类应保持静态。”
Rookian 2011年

8
@Rookian为什么不同意呢?
jjnguy 2011年

6
存储库模式就是一个例子。该类仅包含方法。按照您的方法,不允许使用接口。=>增加耦合
Rookian

1
@Rook,通常人们不应该创建仅用于方法的类。在该示例中,您给了静态方法不是一个好主意。但是对于简单的实用方法,静态方法是最好的。
jjnguy

9
绝对正确:)对于您的条件“简单实用方法”,我完全同意,但是您在回答中确实制定了一条总规则,这是错误的imo。
Rookian

18

如果没有理由为了执行该函数而创建该类的实例,请使用静态实现。为什么在不需要实例时让此类的使用者创建一个实例。


16

如果您不需要保存对象的状态,则无需首先实例化它。我将使用您将参数传递给的单个静态方法。

我还警告不要使用巨型Utils类,该类具有许多不相关的静态方法。这可能会变得混乱而笨拙。最好有很多类,每个类只有很少的相关方法。


6

我会说“静态方法”格式将是更好的选择。而且我也将类设为静态,这样您就不必担心意外创建该类的实例了。


5

我真的不知道这里的情况如何,但我想将其作为方法放入arg1,arg2或arg3所属的一个类中-如果您可以从语义上说这些类之一将拥有方法。


4

我建议根据所提供的信息很难回答。

我的直觉是,如果您只打算使用一种方法,并且要立即丢弃该类,则将其设置为带有所有参数的静态类。

当然,很难确切地说明为什么只需要为此一种方法创建一个类。大多数人都假设这是典型的“公用事业类”情况吗?还是要实现某种规则类,将来可能会更多。

例如,让该类可插入。然后,您想为一个方法创建一个接口,然后希望将所有参数传递到接口中,而不是传递到构造函数中,但是您不希望它是静态的。


3

可以将您的课堂设为静态吗?

如果是这样,那么我将其设置为“实用程序”类,然后将所有单功能类放入其中。


2
我有些不同意。在我参与的项目中,您的Utilities类中可能包含数百种不相关的方法。
Paul Tomblin

3
@Paul:普遍同意。我讨厌看到带有数十个(或者是数百个)完全不相关的方法的类。如果必须采用这种方法,请至少将它们分成较小的相关组的实用程序(例如,EG,FooUtilities,BarUtilities等)。
John Rudy

9
一类,一类责任。
安德烈亚斯·彼得森

3

如果此方法是无状态的,并且您不需要传递它,那么将其定义为静态是最有意义的。如果您确实需要传递该方法,则可以考虑使用委托而不是其他建议的方法之一。


3

对于简单的应用程序和internal助手,我将使用静态方法。对于具有组件的应用程序,我喜欢“ 托管可扩展性框架”。这是我正在编写的文档的摘录,用于描述您将在各种API中找到的模式。

  • 服务
    • I[ServiceName]Service接口定义。
    • 按接口类型导出和导入。
    • 单一实现由主机应用程序提供,并在内部和/或扩展中使用。
    • 服务接口上的方法是线程安全的。

作为一个人为的例子:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

我只是在构造函数中做所有事情。像这样:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

要么

MyClass my_object(arg1, arg2, arg3);

如果您需要返回MyClass类型的对象之外的任何东西怎么办?
比尔蜥蜴

13
我认为将执行逻辑放在构造函数中是一种不好的做法。良好的发展与语义有关。从语义上说,存在一个构造器来构造一个对象,而不是执行其他任务。
mstrobl,

帐单,如果您需要返回值,请将引用传递给ret vvalue:MyClass myObject(arg1,arg2,arg3,retvalue); mstrobl,如果不需要该对象,为什么要创建它?这个技巧实际上可以在某些情况下为您提供帮助。

如果对象没有状态,即没有var,则实例化该对象将不会产生任何分配-既不在堆上也不在堆栈上。您可以将C ++中的对象视为只是C函数调用,并且带有指向结构的隐藏的第一个参数。
mstrobl,

mstrobl,它类似于类固醇上的ac函数,因为您可以定义ctor可以使用的私有函数。您还可以使用类成员变量来帮助将数据传递给私有函数。

0

需要考虑的另一个重要问题是系统是否将在多线程环境下运行,并且具有静态方法或变量是否对线程安全...

您应该注意系统状态。


0

您也许可以完全避免这种情况。尝试重构,以便您得到arg1.myMethod1(arg2, arg3)。如果更有意义,请用arg2或arg3交换arg1。

如果您无法控制arg1的类,请装饰它:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

原因是,在OOP中,数据和处理该数据的方法属于同一类。另外,您还将获得Mark提到的所有优势。


0

我认为,如果您的类或类实例的属性不会在构造函数或方法中使用,则建议不要将方法设计为“静态”模式。静态方法应始终以“帮助”方式考虑。


0

根据您是只想做某件事还是要做某件事然后返回某事,您可以执行以下操作:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.