Idea: @InterfaceOf or @ExtractInterface

180 views
Skip to first unread message

TamasHegedus

unread,
Apr 9, 2019, 5:01:02 PM4/9/19
to Project Lombok
Recently I had to write many interfaces that only contained getters and setters, because we were integrating with a TypeScript frontend and they do like multiple inheritance very much. I would like to reflect those connections in my Java code too, so I started to use interfaces on my DTOs. I came across https://github.com/rzwitserloot/lombok/issues/1285 , where the conslusion was that getters and setters on interfaces are hard to implement because fields in interfaces are automatically static final. This means we don't have a Java syntax that could reflect this intention well.

So I thought of an alternate solution for the same core problem, have lombok to convert a whole class to an interface!

Example 1:

```
@InterfaceOf
public class FooBar {
    public int getFoo() { return 1; }
    public abstract int doBar(int arg);
}
```

Would generate:

```
public interface FooBar {
    int getFoo();
    int doBar(int arg);
}
```

Example 2:

```
@InterfaceOf
@Getter
@Setter
public class Positionable {
    private int position;
}
```

Would generate:

```
public interface Positionable {
    int getPosition();
    void setPosition(int value);
}
```

Alternate interface name suggestions: @ExtractInterface

I am willing and would be more than happy to contribute and implement such an annotation in Lombok, but first let's have a discussion, let's see if others would use it as well.

-Tamas

Leonhard Brenner

unread,
Apr 9, 2019, 5:21:30 PM4/9/19
to project...@googlegroups.com
I like the idea but I see 2 issues. First in the case above your class is not actually implementing the interface generated from it. Also, I would want an alternate constructor in the class which takes the interface and copies the values from it to the default constructor. This would give me something like this:

public interface Positionable { //Derived from DTO
    int getPosition();
    void setPosition(int value);
}

public class Positionable implements IPositionable { //Actually implement the generated interface
    public Positionable(int position) { //Constructor generation triggered by @InterfaceOf 
        this.position = position
    } 
    public Positionable(IPositionable source) {
        this(source)
    } 
     int getPosition() { return position } //Generated by @Getter
    void setPosition(int value) { position = value } //Generated by @Setter

I think rolling all of the annotations into one would be nice. Maybe @DtoWithInterface? The benefit of this is when combined with delegation which in Kotlin is free via `by` I can build composite objects that proxy to an interface then wrap them in the second constructor above and make transient objects concrete. The pattern works real nice for building GraphQL like class structures. Connect everything in a lazy object and put a DTO around the entry point.

Thank you again for responding.
Leonhard

--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

TamasHegedus

unread,
Apr 9, 2019, 6:56:10 PM4/9/19
to Project Lombok
Let me respond inline.
 
I like the idea but I see 2 issues. First in the case above your class is not actually implementing the interface generated from it. Also, I would want an alternate constructor in the class which takes the interface and copies the values from it to the default constructor.
 
Well, this is already something much more than I originally thought of. In my original idea, the original class would be removed, and the interface would take its place. So no class would be there to implement the interface nor to have a constructor. In my usecase, the Positionable class (not the interface) in itself would be meaningless, would be unused. I planned to use it to enforce some structural typing, not to inherit those fields. I would use my version like:

```
@InterfaceOf
@Getter
@Setter
public class Positioned {
    private int position;
}

@InterfaceOf
@Getter
@Setter
public class Titled {
    private String title;
}

@Getter
@Setter
public class PositionedImageDto implements Positioned, Image {
    private int position;
    private String imageUrl;
}

@Getter
@Setter
public class TitledImageDto implements Titled, Image {
    private String title;
    private String imageUrl;
}

@Getter
@Setter
public class PageDto {
    private Titled featured;
    private List<Positioned> widgets;
}

```

In your example, what would you use the Positionable class for? Can you show an example?

I think rolling all of the annotations into one would be nice. Maybe @DtoWithInterface? The benefit of this is when combined with delegation which in Kotlin is free via `by` I can build composite objects that proxy to an interface then wrap them in the second constructor above and make transient objects concrete. The pattern works real nice for building GraphQL like class structures. Connect everything in a lazy object and put a DTO around the entry point.

I have to digest it a littlebit more because I don't think I understood everything. Are you suggesting that you could use this feature for a kind of "typed partial object graphs", to represent the response of a GraphQL query where not all the existing fields and connections are queried? Doesn't that cause an exponential blowup of the number of generated classes? I did think about something like this when writing Hibernate backed repositories where some of the functions fetched one subset of the navigation properties eagerly, and some of them fetched another subset. But I failed to make it work even in theory because I couldn't represent the expected parameter classes for my service classes. For example:

```
public interface Address {  public String getAddress(); }
public interface Name {  public String getName(); }
public interface Age {  public int getAge(); }

@Entity
@Getter
public class PersonEntity implements Address, Name, Age {
    private String address;
    private String name;
    private int age;
}

...
// This is fine
public static <T extends Name & Address> void printNameAndAddress(T sth) { println(sth.getName(), sth.getAddress()); }

// What do I write in place of XXX ? XXX should implement Name and Address but not Age?
public static <T extends Address> XXX addNameToAddress(T sth, String name) {
    XXX result = new XXX();
    result.setAddress(sth.getAddress());
    result.setName(name);
    return result;
}

```

Your suggestion seems to be a cool addition but I still need some explanation about how it would be used.
To unsubscribe from this group and stop receiving emails from it, send an email to project...@googlegroups.com.

Leonhard Brenner

unread,
Apr 9, 2019, 7:21:38 PM4/9/19
to project...@googlegroups.com
Here is an abstract example of what I do in Kotlin. I am happy to translate to java if you would like. I am thinking if the pattern we are discussing can give me a concise repleca of interface X and it's DTO. It does not need to be an inner class I just thought it made it easy to find. I am OK with class being the source of the meta data. This is used for model to resource translation. I have other patterns like this for posting graph like objects. Not working directly with a POJO let's me build impls in the form of class Zebra and because they are not the data class themselves I can override them or delegate to them since they implement my interface. I like to use a DTO only to send data over the wire.

```kotlin
//Playground of ideas skip to line 48 for and example of Entity to Resource adapter.
interface X {
    val l: String
    val m: String
    data class Dto(override val l: String, override val m: String): X {
        constructor(source: X): this(source.l, source.m)
    }
}

interface Y {
    val n: String
    val o: String
    data class Dto(override val n: String, override val o: String): Y {
        constructor(source: Y): this(source.n, source.o)
    }
}

interface Z {
    val l: String
    val m2: String //This is an example of +/- of a field
    val n: String
    val o: String
    data class Dto(override val l: String, override val m2: String, override val n: String, override val o: String): Z {
        constructor(source: Z): this(source.l, source.m2, source.n, source.o)
    }
}

//Here we will compose an implementation of Z based on delegation to X and Y
//    Z gets (l) from X, (n, o) from Z and we will implement m2 on our own
class Zebra(val x: X, val y: Y): X by x, Y by y, Z {
    //We can lazy load
    override val m2 get() = m + "2"
}

//Since the DTO implements our X interface this is one way we can roll
val x = X.Dto("Lenny", "Mark")
//or we can do this which allows us to lazy load n in this case
val y = object: Y {
    override val n: String
        get() = "Nancy"
    override val o = "Oprah"

}

//Since Zebra implement Z we can construct a DTO from it.
val zebra = Z.Dto(Zebra(x, y))
```

To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.

Leonhard Brenner

unread,
Apr 9, 2019, 7:24:18 PM4/9/19
to project...@googlegroups.com
I like to use the DTO only for sending over wire, sticking in a hash, printing, comparing, ... Actually having the equals, hashcode on the interface would be nice. Then I could mix them into different implementations.

Leonhard

Reinier Zwitserloot

unread,
Apr 10, 2019, 12:36:02 PM4/10/19
to Project Lombok
Generating entirely new sourcefiles based off of existing source files is something plain jane annotation processors can already do. Lombok is not necessarily against doing something that plain jane APs can do, but does make the value proposition of this feature request a bit murky. It's not that much effort to write something like this yourself, and this isn't exactly a task that comes up every other day for Joe Q. average java coder.

I do admit that, _IF_ you need this, you presumably need it in many places and the amount of boilerplate saved by the feature is very significant. So, I'm not quite ready to shoot it down entirely.

I assume you want this interface in a new sourcefile? Or do you want it as an internal type, so, you want lombok to turn your code into something like:

