通过向其添加方法来扩展 Java 库

Extend a Java Library by adding methods to it

我正在尝试了解如何在 Java 中正确地 "extend" 第 3 方库(具有 类 和接口)以及如何简单地将它们用作直接替换.

我目前每次调用它时都使用 Twitter4J library. Right now I have to manually prepend @ to the result of getScreenName() method of User

twitter4j.User user = getTwitterUser();
String userHandle   = "@" + user.getScreenName();

我目前已经尝试了一些方法,但没有任何效果。这就是我想出的:

package my.app;

public abstract class User implements twitter4j.User {
    public String getHandle() {
        return "@" + getScreenName();
    }
}

这样称呼它:

User user = (User) getTwitterUser();
String userHandle = user.getHandle();

这会崩溃并显示消息:

java.lang.ClassCastException: twitter4j.UserJSONImpl cannot be cast to my.app.User

Java 对象的工作方式有点像在现实世界中。

我们打个汽车比喻。你的地方旁边有一个卖车的车库。所以你可以从车库取车:

Car car = garage.buyCar(money);

车库刚好卖的车是沃尔沃。所以,你得到一辆车,而这辆车(Car 是一个接口)实际上是一辆沃尔沃。如果你知道这辆车是沃尔沃,你可以这样做:

Volvo car = (Volvo) garage.buyCar(money);

这根本不会改变车库销售汽车的方式或车库销售的汽车类型。只是因为您知道它们是沃尔沃,所以您可以转换结果以访问沃尔沃汽车的特性。

请注意,这现在有效。但没有什么能阻止车库后来决定出售大众汽车而不是沃尔沃汽车。如果他们这样做,演员表将不再有效,这将是你的错:车库告诉你它卖汽车,但没有保证它会是沃尔沃。

现在,您希望车库改为生产保时捷。正在努力

Porsche car = (Porsche) garage.buyCar(money);

行不通。仅仅因为你非常希望这辆车成为保时捷,不会神奇地将车库生产的沃尔沃改成保时捷。


你不能那样扩展库

您所能做的就是获取 User 并调用实用程序方法来创建该用户的句柄。或者创建你自己的 UserWithHandle class 来实现 User,添加一个 getHandle() 方法,并通过委托给原始的、包装的 User:[=21 来实现所有其他方法=]

UserWithHandle u = new UserWithHandle(getTwitterUser())

如果库允许您提供一个 UserFactory 实现,您可以做任何您想做的事情,这将取代它以您自己的方式创建用户的方式。有点像车库允许您提供自己的油漆工。


做你想做的事,即向现有 class 添加方法在 Java 中是不可能的,但在某些语言中是可能的,例如 Kotlin(与 Java 兼容)并在 JVM 上运行,如果您有兴趣的话),它们在其中被称为 扩展方法 。基本上,这些方法是静态实用方法,例如,将 User 作为参数,return 作为其句柄,但可以像 User 的实例方法一样调用。 =21=]

您收到 ClassCastException 的原因是因为您试图将 getTwitterUser() 的结果转换为您的 User 实例,而它 returns twitter4j.User

如果您希望它起作用,仅创建您的 class 是不够的,您还需要创建另一种 getTwitterUser() 风格来创建它。

类似于:

public User getMyTwitterUser() {
  Twitter4j tUser = getTwitterUser();
  User myUser = new User(tUser);// assuming you have a constructor that builds your user from the original one
  return myUser;
}

如果你想扩展一个库,你必须为它创建一个包装器并根据你的需要添加方法。但是您不能向现有库或框架添加任何方法 class。这里分享了一个例子,可以帮助你更好地理解。

Interface TwitterUserWrappable {
  String getHandle();
  String getDescription();
}

//use this Wrapper pattern to create methods as per your need

Class TwitterUserWrapper: TwitterUserWrappable {
   private twitter4j.User user;
   
   public TwitterUserWrapper(twitter4j.User twitterUser) {
     this.user = twitterUser;
   }

   //Customize the return as per your need after getting screenName
   @override
   public String getHandle() { 
     return "@" + user.getScreenName();
   }

   //Customize the return as per your need after getting description
   @Override
   public String getDescrption() {
     return user.getDescription() + "Customized"; // you can also customize the return as per your need
   }
}

Class Demo {
 public static void main(String[] args) {
   TwitterUserWrappable user = TwitterUserWrapper(getTwitterUser());
   user.getHandle(); //returns what you expected
 }
}