以下(反)模式的名称是什么?它的优缺点是什么?


28

在过去的几个月中,我偶然发现了以下技术/模式。但是,我似乎找不到一个特定的名称,也不能百分百确定其所有优点和缺点。

模式如下:

在Java接口中,照常定义了一组常用方法。但是,使用内部类,默认实例会通过接口泄漏。

public interface Vehicle {
    public void accelerate();
    public void decelerate();

    public static class Default {
         public static Vehicle getInstance() {
             return new Car(); // or use Spring to retrieve an instance
         }
    }
 }

对我来说,最大的优势似乎在于,开发人员只需要了解接口,而无需了解其实现,例如,在他快速想要创建实例的情况下。

 Vehicle someVehicle = Vehicle.Default.getInstance();
 someVehicle.accelerate();

此外,我已经看到该技术与Spring一起使用,以便根据配置动态提供实例。在这方面,它看起来也可以帮助模块化。

但是,我无法撼动这是滥用接口的感觉,因为它将接口与其实现之一结合在一起。(相关性反转原理等。)有人可以向我解释该技术的调用方式以及它的优缺点吗?

更新:

经过一段时间的考虑后,我重新检查并注意到,使用以下单例版本的模式的频率更高。在此版本中,公共静态实例通过接口公开,该接口仅初始化一次(由于字段为final)。另外,几乎总是使用Spring或将接口与实现分离的通用工厂来检索实例。

public interface Vehicle {
      public void accelerate();
      public void decelerate();

      public static class Default {
           public static final Vehicle INSTANCE = getInstance();

           private static Vehicle getInstance() {
                return new Car(); // or use Spring/factory here
           }
      }
 }

 // Which allows to retrieve a singleton instance using...
 Vehicle someVehicle = Vehicle.Default.INSTANCE;

简而言之:这似乎是一个自定义的单例/工厂模式,它基本上允许通过其接口公开实例或单例。关于缺点,下面的答案和评论中提到了一些缺点。到目前为止,优势似乎在于其便利性。


某种单例模式?
布赖恩·陈

我认为您是对的,该接口不应“知道”其实现。可能Vehicle.Default应该作为工厂类(例如)上移到包名称空间中VehicleFactory
augurar

7
顺便说一下,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski 2013年

20
这里的反模式是使用与动物相似的反模式密切相关的汽车相似反模式。大多数软件没有轮子或腿。
Pieter B

@PieterB::-))))你成就了我的一天!
布朗

Answers:


24

Default.getInstance恕我直言,IMHO只是工厂方法的一种非常特殊的形式,与取自单例模式的命名约定混合在一起(但不是后者的实现)。在当前形式中,这违反了“ 单一职责原则 ”,因为接口(已经用于声明类API的目的)承担了提供默认实例的额外职责,最好将其放置在单独的实例中VehicleFactory类。

这种结构引起的主要问题是,它引起Vehicle和之间的循环依赖Car。例如,在当前形式下,将无法放置Vehicle在一个库中,而Car在另一个库中。使用单独的工厂类并在其中放置Default.getInstance方法将解决该问题。给该方法起一个不同的名字也可能是一个好主意,以防止与单例相关的任何混乱。所以结果可能像

VehicleFactory.createDefaultInstance()


感谢您的回答!我同意这个特定示例违反了SRP。但是,我不确定在动态检索实现的情况下是否仍然违反。例如,通过使用Spring (或类似框架)来检索由例如配置文件定义的特定实例。
杰罗姆

1
@Jérôme:恕我直言,非嵌套类命名VehicleFactory比嵌套结构更常规Vehicle.Default,尤其是在使用具有误导性的“ getInstance”方法时。尽管可以通过使用Spring来避免循环依赖,但出于个人常识,我个人更喜欢第一个变体。而且,它仍然留给你的工厂转移到一个不同的组件,后来改变了执行工作的选项,而不春等
布朗博士

通常,使用接口而不是类的原因是允许具有其他用途的类可被需要接口实现的代码使用。如果默认的实现类可以满足所有代码需求,而这些代码仅需要某种以“常规”方式实现接口的代码,那么指定一种创建实例的方法对于接口而言似乎是一件有用的事情。
supercat 2014年

如果Car是Vehicle的非常通用的标准实现,则不违反SRP。车辆仍然只有一个更改的理由,例如Google添加autodrive()方法时。然后,必须非常通用的Car进行更改以实现该方法,例如,通过引发NotImplementedException,但这不会赋予对Vehicle进行更改的其他理由
user949300 2014年

@ user949300:SRP不是“数学可证明的”概念。而且软件设计决策并不总是“黑白”的。原始代码还算不错,它当然不能在生产代码中使用,并且正如OP在他的“更新”中亲自指出的那样,有时便利性超过了循环依赖性。因此,请阅读我的回答“考虑是否使用单独的工厂类不能更好地为您服务”。还请注意,在我给出答案后,作者稍微改变了他原来的问题。
布朗

3

在我看来,这就像Null Object模式。

但这在很大程度上取决于Car类的实现方式。如果以“中立”的方式实现,那么它肯定是一个Null对象。这意味着,如果您可以删除接口上的所有null检查,并放置此类的实例而不是每次出现的null,则代码仍必须能够正常工作。

同样,如果是这种模式,那么在接口本身与null对象的实现之间创建紧密的耦合也没有问题。因为空对象可以在使用所述接口的任何地方。


2
您好,感谢您的回答。尽管我很确定在我的情况下这不是用来实现“空对象”模式的,但这是一个有趣的解释。
杰罗姆
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.