简体   繁体   English

如何在Java中实现与单应性方法的接口?

[英]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 ? 我该如何实施I12 C# has a way to do this, but Java doesn't. C# 有办法做到这一点,但Java没有。 So the only way around is a hack. 所以唯一的方法是破解。 How can it be done with reflection/bytecode tricks/etc most reliably (ie 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. 需要注意的是遗留代码,我不能合法逆向工程现有的一些封闭源代码的庞大的一块需要类型的参数I12和代表I12既具有代码I1作为参数,和代码具有I2作为参数。 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. 所以基本上我需要创建一个I12的实例,它知道它何时应该作为I1 ,何时它应该作为I2 ,我相信可以通过直接调用者的运行时查看字节码来完成。 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. 问题是I12的作者没想到Java会从两个接口合并f ,所以现在我必须想出最好的解决问题的方法。 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). 没有任何东西可以调用I12.f (显然如果作者写了一些实际调用I12.f代码,他会在出售之前注意到这个问题)。

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). 请参阅Gray的答案以获得有效示例(我确信有更强大的解决方案)。


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. 我有以下6个简单的类/接口。 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: Set代表一个集合,如集合论:

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. HRDepartment使用Set来代表员工。 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. Set雇员的世界可能是申请雇主的雇员。 So when complement is called on that set, all the existing employees are fired, and all the other ones that applied previously are hired. 因此,当在该集合上调用complement时,将触发所有现有员工,并且之前应用的所有其他员工都被雇用。

Artist represents an artist, such as a musician or an actor. Artist代表艺术家,如音乐家或演员。 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. Theater使Artist表演,这可能使Artist得到补充。 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: 表演者的自我越高,观众就越有可能喜欢Artist ,但如果自我超越某一点,艺术家将受到观众的负面看法:

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 : ArtistSet只是一个ArtistSet

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

TheaterManager runs the show. TheaterManager运行节目。 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. 一旦你尝试实现ArtistSet ,问题就变得清晰了:两个超级ArtistSet都指定complement应该做其他事情,所以你必须以同样的方式在同一个类中实现具有相同签名的两个complement方法。 Artist.complement is a homograph of Set.complement . Artist.complement是单应Set.complement

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
        }
    }
}

How to Solve For Your Specific Case 如何解决您的具体案例

ArtistSet is simply an Artist and a Set: ArtistSet只是一个艺术家和一套:

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

From an OO perspective, that's not a useful declaration. 从OO的角度来看,这不是一个有用的声明。 An Artist is a type of noun, a "thing" that has defined properties and actions (methods). 艺术家是一种名词,一种定义了属性和动作(方法)的“东西”。 A Set is an aggregate of things - a collection of unique elements. 集合是事物的集合 - 独特元素的集合。 Instead, try: 相反,尝试:

ArtistSet is simply a Set of Artists. ArtistSet只是一组艺术家。

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

Then, for your particular case, the homonym methods are on interfaces that are never combined within the one type, so you have no clash and can program away... 然后,对于您的特定情况,同音词方法是在从不在一种类型中组合的接口上,因此您没有冲突并且可以编程...

Further, you don't need to declare ArtistSet because you aren't actually extending Set with any new declarations. 此外,您不需要声明ArtistSet因为您实际上并未使用任何新声明扩展Set。 You're just instantiating a type parameter, so you can replace all usage with Set<Artist> . 您只是实例化一个类型参数,因此您可以使用Set<Artist>替换所有用法。

How to Solve For the More General Case 如何解决更一般的案例

For this clash the method names don't even need to be homographic in the english language sense - they can be the same word with same english meaning, used in different contexts in java. 对于这种冲突,方法名称甚至不需要在英语意义上是单应性的 - 它们可以是具有相同英语含义的相同单词,在java中的不同上下文中使用。 Clash occurs if you have two interfaces that you wish to apply to a type but they contain the same declaration (eg method signature) with conflicting semantic/processing definitions. 如果您希望将两个接口应用于某个类型但它们包含具有冲突语义/处理定义的相同声明(例如方法签名),则会发生冲突。

