【问题标题】:Is there anything wrong with a class with all static methods?具有所有静态方法的类有什么问题吗?
【发布时间】:2011-01-29 01:23:43
【问题描述】:

我正在做代码审查,遇到了一个使用所有静态方法的类。入口方法接受几个参数,然后开始调用其他静态方法,传递入口方法收到的全部或部分参数。

它不像 Math 类,它的实用函数基本不相关。在我自己的正常编程中,我很少编写 Resharper 弹出并说“这可能是一个静态方法”的方法,当我这样做时,它们往往是无意识的实用方法。

这种模式有什么问题吗?如果一个类的状态是保存在字段和属性中还是使用参数在静态方法之间传递,这只是个人选择的问题吗?

UPDATE:正在传递的特定状态是来自数据库的结果集。该类的职责是从数据库的结果集中填充一个 excel 电子表格模板。我不知道这是否有什么不同。

【问题讨论】:

  • 在您编辑后,我会说是的,绝对应该是一个实例化的类,因为您正在维护状态(Excel 电子表格),所以我有一个类可以将您的结果集放在构造函数,以及另一种方法,如“Save(string filename)”或“Save(Worksheet excel)”来创建 Excel 电子表格。我猜的所有其他方法都是辅助方法,应该是私有的。
  • @Andy 我不同意。这取决于它的使用方式。如果相同的信息被多次次传递到方法中,那么它应该是一个实例,它作为一个成员。但如果它只传入一次(然后传入帮助程序/子方法),那么它可能没问题。不过,帮助程序/子方法确实需要是私有的。
  • 写它的人一定对函数的黑盒化深信不疑。

标签: c# java oop


【解决方案1】:

这有什么问题吗 图案?这只是一个问题吗 个人选择,如果一个状态 类保存在字段和属性中 或在静态之间传递 使用参数的方法?

从我个人的经验来说,我已经开发了 100 个 KLOC 应用程序,它们具有非常非常深的对象层次结构,一切都继承和覆盖其他一切,一切都实现了六个接口,甚至接口继承了六个接口,系统实现了书中的每一个设计模式等等。

最终结果:一个真正具有 OOP 风格的架构,具有如此多的间接级别,以至于调试任何东西都需要数小时。我最近开始使用这样的系统开始工作,我将学习曲线描述为“一堵砖墙,然后是一座山”。

有时过度使用 OOP 会导致类变得如此细化,以至于它实际上是一种净伤害。

相比之下,许多函数式编程语言,甚至像 F# 和 OCaml(以及 C#!)这样的 OO 语言,都鼓励扁平和浅层的层次结构。这些语言的库往往具有以下属性:

  • 大多数对象都是 POCO,或者最多具有一到两级继承,其中对象只不过是逻辑相关数据的容器。
  • 您可以使用模块(相当于静态类)来控制对象之间的交互,而不是类相互调用。
  • 模块往往作用于数量非常有限的数据类型,因此范围很窄。例如,OCaml List 模块表示对列表的操作,一个 Customer 模块促进对客户的操作。虽然模块与类上的实例方法具有或多或少相同的功能,但与基于模块的库的主要区别在于,模块更加独立,粒度更小,并且对其他模块的依赖很少(如果有的话)。
  • 通常不需要子类对象覆盖方法,因为您可以将函数作为一等对象传递以进行专门化。
  • 虽然 C# 不支持此功能,但functors 提供了一种方法来子类化专门模块。

大多数大型库往往是宽而不是深,例如 Win32 API、PHP 库、Erlang BIF、OCaml 和 Haskell 库、数据库中的存储过程等。所以这种编程风格是实战测试,似乎在现实世界中运行良好。

在我看来,设计最好的基于模块的 API 往往比设计最好的 OOP API 更容易使用。然而,编码风格在 API 设计中同样重要,因此如果您团队中的其他人都在使用 OOP,并且有人开始以完全不同的风格实现某些东西,那么您可能应该要求重写以更接近您的团队编码标准。

【讨论】:

  • 你的最后一点对我说。我想人们可以用过程语言编写 OOP,用 OOP 语言编写过程,甚至可以尝试用 OOP 或过程语言编写函数式或动态样式。在所有情况下,它都会编写与它所在的框架抗争的代码,并且对于期望在 Java 或 C# 程序中使用 OOP 的程序员来说,这将更难阅读。
  • 您描述的大型应用程序可能不是因为过度热情的 OOP,而是因为糟糕的设计。好的 OOP 设计并不是所有东西的子类化,尽管大多数 OOP 课程往往会提出可怕的 Animal-Mammal-Cow 示例,这会让您相信相反的情况。好的设计 IMO 是以一种有用的方式分解复杂性,这允许在未来发生预期的变化而不会崩溃。
【解决方案2】:

您所描述的只是结构化编程,可以在 C、Pascal 或 Algol 中完成。这在本质上并没有错。 种情况是 OOP 更合适,但 OOP 不是最终答案,如果手头的问题最好通过结构化编程来解决,那么一个充满静态方法的类就是要走的路。 p>

