Home > Software engineering >  Same methods in children, not in Parent
Same methods in children, not in Parent

Time:11-05

I have an interface, such as:

public interface IParent   {
}

And then:

public interface IClassA extends IParent {

    @Nonnull
    IClassAKind getKind ();


    @Nonnull
    @Nonempty
    default String getKindID ()
    {
        return getKind ().getID ();
    }
}

And a second one similar:

public interface IClassB extends Parant {
    
    @Nonnull
    IClassBKind getKind ();
  
    @Nonnull
    @Nonempty
    default String getKindID ()
    {
        return getKind ().getID ();
    } 
}

So both child interfaces have the same methods, but not the parent.

The issue is when I have an instance, that can be of any type, but I want to call the getKind().

As for now I do:

IParent  aCurrentObject = getImplementation();
IClassKind kind = null;
if(aCurrentObject instanceof IClassA){
    kind = ((IClassA) aCurrentObject).getKind();
} else {
    kind = ((IClassB) aCurrentObject).getKind();
}

I'm doing these if and casing too many times in the code. Any idea how to make it nicer?

CodePudding user response:

It looks that you have an issue with IClassAKind and IClassBKind. So, I think you need something like this

public interface IClassKind {
    IClassKind getKind();
}

class IClassAKind implements IClassKind {

    @Override
    public IClassAKind getKind() {
        // return A-Kind object
    }
}

class IClassBKind implements IClassKind {

    @Override
    public IClassBKind getKind() {
        // return B-Kind object
    }
}

So, after his change, your could will be without any casting

IParent aCurrentObject = getImplementation();
IClassKind kind = kind = aCurrentObject.getKind();

Also, need to update IClassA and IClassB to use the interface IClassKind instead of a concrete class. For example

public interface IClassA extends IParent {

    @Nonnull
    IClassKind getKind();
    
    // ... 
}

CodePudding user response:

Why don't you declare getKind() and getKindID() in the parent interface ? Both child interfaces will inherit of these methods.

public interface IParent   {
    @Nonnull
    IParent getKind ();

    @Nonnull
    @Nonempty
    default String getKindID ()
    {
        return getKind().getID ();
    }
}

public interface IClassA extends IParent {}

public interface IClassB extends IParent {}

CodePudding user response:

Try using generics as the following:

public interface IParent<T> {
    T getKind();
}

IClassA

public interface IClassA extends IParent<IClassAKind> {
    @Nonnull
    @Nonempty
    default String getKindID ()
    {
        return getKind().getID ();
    }
}

IClassB

public interface IClassB extends IParent<IClassBKind> {
    @Nonnull
    @Nonempty
    default String getKindID ()
    {
        return getKind().getID();
    }
}

CodePudding user response:

No, as you designed it, java intentionally doesn't offer anything. There's a good reason for that. I changed some names but other than that, the code below is identical to what you have:

interface HandheldObject {}

interface Gun implements HandheldObject {
  void shoot(Person p);
}

interface Camera implements HandheldObject {
  void shoot(Person p);
}

If java makes it easy to call a shoot(Person) method if it exists, then you will definitely end up killing somebody by accident. That would be bad. Hence, nope, you can't do that, and that's a good thing.

The specific principle at work here is that fields and methods aren't 'defined' by their name alone; they are defined by not just their name but also the fully qualified name of the type that defines them. The actual name of a shoot method in the example above is com.foo.pkgThatStuffIsIn.Gun.shoot(Person) - and thus confusion is impossible. There's no way that fully qualified affair is going to accidentally conflict with something completely different.

The alternative ('just call shoot if it exists') is called structural typing and it's not a property java has (intentionally). In languages like python or javascript, you really can write the java equivalent of:

Object o = ....;
o.shoot(myFriend);

and if o happens to be a camera, that'll make a picture, if it happens to be a gun, uhoh, and if it's something else, you get an error at runtime that 'shoot' isn't a thing. Java just doesn't 'do' structural typing like this. Some hybrid languages (like scala) let you create an ersatz type that implies 'anything that has a shoot(Person) method', but java isn't one of those.

Solution

The java way to solve this problem is to roll with it: Make a type. In my example, Camera's shoot and Gun's shoot are utterly unrelated; the fact that their method names are identical is just a weird coincidence in the english language. There shouldn't be a solution and fortunately there isn't. But in your example, perhaps IClassAKind's getKind() method and IClassBKind's getKind() method are related. That is:

  • They mean, semantically, the same thing.
  • The author of IClassAKind and the author of IClassBKind are aware of this and intended it to mean the same thing. In addition they know that it has to mean the same thing in the future too: They are ready to promise that future updates won't change this, and if they do change this, they will mark the new version as backwards incompatible.

If that is the case, then the general concept of this getKind stuff is expressible on its own, so do that:

public interface IParent {}

public abstract class Kind { ... }

public interface Kinded {
  @Nonnull
  Kind getKind();

  @Nonnull @Nonempty
  default String getKindId() {
    return getKind.getId();
  }
}

public interface IClassA extends IParent, Kinded {}

public interface IClassB extends IParent, Kinded {}

You didn't explain what the actual underlying model of any of this is, so that's just so you get the idea. Perhaps Kinded should be defined as interface Kinded implements IParent. It's also possible you definition of Kind needs some methods, and it's even possibly you want a specific subtype of IParent to come with a more specific kind of 'Kind', and involve some self-referential generics to sort this out. Self-ref generics gets a bit complicated, as you are now trying to establish a link between the IClassA and ClassAKind types. As a simple example, a single type having this loop:

interface Foobar<A extends Foobar<A>> {
  A returnSelf();

  public default A name(String name);
}

// can be implemented:

class SpecificFoobar extends Foobar<SpecificFoobar> {
  SpecificFoobar returnSelf() {
    return this;
  }

  // ...
}

and this lets you do stuff like:

SpecificFoobar sf = new SpecificFoobar().name("Hello");

without the generics that wouldn't compile (the name impl might return this; but if the Foobar interface defines it as public Foobar name(String name); then javac doesn't know that, it only knows that it returns Foobar, not SpecificFoobar.

If this all sounds complicated to you, and the first snippet I showed is good enough, then forget about this. As I said, it's a bit complicated; generics can be like that.

  •  Tags:  
  • java
  • Related