Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement interfaces with homographic methods in Java?

In English, a homograph pair is two words that have the same spelling but different meanings.

In software engineering, a pair of homographic methods is two methods with the same name but different requirements. Let's see a contrived example to make the question as clear as possible:

interface I1 { 
    /** return 1 */ 
    int f()
}
interface I2 {
    /** return 2*/
    int f()
}
interface I12 extends I1, I2 {}

How can I implement I12? C# has a way to do this, but Java doesn't. So the only way around is a hack. How can it be done with reflection/bytecode tricks/etc most reliably (i.e it doesn't have to be a perfect solution, I just want the one that works the best)?


Note that some existing closed source massive piece of legacy code which I cannot legally reverse engineer requires a parameter of type I12 and delegates the I12 both to code that has I1 as a parameter, and code that has I2 as a parameter. So basically I need to make an instance of I12 that knows when it should act as I1 and when it should act as I2, which I believe can be done by looking at the bytecode at runtime of the immediate caller. We can assume that no reflection is used by the callers, because this is straightforward code. The problem is that the author of I12 didn't expect that Java merges f from both interfaces, so now I have to come up with the best hack around the problem. Nothing calls I12.f (obviously if the author wrote some code that actually calls I12.f, he would have noticed the problem before selling it).

Note that I'm actually looking for an answer to this question, not how to restructure the code that I can't change. I'm looking for the best heuristic possible or an exact solution if one exists. See Gray's answer for a valid example (I'm sure there are more robust solutions).


Here is a concrete example of how the problem of homographic methods within two interfaces can happen. And here is another concrete example:

I have the following 6 simple classes/interfaces. It resembles a business around a theater and the artists who perform in it. For simplicity and to be specific, let's assume they are all created by different people.

Set represents a set, as in set theory:

interface Set {
    /** Complements this set,
        i.e: all elements in the set are removed,
        and all other elements in the universe are added. */
    public void complement();
    /** Remove an arbitrary element from the set */
    public void remove();
    public boolean empty();
}

HRDepartment uses Set to represent employees. It uses a sophisticated process to decode which employees to hire/fire:

import java.util.Random;
class HRDepartment {
    private Random random = new Random();
    private Set employees;

    public HRDepartment(Set employees) {
        this.employees = employees;
    }

    public void doHiringAndLayingoffProcess() {
        if (random.nextBoolean())
            employees.complement();
        else
            employees.remove();
        if (employees.empty())
            employees.complement();
    }
}

The universe of a Set of employees would probably be the employees who have applied to the employer. So when complement is called on that set, all the existing employees are fired, and all the other ones that applied previously are hired.

Artist represents an artist, such as a musician or an actor. An artist has an ego. This ego can increase when others compliment him:

interface Artist {
    /** Complements the artist. Increases ego. */
    public void complement();
    public int getEgo();
}

Theater makes an Artist perform, which possibly causes the Artist to be complemented. The theater's audience can judge the artist between performances. The higher the ego of the performer, the more likely the audience will like the Artist, but if the ego goes beyond a certain point, the artist will be viewed negatively by the audience:

import java.util.Random;
public class Theater {
    private Artist artist;
    private Random random = new Random();

    public Theater(Artist artist) {
        this.artist = artist;
    }
    public void perform() {
        if (random.nextBoolean())
            artist.complement();
    }
    public boolean judge() {
        int ego = artist.getEgo();
        if (ego > 10)
            return false;
        return (ego - random.nextInt(15) > 0);
    }
}

ArtistSet is simply an Artist and a Set:

/** A set of associated artists, e.g: a band. */
interface ArtistSet extends Set, Artist {
}

TheaterManager runs the show. If the theater's audience judges the artist negatively, the theater talks to the HR department, which will in turn fire artists, hire new ones, etc:

class TheaterManager {
    private Theater theater;
    private HRDepartment hr;

    public TheaterManager(ArtistSet artists) {
        this.theater = new Theater(artists);
        this.hr = new HRDepartment(artists);
    }

    public void runShow() {
        theater.perform();
        if (!theater.judge()) {
            hr.doHiringAndLayingoffProcess();
        }
    }
}

The problem becomes clear once you try to implement an ArtistSet: both superinterfaces specify that complement should do something else, so you have to implement two complement methods with the same signature within the same class, somehow. Artist.complement is a homograph of Set.complement.

like image 751
Dog Avatar asked May 11 '13 06:05

Dog


Video Answer


2 Answers

New idea, kinda messy...

public class MyArtistSet implements ArtistSet {

    public void complement() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

        // the last element in stackTraceElements is the least recent method invocation
        // so we want the one near the top, probably index 1, but you might have to play
        // with it to figure it out: could do something like this

        boolean callCameFromHR = false;
        boolean callCameFromTheatre = false;

        for(int i = 0; i < 3; i++) {
           if(stackTraceElements[i].getClassName().contains("Theatre")) {
               callCameFromTheatre = true;
           }
           if(stackTraceElements[i].getClassName().contains("HRDepartment")) {
               callCameFromHR = true;
           }
        }

        if(callCameFromHR && callCameFromTheatre) {
            // problem
        }
        else if(callCameFromHR) {
            // respond one way
        }
        else if(callCameFromTheatre) {
            // respond another way
        }
        else {
            // it didn't come from either
        }
    }
}
like image 120
Gray Kemmey Avatar answered Sep 23 '22 22:09

Gray Kemmey


Despite Gray Kemmey's valiant attempt, I would say the problem as you have stated it is not solvable. As a general rule given an ArtistSet you cannot know whether the code calling it was expecting an Artist or a Set.

Furthermore, even if you could, according to your comments on various other answers, you actually have a requirement to pass an ArtistSet to a vendor-supplied function, meaning that function has not given the compiler or humans any clue as to what it is expecting. You are completely out of luck for any sort of technically correct answer.

As practical programming matter for getting the job done, I would do the following (in this order):

  1. File a bug report with whoever created an interface requiring ArtistSet and whoever generated the ArtistSet interface itself.
  2. File a support request with the vendor supplying the function requiring an ArtistSet and ask them what they expect the behavior of complement() to be.
  3. Implement the complement() function to throw an exception.
public class Sybil implements ArtistSet {
  public void complement() { 
    throw new UnsupportedOperationException('What am I supposed to do'); 
  }
  ...
}

Because seriously, you don't know what to do. What would be the correct thing to do when called like this (and how do you know for sure)?

class TalentAgent {
    public void pr(ArtistSet artistsSet) {
      artistSet.complement();
    }
}

By throwing an exception you have a chance at getting a stack trace that gives you a clue as to which of the two behaviors the caller is expecting. With luck nobody calls that function, which is why the vendor got as far as shipping code with this problem. With less luck but still some, they handle the exception. If not even that, well, at least now you will have a stack trace you can review to decide what the caller was really expecting and possibly implement that (though I shudder to think of perpetuation a bug that way, I've explained how I would do it in this other answer).

BTW, for the rest of the implementation I would delegate everything to actual Artist and Set objects passed in via the constructor so this can be easily pulled apart later.

like image 44
Old Pro Avatar answered Sep 21 '22 22:09

Old Pro