Java does not allow you to implement the behaviour you request - you must have an alternative work-around. Java不允许您实现您请求的行为 - 您必须有另一种解决方法。 Java doesn't allow a class to provide multiple implementations for the same method signature from multiple different interfaces (implementing the same method multiple times with some form of qualification/alias/annotation to distinguish). Java不允许类为来自多个不同接口的相同方法签名提供多个实现(多次实现相同的方法,并使用某种形式的限定/别名/注释来区分)。 See Java overriding two interfaces, clash of method names , Java - Method name collision in interface implementation 请参阅Java覆盖两个接口,方法名称的冲突Java - 接口实现中的方法名称冲突

Eg If you have the following 例如,如果您有以下内容

 interface TV {
     void switchOn();
     void switchOff();
     void changeChannel(int ChannelNumber);
 }

 interface Video {
     void switchOn();
     void switchOff();
     void eject();
     void play();
     void stop();
 }

Then if you have an object that is both of these things, you can combine the two in a new interface (optional) or type: 然后,如果你有一个同时具有这两个东西的对象,你可以将它们组合在一个新的界面中(可选)或输入:

interface TVVideo {
     TV getTv();
     Video getVideo();
}


class TVVideoImpl implements TVVideo {
     TV tv;
     Video video;

     public TVVideoImpl() {
         tv = new SomeTVImpl(....);
         video = new SomeVideoImpl(....);
     }

     TV getTv() { return tv };
     Video getVideo() { return video };
}

Despite Gray Kemmey's valiant attempt, I would say the problem as you have stated it is not solvable. 尽管Gray Kemmey的勇敢尝试,我会说问题,因为你已经说过它是不可解决的。 As a general rule given an ArtistSet you cannot know whether the code calling it was expecting an Artist or a Set . 作为给定ArtistSet的一般规则,您无法知道调用它的代码是否期望ArtistSet

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. 此外,即使你可以根据你对各种其他答案的评论,你实际上还需要将ArtistSet传递给供应商提供的函数,这意味着函数没有给编译器或人类任何关于它期望的线索。 。 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. 向创建需要ArtistSet的界面的任何人以及自己生成ArtistSet界面的人提交错误报告。
  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. 向提供需要ArtistSet的函数的供应商ArtistSet支持请求,并询问他们对complement()的行为的期望。
  3. Implement the complement() function to throw an exception. 实现complement()函数以抛出异常。
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. 顺便说一句,对于其余的实现,我会将所有内容委托给通过构造函数传入的实际ArtistSet对象,以便以后可以轻松拆分。

How can I implement a class which has two superinterfaces having homographic methods? 如何实现一个具有两个具有单应方法的超接口的类?

In Java, a class which has two superinterfaces having homographic methods is considered to have only one implementation of this method. 在Java中,具有两个具有单应方法的超接口的类被认为仅具有该方法的一个实现。 (See the Java Language Specification section 8.4.8 ). (请参阅Java语言规范部分8.4.8 )。 This allows classes to conveniently inherit from multiple interfaces that all implement the same other interface and only implement the function once. 这允许类方便地从多个接口继承,这些接口都实现相同的其他接口,并且只实现一次该功能。 This also simplifies the language because this eliminates the need for syntax and method dispatching support for distinguishing between homographic methods based on which interface they came from. 这也简化了语言,因为这样就不需要语法和方法调度支持来区分基于它们来自哪个接口的单应方法。

So the correct way to implement a class which has two superinterfaces having homographic methods is to provide a single method that satisfies the contracts of both superinterfaces. 因此,实现具有两个具有单应方法的超接口的类的正确方法是提供满足两个超接口的契约的单个方法。

C# has a way to do this. C#有办法做到这一点。 How can it be done in Java? 如何在Java中完成? Is there no construct for this? 这个没有构造吗?

C# defines interfaces differently than Java does and therefore has capabilities that Java does not. C#定义的接口与Java不同,因此具有Java不具备的功能。

In Java, the language construct is defined to mean that all interfaces get the same single implementation of homographic methods. 在Java中,语言构造被定义为意味着所有接口都获得相同方法的相同单个实现。 There is no Java language construct for creating alternate behaviors of multiply-inherited interface functions based on the compile time class of the object. 没有Java语言构造用于基于对象的编译时类创建多重继承的接口函数的替代行为。 This was a conscious choice made by the Java language designers. 这是Java语言设计者的有意识选择。

If not, how can it be done with reflection/bytecode tricks/etc most reliably? 如果没有,如何最可靠地使用反射/字节码技巧/等?

"It" cannot be done with reflection/bytecode tricks because the information needed to decide which interface's version of the homographic method to choose is not necessarily present in the Java source code. “它”不能用反射/字节码技巧完成,因为决定选择哪个接口版本的单应方法所需的信息不一定存在于Java源代码中。 Given: 鉴于:

interface I1 { 
    // return ASCII character code of first character of String s 
    int f(String s); // f("Hello") returns 72
}
interface I2 {
    // return number of characters in String s 
    int f(String s);  // f("Hello") returns 5
}

interface I12 extends I1, I2 {}

public class C {
  public static int f1(I1 i, String s) { return i.f(s); }  // f1( i, "Hi") == 72
  public static int f2(I2 i, String s) { return i.f(s); }  // f2( i, "Hi") == 2
  public static int f12(I12 i, String s) { return i.f(s);} // f12(i, "Hi") == ???
}

According to the Java language specification, a class implementing I12 must do so in such a way that C.f1() , C.f2() , and C.f12() return the exact same result when called with the same arguments. 根据Java语言规范,实现I12的类必须以这样的方式这样做: C.f1()C.f2()C.f12()在使用相同的参数调用时返回完全相同的结果。 If C.f12(i, "Hello") sometimes returned 72 and sometimes returned 5 based on how C.f12() were called, that would be a serious bug in the program and a violation of the language specification. 如果C.f12(i, "Hello")有时返回72并且有时根据C.f12()的调用方式返回5,那将是程序中的一个严重错误并且违反了语言规范。

Furthermore, if the author of class C expected some kind of consistent behavior out of f12() , there is no bytecode or other information in class C that indicates whether it should be the behavior of I1.f(s) or I2.f(s) . 此外,如果C类的作者期望f12()中存在某种一致的行为,则C类中没有字节码或其他信息表明它是否应该是I1.f(s)I2.f(s)的行为I2.f(s) If the author of C.f12() had in mind Cf("Hello") should return 5 or 72, there's no way to tell from looking at the code. 如果C.f12()的作者想到Cf("Hello")应该返回5或72,那么就无法通过查看代码来判断。

Fine, so I cannot in general provide different behaviors for homographic functions using bytecode tricks, but I really have a class like my example class TheaterManager . 很好,所以我通常不能使用字节码技巧为同形函数提供不同的行为,但我真的有一个像我的示例类TheaterManager类。 What should I do to implement ArtistSet.complement() ? 我该怎么做才能实现ArtistSet.complement()

The actual answer to the actual question you asked is to create your own substitute implementation of TheaterManager that does not require an ArtistSet . 您提出的实际问题实际答案是创建自己的TheaterManager替代实现,不需要ArtistSet You do not need to change the library's implementation, you need to write your own. 您不需要更改库的实现,您需要自己编写。

The actual answer to the other example question you cite is basically "delegate I12.f() to I2.f() " because no function that receives an I12 object goes on to pass that object to a function expecting an I1 object. 您引用的另一个示例问题实际答案基本上是“将I12.f()委托给I2.f() ”,因为没有接收I12对象的函数继续将该对象传递给期望I1对象的函数。

Stack Overflow is only for questions and answers of general interest Stack Overflow仅用于普遍感兴趣的问题和答案

One of the stated reasons to reject a question here is that "it is only relevant to an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet." 这里拒绝一个问题的原因之一是“它只与一个非常狭窄的情况有关,而这种情况通常不适用于全球互联网用户。” Because we want to be helpful, the preferred way to handle such narrow questions is to revise the question to be more broadly applicable. 因为我们希望提供帮助,处理这些狭隘问题的首选方法是修改问题,以便更广泛地适用。 For this question I have taken the approach of answering the broadly applicable version of the question rather than actually editing the question to remove what makes it unique to your situation. 对于这个问题,我采取的方法是回答广泛适用的问题版本,而不是实际编辑问题,以删除使其独特的问题。

In the real world of commercial programming any Java library that has a broken interface like I12 would not accumulate even dozens of commercial clients unless it could be used by implementing I12.f() in one of these ways: 在商业编程的现实世界中,任何像I12这样具有破坏接口的Java库都不会累积甚至数十个商业客户端,除非可以通过以下方式之一实现I12.f()来使用它们:

  • delegate to I1.f() 委托给I1.f()
  • delegate to I2.f() 委托给I2.f()
  • do nothing 没做什么
  • throw an exception 抛出一个例外
  • pick one of the above strategies on a per-call basis based on the values of some members of the I12 object 根据I12对象的某些成员的值,在每次调用的基础上选择上述策略之一

If thousands or even only a handful of companies are using this part of this library in Java then you can be assured they have used one of those solutions. 如果成千上万甚至只有少数几家公司在Java中使用这个库的这一部分,那么可以放心,他们已经使用了其中一种解决方案。 If the library is not in use by even a handful of companies then the question is too narrow for Stack Overflow. 如果即使是少数公司也没有使用该库,那么Stack Overflow的问题就太窄了。

OK, TheaterManager was an oversimplification. 好吧, TheaterManager过于简单了。 In the real case it is too hard for me to replace that class and I don't like any of the practical solutions you've outlined. 在实际情况下,我很难替换那个类,我不喜欢你概述的任何实际解决方案。 Can't I just fix this with fancy JVM tricks? 我不能用花哨的JVM技巧解决这个问题吗?

It depends on what you want to fix. 这取决于你想要修复的内容。 If you want to fix your specific library by mapping all the calls to I12.f() and then parsing the the stack to determine the caller and choosing a behavior based on that. 如果要通过将所有调用映射到I12.f()然后解析堆栈以确定调用者并基于此选择行为来修复特定库。 You can access the stack via Thread.currentThread().getStackTrace() . 您可以通过Thread.currentThread().getStackTrace()访问堆栈。

If you run across a caller you do not recognize you may have a hard time figuring out which version they want. 如果您遇到呼叫者,则无法识别您可能很难确定他们想要的版本。 For example you may be called from a generic (as was the actual case in the other specific example you gave), like: 例如,您可以从泛型调用(就像您给出的其他特定示例中的实际情况一样),例如:

public class TalentAgent<T extends Artist> {
  public static void butterUp(List<T> people) {
    for (T a: people) {
      a.complement()
    }
  }
}

In Java, generics are implemented as erasures , meaning all type information is thrown away at compile time. 在Java中, 泛型被实现为擦除 ,这意味着在编译时抛弃所有类型信息。 There is no class or method signature difference between a TalentAgent<Artist> and a TalentAgent<Set> and the formal type of the people parameter is just List . TalentAgent<Artist>TalentAgent<Set>之间没有类或方法签名差异, people参数的正式类型只是List There is nothing in the class interface or method signature of the caller to tell you what to do by looking at the stack. 调用者的类接口或方法签名中没有任何内容可以通过查看堆栈来告诉您该做什么。

So you would need to implement multiple strategies, one of which would be decompiling the code of the calling method looking for clues that the caller is expecting one class or another. 所以你需要实现多个策略,其中一个策略是反编译调用方法的代码,寻找调用者期望一个或另一个类的线索。 It would have to be very sophisticated to cover all the ways this could happen, because among other things you have no way of knowing in advance what class it actually expecting, only that it is expecting a class that implements one of the interfaces. 它必须非常复杂,以涵盖所有可能发生的方式,因为除了其他事项之外,你无法事先知道它实际期望的类,只是它期望一个实现其中一个接口的类。

