简体   繁体   中英

Proper use of Comparable with generics

I'm having trouble getting an interface to work the way I want using generics.

I have a CRUD-style interface to handle data access of various objects. Comparable<?> is used for getting objects by identifier since objects may have identifiers of different types:

public interface DataProvider<T> {
  T create(T object);
  T get(Comparable<?> id);
  void update(T object);
  void delete(T object);
}

Now imagine one such (contrived) object that needs to be accessed, Book :

public class Book implements Comparable<Book> {

  private ISBN isbn;

  public int compareTo(Book other) {
    return getIsbn().compareTo(other.getIsbn());
  } 

  // ...
}

What I would like to be able to do is this:

public class BookDataProvider implements DataProvider<Book> {
  public Book create(Book book) { ... }
  public Book get(ISBN isbn) { ... }
  public void update(Book book) { ... }
  public void delete(Book book) { ... }
}

What's the simplest way to modify DataProvider and/or Book to allow get(ISBN) to compile?

I've come up with a couple partial solutions, but I'm not sure either is optimal:

  • Type DataProvider as DataProvider<T, I extends Comparable<I>> , but this adds an extra type parameter.
  • Introduce an Identifiable interface per this related post , make Book implement it, and type DataProvider as DataProvider<T extends Identifiable<?>> , but then I don't achieve the signature for get(ISBN) .

Ideally I would like to limit DataProvider to have only one type parameter. I'm open to other approaches as well. Thanks.

Using Comparable as a parameter in application specific getter method is not appropriate. Ideally your get method is not supposed to work for any Comparable. It is only for particular types like ISBN. SO I suggest instead of struggling for Comparable generics, go for common interface for such identifiers.

Lets take 2nd approach from your partial options & create one solution. Below is code that I suggest.

public class TestGenerics {
    public static void main(String[] args) {

    }
}

interface Identifiable<T extends Identifier> {
    T getIdentifier();
}

interface Identifier {

}

class Book implements Comparable<Book>, Identifiable<ISBN> {

    private ISBN isbn;

    public int compareTo(Book other) {
        return getIdentifier().compareTo(other.getIdentifier());
    }

    public ISBN getIdentifier() {

        return null;
    }

}

class ISBN implements Comparable<ISBN>, Identifier {

    public int compareTo(ISBN o) {
        return 0;
    }

}

interface DataProvider<T extends Identifiable<? extends Identifier>> {
    T create(T object);

    T get(Identifier id);

    void update(T object);

    void delete(T object);
}

class BookDataProvider implements DataProvider<Book> {

    public Book create(Book book) {
        return null;
    }

    public void update(Book book) {
    }

    public void delete(Book book) {
    }

    public Book get(Identifier id) {
        // TODO Auto-generated method stub
        return null;
    }

}

Now this approach is truly generic because now at top level you have interfaces Identifiable & Identifier which means, every Identifiable (eg. book, pdf etc) must have some Identifier (eg. ISBN, bookNumber etc).

Now you implement this top level hierarchy with Book as Identifiable & ISBN as Identifier. THis will be your implementation layer.

Then you create DataProvider interface for Identifiable mentioned in generics. Its get method will have Identifier as parameter ie if DataProvider is for Book then its get method will be for ISBN.

Now here you implement DataProvider with BookDataProvider. In this class, you won't get ISBN as parameter, but instead you will have Identifier as parameter. You can pass ISBN in it as it is a Identifier.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM