Java - Getter/Setter,行为和接口

4

我有一个问题,有点理论性:

假设我有以下类:

interface ReportInterface {
     void execute();
}

class Report implements ReportInterface {

  private final Repository rep; 

  Report(Repository ref){
     this.rep = ref;
  }

  public void execute(){
     //do some logic
  }
}


class ReportWithSetter implements ReportInterface {

  private final Repository rep;
  private String release;

  ReportWithSetter(Repository ref){
     rep = ref;
  }

  public void execute(){
     if (release == null) throw IlligalArgumentException("release is not specified");
     //do some logic
  }
  
  public void setRelease(String release){
     this.release=release;
  }
}


第二份报告需要一个额外的参数release才能正常工作,但我的接口未定义execute方法的参数,因此我通过使用setter方法来解决这个问题,代码如下:
ReportWithSetter rep2 = new ReportWithSetter (rep);
rep.setRelease("R1.1");
rep.execute();

所以我不喜欢这个额外的rep.setRelease。它看起来很奇怪和人工 - 这个类的用户可能会感到困惑,例如,如果我在Spring中将该类作为单例bean,那么它是潜在错误的来源,如果第二次请求它,有人忘记了第二次触发rep.setRelease。除了把它放进构造函数里(我想把它做成一个Spring bean),有什么最佳实践来处理这种情况吗?


如果是这种情况,那么ReportWithSetterReport可能不应该实现相同的Report接口。ReportWithSetter.execute应该带有一个release参数。 - Sweeper
我会添加一个名为Versionnable的接口,其中只有一个getRelease()方法,将ReportWithSetter重命名为VersionnableReport,然后让它同时实现ReportVersionnable接口。 - Vincent C.
如果你将它放到构造函数中会有什么问题?这会防止你将其作为Spring Bean。 - haoyu wang
1
一个明智的解决方案是在ReportInterface中添加一个新的重载方法execute(String release),并为execute()方法提供一个默认值。 - Jerry Chin
4个回答

5

如果你可以更改接口,以下是我能想到的几个解决方案:

解决方案 #1

void execute(Optional<String> release);

或者
void execute(@Nullable String release);

然后在Report类中使用它们,可以使用execute(Optional.empty())execute(null)

解决方案 #2

void execute(String... release);

然后在Report类中使用execute()方法,在ReportWithSetter类中使用execute("R1.1")方法。

解决方案 #3

在接口中定义void execute();void execute(String release);。然后在实现时,对于不需要的方法抛出UnsupportedOperationException异常。例如,在Report类中,您可以这样做:

  public void execute(){
     //do some logic
  }

  public void execute(String release){
     throw new UnsupportedOperationException("Use the overloaded method");
  }

你还可以将这两种方法都设置为接口中的默认方法,这样你的实现类就不必担心实现不支持的方法。
使用最适合你阅读和维护的方式。

第二个解决方案非常靠不住,因为没有迹象表明他需要多个输入字符串,只是为了使用而试图弯曲接口。但其他解决方案值得一试。 - Dropout

2

解决方案1:Spring依赖注入 - 字段注入:

Spring的依赖注入使用反射,因此不需要Setter方法。
如果将您的Report类设置为Spring Bean,并使用@Autowired注入另一个bean,则不需要Setter方法。
代码如下:

@Component
class ReportWithRelease implements ReportInterface {

@Autowired private final Repository rep;
@Autowired private Release release;

public void execute(){
  if (release == null) throw IlligalArgumentException("release is not specified");
    //do some logic
  }
}

我将"String release"改为"Release release",因为使用"String"作为bean也很奇怪。所以"Release"类必须包含你的"String release"。

如果"String release"只包含一些配置值,在运行时不会更改。那么你可以使用@Value从属性文件中读取其String值。

解决方案2:Spring构造函数注入:

构造函数注入是另一个选项,更加推荐。然后你的Report bean将如下所示:

@Component
class ReportWithRelease implements ReportInterface {

private Repository rep;
private Release release;

@Autowired
public ReportWithRelease(Repository rep, Release release) {
  this.rep = rep;
  this.release = release;
}

public void execute(){
  if (release == null) throw IlligalArgumentException("release is not specified");
    //do some logic
  }
}

字段注入在测试时真的很糟糕,即使使用setter注入也更多是妥协,因为必需的依赖项可能不存在,你会在运行时得到一个漂亮的NPE。尽可能使用构造函数注入! - Christoph Grimmer
@ChristophGrimmer-Dietrich 你说得对,构造函数注入更加推荐,所以我更新了我的回答。我不同意字段注入被称为“糟糕”,但这超出了问题的范围。 - Elmar Brauch
由于你不得不使用公共非最终字段来进行适当的单元测试,或者使用受保护的字段并与子类一起测试,或者使用Mockito注入,而这对我个人来说都太过于反射魔法了,因此“难以测试” :-) - Christoph Grimmer

0

如果您想创建同一接口的不同类的实例,则工厂方法模式是很好的选择。

class MyFactory {
       ReportInterface createInstance(Class clazz, String... args) {
           if (Report.class.equals(clazz)) {
               return new Report();
           }
           if (ReportWithSetter.class.equals(clazz)) {
               return new ReportWithSetter(args[0]);
           }
           throw new IllegalArgumentException(clazz.getName());
       }
}


0

Spring当然提供自动装配,但引入@AutoWire应该出于系统化的目的。

在这里,您可以使用两阶段执行、工厂来实现。

class ReportFactory /*ReportWithSetter*/ {

  private final Repository rep;
  private final String release;

  private final ReportInterface report = ...;

  ReportFactory (Repository rep, String release) {
     this.rep = rep;
     this.release = release;
  }

  public ReportInterface report() {
      return report;
  }
}

new ReportFactory(rep, release).execute();

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