  --- BEGIN dump of Foo.java ---

public class Foo implements Foo.IFoo /* looks recursively weird but javac will accept this kind of thing */ {
    private String someField;

    public String getSomeField() { return this.someField; }

    public interface IFoo { // this is an inner-interface!
        String getSomeField();
    }
}

--- END dump --

that might be a lombok job. But how do we name the interface? We can't name it the same as the class. For name 'X', prefix it with a capital I? Ugh, feels ugly. Force the user to explicitly pick a name? "@InterfaceOf("ActualNameOfInterfaceGoesHere")?

pragmatically speaking though, if the point is to generate a separate, new source file, (so not the inner-interface thing), I don't think lombok should be doing this.

Leonhard Brenner

unread,
Apr 11, 2019, 10:34:36 AM4/11/19
to project...@googlegroups.com
I would prefer generating within the source of the metadata as you have it. I've done the plain jane separate generated files many times. As for the name of the interface above I suppose it could be Foo.Interface. The big thing for me is the constructor setup and that only getters are exposed in the interface. Also, properties should be final. Pretty much like this:
```
        public class Foo implements Foo.Interface /* looks recursively weird but javac will accept this kind of thing */ {

            public interface Interface { // this is an inner-interface!
                String getSomeField();
            }

            /* Allows me to make changes to object without deep copy. */
            public class Decorator {
                private final source Foo.Interface
                Decorator(source.Foo.Interface) {
                    this.source = source
                }
                String getSomeField() {
                    return source.someField 
                }
            }

            private final String someField;

            public Foo(String someField) {
                this.someField = someField;
            }

            public Foo(Foo.Inteface source) {
                this(source.someField)
            }

            public String getSomeField() { return this.someField; }

        }
```
per

--

Reinier Zwitserloot

unread,
Apr 11, 2019, 7:57:41 PM4/11/19
to Project Lombok
So how would lombok know which methods to put in the interface? Literally 'all the getters'? That sounds entirely arbitrary; you'd have to make a clear case as to why that's a useful concept.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombok+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages