Je voudrais remplacer une méthode dans un objet qui m'a été confié par une usine sur laquelle j'ai peu de contrôle.
Mon problème spécifique est que je veux remplacer les getInputStream et getOutputStream d'un objet Socket pour effectuer log logging.
Le problème générique est le suivant:
public class Foo {
public Bar doBar() {
// Some activity
}
}
Où j'aimerais prendre une Foo
instanciée et remplacer la doBar
par la mienne qui fonctionnerait comme suit:
Bar doBar() {
// My own activity
return original.doBar();
}
Pour le Socket, je vais renvoyer un InputStream et OutputStream qui sont encapsulés par la journalisation pour intercepter les données.
Étant donné que Java utilise une OO basée sur des classes, cela est impossible. Ce que vous pouvez faire est d’utiliser le motif décorateur , c’est-à-dire écrire un wrapper pour l’objet qui renvoie les flux encapsulés.
Je pense qu'il y a un moyen d'obtenir l'effet que vous voulez. Je l’ai vu à l’origine très utilisé en swing avec des boutons pour permettre au programmeur de faire faire quelque chose au bouton quand il est cliqué.
Disons que vous avez votre cours de Foo:
public class Foo {
public Bar doBar() {
// Some activity
}
}
Ensuite, vous avez une classe de coureur ou quelque chose de similaire. Vous pouvez remplacer la méthode doBar () au moment de l'instanciation et cela n'affectera que cet objet spécifique.
cette classe peut ressembler à ceci:
public class FooInstance{
Foo F1 = new Foo(){
public Bar doBar(){
//new activity
}
}
Foo F2 = new Foo();
F1.doBar(); //does the new activity
F2.doBar(); //does the original activity found in the class
}
Je ne suis pas tout à fait sûr que cela fera l'affaire pour vous, mais peut-être que cela vous placera dans la bonne direction. Si rien d'autre ne permet de remplacer une méthode en dehors de la classe, cela vous aidera peut-être.
Vous ne pouvez pas remplacer des méthodes dans des objets existants - vous ne pouvez pas changer le type d'un objet existant, d'une part.
Vous pouvez créer une nouvelle instance d’une autre classe qui déléguée à l’instance existante, mais qui a aussi des limites.
Dans votre cas réel, n'y a-t-il pas moyen de simplement faire un appel séparé pour envelopper les flux renvoyés par le socket? Pouvez-vous donner plus de détails.
Utiliser un décorateur est la bonne voie à suivre:
Un code très similaire à l'exigence que vous avez avec les sockets est ici:
Si Socket était une interface, vous pouvez créer un proxy dynamique. Voici un exemple. Je mets ceci ici au cas où d'autres personnes auraient besoin de le faire et que l'instance cible soit une instance d'une interface.
La raison principale pour laquelle cela ne fonctionnera pas pour Socket est parce que Java.lang.reflect.Proxy.newProxyInstance
nécessite un tableau d'interfaces pour son second argument, ainsi les classes ne fonctionneront pas ici. En tant que tel pour cet exemple, je devais créer une interface appelée ParentInterface
, qui ne contenait que les trois méthodes d'impression.
public class Parent implements ParentInterface {
@Override
public void print1() {
System.out.println("parent 1");
}
@Override
public void print2() {
System.out.println("parent 2");
}
@Override
public void print3() {
System.out.println("parent 3");
}
public static void main(String[] args) {
Parent originalInstance = new Parent();
ParentInterface proxied = (ParentInterface) Java.lang.reflect.Proxy.newProxyInstance(
Parent.class.getClassLoader(),
new Class[]{ParentInterface.class},
new ParentProxy(originalInstance));
proxied.print1();
proxied.print2();
proxied.print3();
}
static class ParentProxy implements InvocationHandler {
final Object realObject;
public ParentProxy(Object real) {
realObject = real;
}
@Override
public Object invoke(Object target, Method m, Object[] args) throws Throwable {
try {
if (m.getName().equals("print2")) {
print2();
return null;
} else {
return m.invoke(realObject, args);
}
} catch (Java.lang.reflect.InvocationTargetException e) {
throw e.getTargetException();
}
}
public void print2() {
System.out.println("wrapper 2");
}
}
}
Je ne sais pas si c'est possible. Avez-vous envisagé de créer votre propre classe, de renvoyer l'objet en tant que membre, puis d'écrire la méthode doBar () pour cette classe
Vous ne pouvez pas vraiment changer un objet à la volée en Java.
Vous pouvez avoir quelque chose qui fait ce que vous voulez en encapsulant votre Foo
dans un autre objet similaire qui déléguera chaque appel à Foo
et enregistrera en même temps tout ce que vous voulez. (voir Proxy
)
Mais si vous voulez vous connecter, l’aspect est peut-être un meilleur choix. (voir AspectJ )
deux options:
Autre solution liée au proxy: vous pouvez utiliser Aspects pour remplacer une méthode sur un objet donné sans la sous-classer vous-même. Ceci est particulièrement approprié et courant pour la journalisation. Cet exemple utilise spring-aop.
class Example {
final Foo foo;
Example(Foo original) {
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(original);
factory.addAspect(FooAspect.class);
foo = (Foo) factory.getProxy();
}
@Aspect
static class FooAspect {
@Before("execution(Foo.doBar())")
Object beforeDoBar() {
// My own activity
}
}