【问题标题】:Class with single method -- best approach?使用单一方法的类——最好的方法?
【发布时间】:2010-09-17 08:50:22
【问题描述】:

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

// 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() 这样的简单库函数。我正在考虑更多执行某些特定、复杂任务的类,但只需要一个(公共)方法即可。

【问题讨论】:

    标签: c# java oop static-methods


    【解决方案1】:

    我曾经喜欢用静态方法填充的实用程序类。他们对辅助方法进行了极大的整合,否则这些方法会导致冗余和维护地狱。它们非常易于使用,无需实例化,无需处置,只需一劳永逸。我想这是我第一次在不知情的情况下尝试创建面向服务的架构——许多无状态服务只是完成了它们的工作,没有别的。然而,随着系统的发展,巨龙即将到来。

    多态性
    假设我们有方法 UtilityClass.SomeMethod 愉快地嗡嗡作响。突然我们需要稍微改变一下功能。大多数功能是相同的,但我们仍然必须更改几个部分。如果它不是静态方法,我们可以创建一个派生类并根据需要更改方法内容。因为它是一个静态方法,我们不能。当然,如果我们只需要在旧方法之前或之后添加功能,我们可以创建一个新类并在其中调用旧类——但这太恶心了。

    界面问题
    由于逻辑原因,不能通过接口定义静态方法。而且由于我们不能覆盖静态方法,所以当我们需要通过接口传递静态类时,它们是无用的。这使我们无法使用静态类作为策略模式的一部分。我们可能会通过passing delegates instead of interfaces 解决一些问题。

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

    促进 blobs
    由于静态方法通常用作实用方法,而实用方法通常有不同的用途,我们很快就会得到一个充满不连贯功能的大型类——理想情况下,每个类在系统中应该有一个单一的用途。只要目的明确,我宁愿有五倍的课程。

    参数蠕变
    首先,那个可爱又纯真的静态方法可能只需要一个参数。随着功能的增长,添加了几个新参数。很快就会添加更多可选参数,因此我们创建方法的重载(或仅添加默认值,使用支持它们的语言)。不久之后,我们就有了一个需要 10 个参数的方法。只有前三个是真正需要的,参数 4-7 是可选的。但是如果指定了参数 6,那么 7-9 也需要填写...如果我们创建一个类的目的只是为了做这个静态方法所做的事情,我们可以通过在构造函数,并允许用户通过属性设置可选值,或者同时设置多个相互依赖的值的方法。此外,如果一个方法已经发展到如此复杂的程度,那么它很可能无论如何都需要在自己的类中。

    无缘无故要求消费者创建类的实例
    最常见的论点之一是,为什么要求我们类的消费者创建一个实例来调用这个单一方法,而之后却没有使用该实例?在大多数语言中,创建类的实例是一项非常便宜的操作,因此速度不是问题。向消费者添加额外的代码行成本很低,可以为未来更易于维护的解决方案奠定基础。最后,如果您想避免创建实例,只需创建一个允许轻松重用的类的单例包装器 - 尽管这确实要求您的类是无状态的。如果它不是无状态的,您仍然可以创建处理所有内容的静态包装器方法,同时从长远来看仍然为您提供所有好处。最后,您还可以创建一个隐藏实例化的类,就像它是一个单例一样:MyWrapper.Instance 是一个只返回 new MyClass();

    只有西斯才能绝对交易
    当然,我不喜欢静态方法也有例外。不会造成任何膨胀风险的真正实用程序类是静态方法的极好案例 - 以 System.Convert 为例。如果您的项目是一次性的,对未来的维护没有要求,那么整体架构确实不是很重要 - 静态或非静态,并不重要 - 但是开发速度才是。

    标准、标准、标准!
    使用实例方法不会阻止您也使用静态方法,反之亦然。只要差异化背后有推理并且它是标准化的。没有什么比查看拥有不同实现方法的业务层更糟糕的了。

    【讨论】:

    • “随着系统的增长......”你重构了吗?
    • @Mark S. Rasmussen 很棒的帖子。您会向方法传递参数还是在构造函数中初始化参数?
    • @user3667089 类在逻辑上存在所需的一般参数,我将在构造函数中传递这些参数。这些通常用于大多数/所有方法。我会在该特定方法中传递特定于方法的参数。
    • @MarkS.Rasmussen 澄清一下,对于具有单个方法的类,您会将其作为构造函数传入,因为它用于所有(单个)方法?跨度>
    • 这些原因大多与糟糕的代码管理有关,而不是与使用静态方法有关。在 F# 和其他函数式语言中,我们到处都使用基本上是静态方法(没有实例状态的函数)并且没有这些问题。也就是说,F# 为使用函数提供了更好的范例(函数是一流的,函数可以被柯里化和部分应用),这使得它们比在 C# 中更可行。使用类的一个更大的原因是因为 C# 就是为此而构建的。 .NET Core 中的所有依赖注入构造都围绕着具有 deps 的实例类。
    【解决方案2】:

    我更喜欢静态方式。由于 Class 不代表对象,因此创建它的实例是没有意义的。

    只存在于它们的方法的类应该保持静态。

    【讨论】:

    • -1 "只为其方法而存在的类应该保持静态。"
    • @Rookian 你为什么不同意?
    • 存储库模式就是一个例子。该类仅包含方法。遵循您的方法不允许使用接口。 => 增加耦合
    • @Rook,一般来说,人们不应该创建仅用于方法的类。在示例中,您提供的静态方法不是一个好主意。但是对于简单的实用方法,静态方法是最好的。
    • 绝对正确 :) 对于“简单实用方法”的条件,我完全同意你的看法,但你确实在你的回答中制定了一个一般规则,这是错误的 imo。
    【解决方案3】:

    如果没有理由创建类的实例来执行函数,则使用静态实现。为什么在不需要的时候让这个类的消费者创建一个实例。

    【讨论】:

      【解决方案4】:

      如果你不需要保存对象的状态,那么一开始就不需要实例化它。我会使用您将参数传递给的单个静态方法。

      我还要警告一个巨大的 Utils 类,它有几十个不相关的静态方法。这可能会很快变得混乱和笨拙。最好有很多类,每个类都有很少的相关方法。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          我真的不知道这里的情况如何,但我会考虑将其作为方法放在 arg1、arg2 或 arg3 所属的类之一中——如果你可以从语义上说其中一个类将拥有该方法。

          【讨论】:

            【解决方案7】:

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

            我的直觉是,如果您只需要一种方法,并且您将立即丢弃该类,那么将其设为一个接受所有参数的静态类。

            当然,很难准确地说出为什么您需要为这个方法创建一个类。这是大多数人假设的典型“实用程序类”情况吗?或者您是否正在实施某种规则类,未来可能会有更多。

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

            【讨论】:

              【解决方案8】:

              你的类可以设为静态吗?

              如果是这样,那么我会将它设为一个“实用程序”类,我会将所有单功能类都放入其中。

              【讨论】:

              • 我有点不同意。在我参与的项目中,您的 Utilities 类中可能包含数百个不相关的方法。
              • @Paul:普遍同意。我讨厌看到有几十个(或者是的,几百个)完全不相关的方法的类。如果必须采用这种方法,至少将它们分成较小的相关实用程序集(EG、FooUtilities、BarUtilities 等)。
              • 一等一的责任。
              【解决方案9】:

              如果这个方法是无状态的并且你不需要传递它,那么将它定义为静态是最有意义的。如果您确实需要传递该方法,您可以考虑使用 delegate 而不是您提出的其他方法之一。

              【讨论】:

                【解决方案10】:

                对于简单的应用程序和internal 助手,我会使用静态方法。对于带有组件的应用程序,我喜欢Managed Extensibility Framework。这是我正在编写的文档的摘录,用于描述您在我的 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
                        }
                    }
                }
                

                【讨论】:

                  【解决方案11】:

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

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

                  或

                  MyClass my_object(arg1, arg2, arg3);
                  

                  【讨论】:

                  • 如果您需要返回除 MyClass 类型的对象之外的任何内容怎么办?
                  • 我认为将执行逻辑放在构造函数中是不好的做法。好的开发是关于语义的。从语义上讲,构造函数的存在是为了构造一个对象,而不是执行其他任务。
                  • bill,如果需要返回值,请传递对 ret vvalue 的引用: MyClass myObject(arg1, arg2, arg3,retvalue); mstrobl,如果不需要对象,为什么要创建它?在某些情况下,这个技巧实际上可以帮助你。
                  • 如果一个对象没有状态,即没有变量,那么实例化它不会产生任何分配——既不在堆上也不在堆栈上。您可以将 C++ 中的对象视为只是 C 函数调用,其中隐藏的第一个参数指向一个结构。
                  • mstrobl,它就像类固醇上的 c 函数,因为您可以定义 ctor 可以使用的私有函数。您还可以使用类成员变量来帮助将数据传递给私有函数。
                  【解决方案12】:

                  另一个需要考虑的重要问题是系统是否会在多线程环境中运行,以及拥有静态方法或变量是否是线程安全的......

                  您应该注意系统状态。

                  【讨论】:

                    【解决方案13】:

                    您也许可以一起避免这种情况。尝试重构以获得arg1.myMethod1(arg2, arg3)。 如果更有意义,请将 arg1 与 arg2 或 arg3 交换。

                    如果你无法控制 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 提到的所有优势。

                    【讨论】:

                      【解决方案14】:

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

                      【讨论】:

                        【解决方案15】:

                        根据您是只想做某事还是做某事并返回某事,您可以这样做:

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

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-07-22
                          相关资源
                          最近更新 更多