安全地实施类型之间的关系

时间:2018-01-29 19:42:22

标签: java oop generics type-safety

我正在构建一个系统,用于编写关于拍摄鸟类照片的人的代码(真正的系统实际上并不是这样,我在这里用鸟来代替我无法发布的商业逻辑)。我在保持代码类型安全的同时也执行了我想要的所有关系并避免代码变得非常混乱。这就是我所拥有的。

有三种类型的鸟

public interface BirdType {}
public class BlueJay implements BirdType {}
public class Cardinal implements BirdType {}
public class Canary implements BirdType {}

对于每种类型的鸟类,都有一种专门拍摄该鸟类照相机的摄像机,以及一种吸引该类型鸟类的特殊鸟类呼叫。

public interface BirdCamera<BT extends BirdType> {}
public interface BirdCall<BT extends BirdType> {}
public class BlueJayCamera implements BirdCamera<BlueJay> {}
public class CardinalCamera implements BirdCamera<Cardinal> {}
public class CanaryCamera implements BirdCamera<Canary> {}
public class BlueJayCall implements BirdCall<BlueJay> {}
public class CardinalCall implements BirdCall<Cardinal> {}
public class CanaryCall implements BirdCall<Canary> {}

这些部分组合在摄影师界面中,该界面强化了摄影师对其实施者的部分之间的关​​系。

public interface Photographer 
  <BT extends BirdType,
   CAM extends BirdCamera<BT>,
   CAL extends BirdCall<BT>> 
{
  CAM getPhotographersCamera();
  CAL getPhotographersBirdCall();
  void examineCamera(CAM camera);
}

它由三个类实现,每个类一个:

public class BlueJayPhotographer implements Photographer<BlueJay, BlueJayCamera, BlueJayCall> {

  @Override
  public BlueJayCamera getPhotographersCamera() {
    //Do bluejay specific logic
    return new BlueJayCamera();
  }

  @Override
  public BlueJayCall getPhotographersBirdCall() {
    //Do bluejay specific logic
    return new BlueJayCall();
  }

  @Override
  public void examineCamera(BlueJayCamera camera) {
    //Do bluejay specific logic
  }
}

这是一个令人满意的事情,它可以防止其他开发人员未来的错误。其他人以后无法创建

public class ConfusedPhotographer implements Photographer<BlueJay, CardinalCamera, CanaryCall>

因为摄影师的限制阻止了它。

当一个班级想要使用摄影师时,他们会调用PhotographerProvider

public class PhotographerProvider {
  public Photographer get(int birdCode) {
    if (birdCode == 0) {
      return new BlueJayPhotographer();
    }
    else if (birdCode == 1) {
      return new CardinalPhotographer();
    }
    else if (birdCode == 2) {
      return new CanaryPhotographer();
    }
    else {
      throw new IllegalArgumentException("Unsupported bird code: " + birdCode);
    }
  }
}

需要在其许多方法中使用摄影师的一个类是NationalPark,它是我的应用程序的核心类之一。以下是NationalPark方法的示例:

  public void importantMethod(PhotographerProvider photographerProvider) {
    // blah blah logic goes here
    int mostCommonBirdType = 1;
    Photographer typicalTourist = photographerProvider.get(mostCommonBirdType);
    BirdCamera cam = typicalTourist.getPhotographersCamera();
    typicalTourist.examineCamera(cam);
    // blah blah more logic here
  }

编译器不喜欢该方法的最后一行,并产生警告: 未经检查的调用'examineCamera(CAM)'作为原始类型的成员'birdexample.Photographer'

我不想要这个警告,但是我找不到一种方法来修复它,不会在其他地方引起错误...我也想避免在我的每个班级中制造一个巨大的混乱整个节目必须用鸟类,相机和电话进行参数化。我怎么能这样做(不仅仅是抑制/忽略警告)?

3 个答案:

答案 0 :(得分:3)

您的具体示例可以在没有警告的情况下完成,如下所示:

Photographer<?, ?, ?> typicalTourist = photographerProvider.get(mostCommonBirdType);
foo(typicalTourist);

<BT extends BirdType,
 CAM extends BirdCamera<BT>> void foo(Photographer<BT, CAM, ?> typicalTourist) {
    CAM cam = typicalTourist.getPhotographersCamera();
    typicalTourist.examineCamera(cam);
}

但我会提醒你的设计。老实说,任何超过1种类型的变量都非常笨拙。 2对于像地图这样的情况是可以容忍的,但是3只是有点疯狂 - 你最终用类型声明填充了你的一半。 6 (你在真实代码中说的数字)完全属于仇恨自己,恨你的同事或两者兼而有之。

仔细想想你是否真的需要这些仿制药 - 如果你能摆脱它们,你可能会节省很多眼睛疲劳。

答案 1 :(得分:0)

除了Andy's answer之外,由于PhotographerProvider#get方法的原始返回类型为Photographer,因此public class PhotographerProvider { public Photographer<?, ?, ?> get(int birdCode) { if (birdCode == 0) { return new BlueJayPhotographer(); } else if (birdCode == 1) { return new CardinalPhotographer(); } else if (birdCode == 2) { return new CanaryPhotographer(); } else { throw new IllegalArgumentException("Unsupported bird code: " + birdCode); } } } 方法会发出编译器警告。要解决此问题,您可以使用以下内容:

In [111]: def mean1(x): return np.array(x).astype(int).mean()

In [112]: df.topics.str.split(',', expand=False).agg([mean1, len])
Out[112]:
       mean1  len
0  21.000000       3
1  19.666667       3
2  14.333333       3
3  16.333333       3

答案 2 :(得分:0)

你的方法PhotographerProvider.get返回未知类型的相机和鸟类的原始摄影师:

public Photographer get(int birdCode) {
[...]
}

你可以写一些像

这样的东西
public   <BT extends BirdType, CAM extends BirdCamera<BT>, CAL extends BirdCall<BT>> Photographer<BT, CAM, CALL> get(int birdCode) {
[...]
}

所以你得到了类型安全。问题是,至少在最新版本的java中,你可以在调用get方法时选择任何BT类型,对编译器也是如此。

事实上,所有这些系统都没有为您带来太多类型的安全性。例如,您实际使用整数实例化正确的类型。当一个人使用摄影师时,他根本不知道鸟类或相机,所以他并没有真正受益于这一切。

它提供附加值的唯一地方是给定BT类型的摄影师必须使用相机和所有给定类型...但你也可以只使用BT作为参数化类型:

public interface Photographer<BT extends BirdType> 
{
  BirdCamera<BT> getPhotographersCamera();
  BirdCall<BT> getPhotographersBirdCall();
  void examineCamera(BirdCamera<BT> camera);
}

你得到的方法会直接得到一个BirdType:

public <BT extends BirdType> Photographer<BT> get(BT birdType) {
[...]
}