静态初始化程序的合法使用方式是什么?

6

我记得几年前我使用静态初始化器来调用类级别的设置操作。我记得它有非常奇怪的行为,所以我决定远离它们。也许是因为我搞错了从上到下的顺序或者我还是个新手。但是现在我需要重新审视它们,并且我想确保没有更好的方法可以同样简洁。

我知道这不是时髦的做法,但是我经常有数据驱动的类,它们维护一个从数据库中导入的实例静态列表。

public class StratBand { 
      private static volatile ImmutableList<StratBand> stratBands = importFromDb();

      private final int minRange;
      private final int maxRange;

      private static ImmutableList<StratBand> importFromDb() { 
            //construct list from database here
      }
      //constructors, methods, etc
}

当我有数十个类似于这个的表驱动类时,这种模式非常简洁(是的,我知道它将类与一种数据/实例源紧密耦合)。
然而,当我发现Google Guava的好处时,我想使用EventBus在发布某个事件时更新静态列表。我会创建一个静态final布尔变量只是为了调用初始化注册的静态方法。
public class StratBand { 
      private static volatile ImmutableList<StratBand> stratBands = importFromDb();
      private static final boolean subscribed = subscribe();

      private final int minRange;
      private final int maxRange;

      private static ImmutableList<StratBand> importFromDb() { 
            //construct list from database here
      }
      //constructors, methods, etc

      private static boolean subscribe() {
            MyEventBus.get().register(new Object() { 
                @Subscribe
                public void refresh(ParameterRefreshEvent e) { 
                    stratBands = importFromDb();
                }
            });
        return true;
      }
}

这很快就变得很烦人了,因为编译器会对已订阅的变量从未被使用发出警告。而且,它只会增加混乱。所以我想知道是否可以使用静态初始化程序,如果我不将其解耦成两个或更多个类,那么真的没有更好的方法吗?您有什么想法吗?

 public class StratBand { 
          private static volatile ImmutableList<StratBand> stratBands = importFromDb();

          static { 
           MyEventBus.get().register(new Object() { 
                    @Subscribe
                    public void refresh(ParameterRefreshEvent e) { 
                        stratBands = importFromDb();
                    }
                });
          }

          private final int minRange;
          private final int maxRange;

          private static ImmutableList<StratBand> importFromDb() { 
                //construct list from database here
          }
          //constructors, methods, etc


    }

2
它之所以“不流行”,正是因为像你发现的那样,它确实会导致“非常奇怪的行为”。通常最好的做法是根本不要将这些东西静态化,而是在需要它们的地方显式(或隐式地通过依赖注入)传递它们。当它们只创建单例或进行纯计算时,静态初始化程序是可以接受的,但通常不应与文件系统、数据库或任何外部交互。 - Louis Wasserman
我一直在等待有人这么说。我理解 DI 范式,也很欣赏它。但在我们准备好升级到 DI 驱动的框架之前,我还希望能够维持现有的状态更长一些。 - tmn
1
你在这里不需要使用依赖注入,但如果你能让它正常工作,我会感到惊讶,而且它仍然非常脆弱,难以测试,坦率地说,这比正确地完成它需要更多的努力。 - Louis Wasserman
在这个问题上,我可能会避免使用静态初始化程序。 - tmn
1个回答

3

我想知道是否可以使用静态初始化程序

有趣的是

private static final boolean subscribed = subscribe();

并且

private static final boolean subscribed;
static {
    subscribed = subscribe();
}

编译后的字节码完全相同,因此使用不必要的静态变量严格来说是更差的选择。


但在我们准备升级到 DI 驱动的框架之前,

了解 Guice。虽然它是框架,但它易于使用,并且可以让您摆脱 static

或者手动完成。通过去掉所有静态修饰符并在需要时传递它,重写您的类。有时会相当冗长,但明确说明依赖关系允许您在隔离的情况下测试类。

现在,无论被测试的方法有多么微不足道,都无法测试 StratBand 而不触及数据库。问题是每个 StratBand 实例与所有 StratBand 列表的耦合。

此外,您无法测试取决于 stratBands 内容的行为,因为它总是从数据库加载(当然,您可以相应地填充数据库,但这很麻烦)。

首先,我会创建 StratBandManager(或 StratBands 或您喜欢的任何名称),并将所有静态功能移动到其中。为了简化过渡,我会创建一个带有静态助手的临时类,例如

private static StratBandManager stratBandManager = new StratBandManager();
public static ImmutableList<StratBand> stratBands() {
   return stratBandManager.stratBands();
}

然后弃用所有内容,并通过DI(使用Guice或手动完成)进行替换。
我发现即使对于小项目,Guice也很有用。开销很小,因为通常没有或几乎没有配置。

是的,我已经花了很多时间研究Guice,尽管我还没有应用它,但我想这样做。考虑到我们当前的业务环境和职能,我们采取了最简主义的方法...最简主义意味着如果简化了事情,就要合并和减少类的数量。我一定需要考虑您的过渡建议,因为社区似乎非常坚定地支持解耦和使用DI。 - tmn
@ThomasN。我不会将类的数量最小化。您可能希望使用嵌套类来管理,以保持文件数量较少,但是额外的类基本上是免费的(除了在Android上可能很重要的内存成本)。 - maaartinus
是的,我一直喜欢嵌套类,因为它可以将所有内容都包含在一个地方。但我也意识到,当复杂度达到一定阈值时,它可能会对可维护性造成压力。我想我会改变我的方法,开始更多地利用包私有范例,并采用 DI 友好的设计。 - tmn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接