【讨论】:

  • 确实,用 OO 语言进行结构化编程并没有什么坏处。但实际上浪费了很多潜力,例如使用类和实现作为使用继承的更专业行为的基本案例等。
  • +1,仅仅因为你有电锯并不意味着每一个问题都最好用电锯来解决。有时你真的只需要一把锤子。有时保持简单会更容易。
  • +1 表示问题只是结构化编程。 Excel 和一个,让我猜猜,关系数据库之间的管道?那不是OO。只要您不需要 OO 并且您不调用该 OO,就没有问题。
【解决方案3】:

改写问题是否有帮助:

您能否将静态方法操作的数据描述为具有以下属性的实体:

  • 明确的意思
  • 负责保持其内部状态一致。

在这种情况下它应该是一个实例化的对象,否则它可能只是一堆相关的函数,很像一个数学库。

【讨论】:

    【解决方案4】:

    这是我经常遇到的涉及静态方法的重构工作流程。它可能有助于深入了解您的问题。

    我将从一个具有相当好的封装性的类开始。当我开始添加功能时,我遇到了一个不需要访问我的类中的私有字段但似乎包含相关功能的功能。在这种情况发生几次(有时只是一次)之后,我开始在我已实现的静态方法中看到一个新类的轮廓,以及该新类与我第一次实现静态方法的旧类之间的关系。

    我看到将这些静态方法转换为一个或多个类的好处是,当您这样做时,通常会更容易理解和维护您的软件。

    【讨论】:

      【解决方案5】:

      我觉得如果类需要维护某种形式的状态(例如属性),那么它应该被实例化(即“普通”类。)

      如果应该只有这个类的一个实例(因此所有静态方法),那么应该有一个单例属性/方法或工厂方法,在第一次调用它时创建该类的实例,然后只提供其他人要求时的那个实例。

      话虽如此,这只是我个人的意见和我实施它的方式。我相信其他人会不同意我的观点。老实说,在不了解更多信息的情况下,很难给出支持/反对每种方法的理由。

      【讨论】:

      • 自 IoC 以来,单例模式不是已经过时了吗?
      • 国际奥委会?我看不出它怎么会过时 - 有很多用途,你只需要一个类的实例,而 IMO 单例模式是确保这一点的最佳方式。
      【解决方案6】:

      IMO 最大的问题是,如果您想对调用您提到的类的类进行单元测试,则无法替换该依赖项。所以你不得不同时测试客户端类和静态调用的类。

      如果我们谈论的是一个带有 Math.floor() 之类的实用方法的类,这并不是一个真正的问题。但是,如果该类是一个真正的依赖项,例如一个数据访问对象,那么它会将其所有客户端与其实现联系起来。

      编辑:我不同意人们说这种“结构化编程”“没有错”。我会说这样的类在普通 Java 项目中遇到时至少是一种代码味道,并且可能表明创建者对面向对象设计的误解。

      【讨论】:

      • “可能表示对面向对象设计的误解”...或者,他们只是不相信所有代码都必须是 OO。
      • 传统上,您会将所需的所有依赖项传递给静态类,因此方法可能有更多参数,但您可以轻松地对任何依赖项进行单元测试。良好的基于​​模块的编程会阻止紧密的模块耦合,因此您通常不会有模块调用其他模块 - 但如果您这样做,您会将static void A(someState) { B.HandleState(someState) } 重写为static void A(somestate, handleState) { handleState(someState) },因此可以用您的任何实现替换您的处理函数想要,包括对原始处理程序模块或模拟的调用。
      • @Adriaan Koster:+1... 对该类进行单元测试或需要模拟该“抽象”会非常困难。
      • @John:或者他们只是不相信所有代码都必须是面向对象的并且他们不相信正确的测试并且他们'重新误解了 OO 和测试。
      • 因为不能测试静态方法?只要您的测试框架可以测试非公共方法,就可以测试无状态方法的正确性。
      【解决方案7】:

      这种模式没有错。 C# 实际上有一个称为静态类的构造,它通过强制要求所有方法都是静态的来支持这个概念。此外,框架中有许多类具有此功能:EnumerableMath 等...

      【讨论】:

        【解决方案8】:

        没有什么问题。这是一种更“实用”的编码方式。它可以更容易测试(因为没有内部状态)并在运行时获得更好的性能(因为没有开销来实例化一个原本无用的对象)。

        但是你会立即失去一些面向对象的能力 静态方法(根本)不能很好地响应继承。 静态类不能参与许多设计模式,例如工厂/服务定位器。

        【讨论】:

          【解决方案9】:

          不,许多人倾向于为他们希望在相关命名空间下分组的实用函数创建完全静态的类。拥有完全静态的类有很多正当理由。

          在 C# 中要考虑的一件事是,许多以前完全静态编写的类现在可以被视为 .net 扩展类,它们的核心仍​​然是静态类。很多 Linq 扩展都是基于此。

          一个例子:

          namespace Utils {
              public static class IntUtils        {
                      public static bool IsLessThanZero(this int source)
                      {
                          return (source < 0);
                      }
              }
          }
          

          然后您可以简单地执行以下操作:

          var intTest = 0;
          var blNegative = intTest.IsLessThanZero();
          

          【讨论】:

          • 迂腐注意 - 它应该是“返回源
          • 哎呀,我打字太快了 :-) ,相应地进行了修改。
          【解决方案10】:

          使用静态类的一个缺点是它的客户端不能用测试替身替换它以便进行单元测试。

          同样,对静态类进行单元测试更难,因为它的协作者不能被测试替身替换(实际上,所有没有依赖注入的类都会发生这种情况)。

          【讨论】:

            【解决方案11】:

            这取决于传递的参数是否真的可以归类为状态

            让静态方法相互调用是可以的,以防所有实用程序功能都拆分为多个方法以避免重复。例如:

            public static File loadConfiguration(String name, Enum type) {
                String fileName = (form file name based on name and type);
                return loadFile(fileName); // static method in the same class
            }
            

            【讨论】:

              【解决方案12】:

              嗯,就个人而言,我倾向于认为修改对象状态的方法应该是该对象类的实例方法。事实上,我认为这是一个经验法则:修改对象的方法是该对象类的实例方法。

              但也有一些例外:

              • 处理字符串的方法(例如将首字母大写,或此类功能)
              • 方法是无状态的,只是简单地组装一些东西来产生一个新的东西,没有任何内部状态。它们显然很少见,但将它们设为静态通常很有用。

              事实上,我认为 static 关键字就是这样:一个应该小心使用的选项,因为它违反了一些 OOP 原则。

              【讨论】:

                【解决方案13】:

                将所有状态作为方法参数传递可能是一种有用的设计模式。它确保没有共享的可变状态,因此该类本质上是线程安全的。服务通常使用这种模式实现。

                但是,通过方法参数传递所有状态并不意味着方法必须是静态的 - 您仍然可以对非静态方法使用相同的模式。使方法静态的优点是调用代码可以通过按名称引用类来使用该类。不需要注入、查找或任何其他中间人。缺点是可维护性 - 静态方法不是动态分派,并且不能轻松子类化,也不能重构为接口。当类本质上只有一种可能的实现,并且有充分的理由不使用非静态方法时,我建议使用静态方法。

                【讨论】:

                  【解决方案14】:

                  “一个类的状态是......在使用参数的静态方法之间传递?” 这就是过程式编程的工作原理。

                  具有所有静态方法且没有实例变量(静态最终常量除外)的类通常是实用程序类,例如 Math。 制作一个统一类没有任何问题,(不是本身) 顺便说一句:如果创建一个实用程序类,你应该防止类 aver 被用来创建一个对象。在java中,你可以通过明确定义构造函数来做到这一点,但将构造函数设为私有。 正如我所说,创建实用程序类没有错, 如果大部分工作是由实用程序类完成的(至于 esc。不是通常意义上的类 - 它更多是函数的集合) 那么这很可能表明问题尚未使用面向对象的范式解决。 这可能是也可能不是一件好事

                  入口方法接受几个参数,然后开始调用其他静态方法,传递入口方法收到的全部或部分参数。 从这个声音来看,整个类实际上只是一种方法(这肯定是所有其他静态方法都是私有的(并且只是辅助函数),并且没有实例变量(禁止常量)) 这可能是好的, 是esc结构化/程序化编程,将它们(函数和它的助手)都捆绑在一个类中相当整洁。 (在 C 语言中,您只需将它们全部放在一个文件中,并声明助手的静态(意味着不能从该文件外部访问))

                  【讨论】:

                    【解决方案15】:

                    如果不需要创建一个类的对象,那么将所有方法创建为该类的静态方法就没有问题,但我想知道你在用一个充满静态方法的类做什么。

                    【讨论】:

                      【解决方案16】:

                      我不太清楚你所说的入口方法是什么意思,但如果你说的是这样的:

                       MyMethod myMethod = new MyMethod();
                       myMethod.doSomething(1);
                      
                       public class MyMethod {
                            public String doSomething(int a) {
                                String p1 = MyMethod.functionA(a);
                                String p2 = MyMethod.functionB(p1);
                                return p1 + P2;
                            }
                            public static String functionA(...) {...}
                            public static String functionB(...) {...}
                       }
                      

                      这是不可取的。

                      我认为,当您不必在类中保留任何内容时,使用所有静态方法/单例是编写业务逻辑的好方法。我倾向于使用它而不是单例,但这只是一种偏好。

                       MyClass.myStaticMethod(....);
                      

                      相对于:

                       MyClass.getInstance().mySingletonMethod(...);
                      

                      所有静态方法/单例也倾向于使用较少的内存,但取决于您拥有的用户数量,您甚至可能不会注意到它。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-11-19
                        • 2012-04-19
                        • 2012-11-18
                        相关资源
                        最近更新 更多