web-dev-qa-db-fra.com

Redéfinition d'une méthode dans un objet Java instancié

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.

28
Tyler Szabo

É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.

19

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.

12
John

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.

6
Jon Skeet

Utiliser un décorateur est la bonne voie à suivre:

Un code très similaire à l'exigence que vous avez avec les sockets est ici:

http://www.javaspecialists.eu/archive/Issue058.html

1
Pablojim

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");
        }

    }

}
0
Jose Martinez

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 

0
sjobe

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 )

0
Colin Hebert

deux options:

  1. facile: si vous utilisez une interface, vous pouvez utiliser un proxy dynamique pour ajouter de nouvelles fonctionnalités. 
  2. plus de travail: ce que vous avez est un conseil "général" de AOP - vous pouvez utiliser n’importe quel outil AOP existant pour rendre cela possible. Spring Framework peut le faire pour vous, si vous l'utilisez déjà.
0
kartheek

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
        }
    }
0
Lucas Ross