There are mature and extremely sophisticated open source bytecode utilities, including one that automatically generates a proxy for a given class at runtime (written long before there was support for that in the Java language), so the fact that there isn't an open source utility for handling this case speaks volumes about the ratio of effort to usefulness in pursuing this approach. 有成熟且极其复杂的开源字节码实用程序,包括在运行时自动为给定类生成代理的实用程序(在Java语言支持之前很久就已编写),因此没有开源的事实处理这种情况的实用程序说明了在采用这种方法时努力与有用的比率。

Okay, after much research, I have another idea to fully accommodate the situation. 好的,经过大量的研究,我有另一个想法来完全适应这种情况。 Since you can't directly modify their code... you can force the modifications yourself. 由于您无法直接修改其代码......您可以自行强制进行修改。

DISCLAIMER: The example code below is very simplified. 免责声明:以下示例代码非常简化。 My intention is to show the general method of how this might be done, not to produce functioning source code to do it (since that's a project in itself). 我的目的是展示如何做到这一点的一般方法,而不是生成有效的源代码(因为这本身就是一个项目)。

The issue is that the methods are homographic. 问题是这些方法是单应性的。 So to solve it, we can just rename the methods. 所以要解决它,我们可以重命名方法。 Simple, right? 简单吧? We can use the Instrument package to achieve this. 我们可以使用Instrument包来实现这一目标。 As you'll see in the linked documentation, it allows you to make an "agent" which can directly modify classes as they're loaded or re-modify them even if they've already been loaded. 正如您在链接文档中看到的那样,它允许您创建一个“代理”,可以在加载类时直接修改类,或者即使它们已经加载也可以重新修改它们。

Essentially, this requires you to make two classes: 从本质上讲,这需要您创建两个类:

  • An agent class which preprocesses and reloads classes; 预处理和重新加载类的代理类; and, 和,
  • A ClassFileTransformer implementation which specifies the changes you want to make. 一个ClassFileTransformer实现,它指定您要进行的更改。

The agent class must have either a premain() or agentmain() method defined, based on whether you want it to begin its processing as the JVM starts up or after it is already running. 代理类必须定义premain()agentmain()方法,具体取决于您是希望它在JVM启动时还是在它已经运行之后开始处理。 Examples of this are in the package documentation above. 这方面的例子在上面的包文档中。 These methods give you access to an Instrumenation instance, which will allow you to register your ClassFileTransformer . 这些方法允许您访问Instrumenation实例,这将允许您注册ClassFileTransformer So it might look something like this: 所以它可能看起来像这样:

InterfaceFixAgent.java InterfaceFixAgent.java

public class InterfaceFixAgent {

    public static void premain(String agentArgs, Instrumentation inst) {

        //Register an ArtistTransformer
        inst.addTransformer(new ArtistTransformer());

        //In case the Artist interface or its subclasses 
        //have already been loaded by the JVM
        try {
            for(Class<?> clazz : inst.getAllLoadedClasses()) {
                if(Artist.class.isAssignableFrom(clazz)) {
                    inst.retransformClasses(clazz);
                }
            }
        }
        catch(UnmodifiableClassException e) {
            //TODO logging
            e.printStackTrace();
        }
    }
}

ArtistTransformer.java ArtistTransformer.java

public class ArtistTransformer implements ClassFileTransformer {

    private static final byte[] BYTES_TO_REPLACE = "complement".getBytes();
    private static final byte[] BYTES_TO_INSERT = "compliment".getBytes();

    @Override
    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        if(Artist.class.isAssignableFrom(classBeingRedefined)) {
            //Loop through the classfileBuffer, find sequences of bytes
            //which match BYTES_TO_REPLACE, replace with BYTES_TO_INSERT
        }
        else return classfileBuffer;
    }

This is, of course, simplified. 当然,这是简化的。 It will replace the word "complement" with "compliment" in any class which extends or implements Artist , so you will very likely need to further conditionalize it (for example, if Artist.class.isAssignableFrom(classBeingRedefined) && Set.class.isAssignableFrom(classBeingRedefined) , you obviously don't want to replace every instance of "complement" with "compliment", as the "complement" for Set is perfectly legitimate). 它将在extendsimplements Artist任何类中用“compliment”替换“补充”一词,因此您很可能需要进一步对其进行条件化(例如,如果Artist.class.isAssignableFrom(classBeingRedefined) && Set.class.isAssignableFrom(classBeingRedefined) ,你显然不希望用“compliment”替换“补充”的每个实例,因为Set的“补充”是完全合法的)。

So, now we've corrected the Artist interface and its implementations. 所以,现在我们已经纠正了Artist接口及其实现。 The typo is gone, the methods have two different names, so there is no homography. 错字消失了,方法有两个不同的名字,所以没有单应性。 This allows us to have two different implementations in our CommunityTheatre class now, each of which will properly implement/override the methods from the ArtistSet . 这允许我们现在在CommunityTheatre类中有两个不同的实现,每个实现都将正确实现/覆盖ArtistSet的方法。

Unfortunately, we've now created another (possibly even bigger) issue. 不幸的是,我们现在已经创建了另一个(可能更大)的问题。 We've just broken all the previously-legitimate references to complement() from classes implementing Artist . 我们刚刚从实现Artist类中删除了所有以前合法的对complement()引用。 To fix this, we will need to create another ClassFileTransformer which replaces these calls with our new method name. 要解决这个问题,我们需要创建另一个ClassFileTransformer ,用我们新的方法名替换这些调用。

This is somewhat more difficult, but not impossible. 这有点困难,但并非不可能。 Essentially, the new ClassFileTransformer (let's say we call it the OldComplementTransformer ) will have to perform the following steps: 基本上,新的ClassFileTransformer (假设我们称之为OldComplementTransformer )必须执行以下步骤:

  1. Find the same string of bytes as before (the one representing the old method name, "complement"); 找到与以前相同的字节串(表示旧方法名称的字符串,“补码”);
  2. Get the bytes before this which represent the object reference calling the method; 获取之前的字节,表示调用方法的对象引用;
  3. Convert those bytes into an Object ; 将这些字节转换为Object ;
  4. Check to see if that Object is an Artist ; 检查该Object是否是Artist ; and, 和,
  5. If so, replace those bytes with the new method name. 如果是,请使用新方法名称替换这些字节。

Once you've made this second transformer, you can modify the InterfaceFixAgent to accommodate it. 完成第二个变换器后,可以修改InterfaceFixAgent以适应它。 (I also simplified the retransformClasses() call, since in the example above we perform the needed check within the transformer itself.) (我还简化了retransformClasses()调用,因为在上面的例子中,我们在变换器本身内执行所需的检查。)

InterfaceFixAgent.java ( modified ) InterfaceFixAgent.java (已修改

public class InterfaceFixAgent {

    public static void premain(String agentArgs, Instrumentation inst) {

        //Register our transformers
        inst.addTransformer(new ArtistTransformer());
        inst.addTransformer(new OldComplementTransformer());

        //Retransform the classes that have already been loaded
        try {
            inst.retransformClasses(inst.getAllLoadedClasses());
        }
        catch(UnmodifiableClassException e) {
            //TODO logging
            e.printStackTrace();
        }
    }
}

And now... our program is good to go. 现在......我们的计划很好。 It certainly wouldn't be easy to code, and it will be utter hell to QA and test. 编码肯定不容易,而且QA和测试将是彻头彻尾的地狱。 But it's certainly robust, and it solves the issue. 但它确实很强大,它解决了这个问题。 (Technically, I suppose it avoids the issue by removing it, but... I'll take what I can get.) (从技术上讲,我认为通过删除它可以避免这个问题,但是......我将采取我能得到的东西。)

Other ways we might have solved the problem: 我们可能解决问题的其他方法:

Both of these would allow you to directly manipulate bytes in memory. 这两个都允许您直接操作内存中的字节。 A solution could certainly be designed around these, but I believe it would be much more difficult and much less safe. 当然可以围绕这些解决方案来设计解决方案,但我相信它会更加困难并且更不安全。 So I went with the route above. 所以我选择了上面的路线。

I think this solution could even be made more generic into an incredibly useful library for integrating code bases. 我认为这个解决方案甚至可以更加通用,成为一个非常有用的库,用于集成代码库。 Specify which interface and which method you need refactored in a variable, a command line argument, or a configuration file, and let her loose. 指定在变量,命令行参数或配置文件中需要重构的接口和方法,让她松散。 The library that reconciles conflicting interfaces in Java at runtime. 在运行时协调Java中冲突接口的库。 (Of course, I think it would still be better for everyone if they just fixed the bug in Java 8.) (当然,我认为如果他们只修复Java 8中的错误,对每个人来说仍然会更好。)

Here's what I'd do to remove the ambiguity: 这是我要做的消除歧义的方法:

interface Artist {
    void complement(); // [SIC] from OP, really "compliment"
    int getEgo();
}

interface Set {
    void complement(); // as in Set Theory
    void remove();
    boolean empty(); // [SIC] from OP, I prefer: isEmpty()
}

/**
 * This class is to represent a Set of Artists (as a group) -OR-
 * act like a single Artist (with some aggregate behavior).  I
 * choose to implement NEITHER interface so that a caller is
 * forced to designate, for any given operation, which type's
 * behavior is desired.
 */
class GroupOfArtists { // does NOT implement either

    private final Set setBehavior = new Set() {
        @Override public void remove() { /*...*/ }
        @Override public boolean empty() { return true; /* TODO */ }            
        @Override public void complement() {
            // implement Set-specific behavior
        }
    };

    private final Artist artistBehavior = new Artist() {
        @Override public int getEgo() { return Integer.MAX_VALUE; /* TODO */ }            
        @Override public void complement() {
            // implement Artist-specific behavior
        }
    };

    Set asSet() {
        return setBehavior;
    }

    Artist asArtist() {
        return artistBehavior;
    }
}

If I were passing this object to the HR department, I'd actually give it the value returned from asSet() to hire/fire the entire group. 如果我将此对象传递给人力资源部门,我实际上会给它从asSet()返回的值来雇用/解雇整个组。

If I were passing this object to the Theater for a performance, I'd actually give it the value returned from asArtist() to be treated as talent. 如果我将这个对象传递给剧院进行表演,我实际上会给它从asArtist()返回的值作为天赋。

This works as long as YOU are in control of talking to the different components directly... 只要您控制直接与不同组件交谈,这就有效...

But I realize that your problem is a single third-party vendor has created a component, TheaterManager , that expects one object for both of these functions and it won't know about the asSet and asArtist methods. 但我意识到你的问题是单个第三方供应商创建了一个组件, TheaterManager ,它需要这两个函数的一个对象,它不会知道asSetasArtist方法。 The problem is not with the vendors that created Set and Artist , it is the vendor that combined them instead of using a Visitor pattern or just specifying an interface that would mirror the asSet and asArtist methods I made above. 问题不在于创建SetArtist的供应商,而是供应商将它们组合在一起而不是使用访问者模式,或仅指定一个将镜像我上面提到的asSetasArtist方法的接口。 If you can convince your one vendor "C" to fix that interface, your world will be a lot happier. 如果你可以说服你的一个供应商“C”来修复这个界面,你的世界将会更加快乐。

Good luck! 祝好运!

Dog, I have a strong feeling you are leaving out some details that are crucial to the solution. 狗,我有一种强烈的感觉,你遗漏了一些对解决方案至关重要的细节。 This often happens on SO because 这通常发生在SO上,因为

  • people need to leave out a lot of details to get the question to a reasonable size and scope, 人们需要遗漏很多细节才能将问题调到合理的范围和范围,
  • people do not fully understand the problem and the solution (which is why they are asking for help) so they cannot be sure which details are important and which are not, and 人们不完全理解问题和解决方案(这就是他们寻求帮助的原因)所以他们无法确定哪些细节是重要的,哪些不是,以及
  • the reason the person cannot solve the problem on their own is because they do not understand the importance of this detail, which is the same reason they left it out. 这个人不能自己解决问题的原因是因为他们不了解这个细节的重要性,这就是他们遗漏的原因。

I've said in another answer what I would do about ArtistSet. 我在另一个回答中说过我会对ArtistSet做些什么。 But keeping the above in mind I will give you another solution to a slightly different problem. 但是记住上面的内容我会给你一个稍微不同的问题的另一个解决方案。 Lets say I had code from a bad vendor: 可以说我有来自坏供应商的代码:

package com.bad;

public interface IAlpha {
    public String getName();
    // Sort Alphabetically by Name
    public int compareTo(IAlpha other);
}

This is bad because you should declare a function returning a Comparator<IAlpha> to implement the sorting strategy, but whatever. 这很糟糕,因为你应该声明一个返回Comparator<IAlpha>的函数来实现排序策略,但无论如何。 Now I get code from a worse company: 现在我从更糟糕的公司获得代码:

package com.worse;
import com.bad.IAlpha;

// an Alpha ordered by name length
public interface ISybil extends IAlpha, Comparable<IAlpha> {}

This is worse, because it is totally wrong, in that it overrides behavior incompatibly. 这更糟糕,因为它完全错误,因为它不相容地覆盖行为。 An ISybil orders itself by name length, but an IAlpha orders itself alphabetically, except an ISybil is an IAlpha . ISybil按名称长度命令自己,但是IAlpha按字母顺序排序,除了ISybil IAlpha They were mislead by the anti-pattern of IAlpha when they could and should have done something like: 当他们可以而且应该做的事情时,他们被IAlpha的反模式误导了:

public interface ISybil extends IAlpha {
  public Comparator<IAlpha> getLengthComparator();
}

However , this situation is still much better than ArtistSet because here the expected behavior is documented. 但是 ,这种情况仍然比ArtistSet好得多,因为这里记录了预期的行为。 There is no confusion about what ISybil.compareTo() should do. 关于ISybil.compareTo()应该做什么没有混淆。 So I would create classes as follows. 所以我会按如下方式创建类。 A Sybil class that implements compareTo() as com.worse expects and delegates everything else: 实现compareTo()为com.worse的Sybil类需要并委托其他所有内容:

package com.hack;

import com.bad.IAlpha;
import com.worse.ISybil;

public class Sybil implements ISybil {

    private final Alpha delegate;

    public Sybil(Alpha delegate) { this.delegate = delegate; }
    public Alpha getAlpha() {   return delegate; }
    public String getName() { return delegate.getName(); }
    public int compareTo(IAlpha other) {
        return delegate.getName().length() - other.getName().length();
    }

}

and an Alpha class that works exactly like com.bad said it should: 和一个像com.bad一样工作的Alpha类应该说:

package com.hack;
import com.bad.IAlpha;

public class Alpha implements IAlpha {
    private String name;
    private final Sybil sybil;
    public Alpha(String name) { 
        this.name = name;
        this.sybil = new Sybil(this);
    }

    // Sort Alphabetically
    public int compareTo(IAlpha other) {
        return name.compareTo(other.getName());
    }

    public String getName() { return name; }
    public Sybil getSybil() { return sybil; }
}

Note that I included type conversion methods: Alpha.getSybil() and Sybil.getAlpha(). 请注意,我包含了类型转换方法:Alpha.getSybil()和Sybil.getAlpha()。 This is so I could create my own wrappers around any com.worse vendor's methods that take or return Sybils so I can avoid polluting my code or any other vendor's code with com.worse's breakage. 这样我就可以围绕任何com.worse供应商的方法创建自己的包装器,这些方法可以使用或返回Sybils,因此我可以避免使用com.worse的破坏来污染我的代码或任何其他供应商的代码。 So if com.worse had: 所以如果com.worse有:

public ISybil breakage(ISybil broken);

I could write a function 我可以写一个函数

public Alpha safeDelegateBreakage(Alpha alpha) {
  return breakage(alpha.getSybil).getAlpha();
}

and be done with it, except I would still complain vociferously to com.worse and politely to com.bad. 并且完成它,除了我仍然会大声抱怨com.wad和礼貌地com.bad。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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