Diseño de OOP sin considerar modificaciones al diseño existente

Aug 17 2020

La pregunta es:

interface Animal {
    void eat();
}

class Lion implements Animal{    
    public void eat(){
        //do somethng
    }
}


class Test {
    public static void main(String[] args) {
        Animal lion = new Lion();
        lion.eat();
        lion.eat();
        lion.eat();
    }
}

El requisito es calcular cuántas veces se llama al método eat sin modificar la interfaz y la clase en sí.

Una forma es extender la clase lion y obtener los resultados, pero para cada objeto que extienda la clase tendremos que crear tales clases.

¿Hay alguna forma optimizada de hacer esto?

Publicar Suscribirse es una forma, pero no tenemos los permisos para modificar la interfaz o la clase Lion en sí.

Respuestas

25 TulainsCórdova Aug 17 2020 at 15:07

Puede usar el patrón de decorador para agregar responsabilidades adicionales a un animal sin subclases.


public interface Animal {
    void eat();
}

public class Lion implements Animal {
    public void eat() {
        // do something
    }
}

/* In the original Decorator pattern, 
the decorator is an abstract class, 
but for the sake of brevity, 
in this example it's a concrete class. */

public class AnimalWithEatCountDecorator implements Animal {
        private Animal animalWeWantToCountEats;
        private int eatCount=0;

        public AnimalWithEatCountDecorator(Animal animal) {
            this.animalWeWantToCountEats= animal;
        }
        
        public void eat(){ 
            this.animalWeWantToCountEats.eat();
            this.eatCount++;
        }
        
        public int getEatCount() {
            return this.eatCount;
        }   
        
}  

public class Test {

    public static void main(String[] args) {
        AnimalWithEatCountDecorator lion = new AnimalWithEatCountDecorator(new Lion());
        lion.eat();
        lion.eat();
        lion.eat();
        
        System.out.println(lion.getEatCount());
    }

}

ACTUALIZAR

Si queremos ser más fieles al Patrón Decorador, no podemos usar el getEatCount()getter en absoluto, y en su lugar inyectar un objeto Counter en el constructor.

public interface Counter {
    public void increment();
    public int getCount();
}

/* I will omit the trivial implementation of Counter */ 

public class AnimalWithEatCountDecorator implements Animal {
        private Animal animalWeWantToCountEats;
        private Counter counterThingy;
        
        public AnimalWithEatCountDecorator(Animal animal, Counter counterThingy) {
            this.animalWeWantToCountEats= animal;
            this.counterThingy=counterThingy;
        }
        
        public void eat(){ 
            this.animalWeWantToCountEats.eat();
            this.counterThingy.increment();;
        }
        
}

public class Test {

    public static void main(String[] args) {
        Counter counterThingy = new CounterThingy();
        AnimalWithEatCountDecorator lion = 
                new AnimalWithEatCountDecorator(new Lion(), counterThingy);
        lion.eat();
        lion.eat();
        lion.eat();
        
        System.out.println(counterThingy.getCount());


    }

}

22 RobertBräutigam Aug 17 2020 at 14:39

Momento perfecto para la composición. Crea una nueva implementación Animalque hace el conteo, pero también delega la función "real". Me gusta esto:

public final class LoggingAnimal implements Animal {
   private final Animal delegate;
   private int eatCount = 0;

   public LoggingAnimal(Animal delegate) {
      this.delegate = delegate;
   }

   @Override
   public void eat() {
      eatCount++;
      delegate.eat();
      log("Animal ate {} times", eatCount); // Pseudo-functionality
   }
}

No tiene que modificar ninguna de las clases existentes, y puede conectar esto junto con cualquier implementación Animalque desee. Use de esta manera:

Animal lion = new LoggingAnimal(new Lion());
lion.eat();
1 candied_orange Aug 17 2020 at 14:01

Crea una nueva clase con el nuevo comportamiento. Luego actualice mainen la Testclase.

class Test {
    public static void main(String[] args) {
        Animal lion = new AnimalThatKeepsAMealLog();
        lion.eat();
        lion.eat();
        lion.eat();
    }
}

o simplemente lea el archivo de prueba y cuente la cantidad de veces que llamó eat(). Supongo que la respuesta será tres.

Shadov Aug 18 2020 at 23:35

Una especie de enfoque diferente, que te permitirá evitar tener 50 clases para tareas que solo se realizan unas pocas veces. Es más genérico, muy útil en los aspectos técnicos, no tanto en los comerciales, aunque puede ser muy útil.

Por cierto Builder es solo para ilustrar, por supuesto que no tiene que usarlo así, preEat / postEat son importantes aquí

class PointcutAnimal implements Animal {
    private Runnable preEat;
    private Runnable postEat;
    @NonNull
    private Animal downstream;
    
    @Override
    public void eat() {
        if(preEat != null)
            preEat.run();
        
        downstream.eat();
        
        if(postEat != null)
            postEat.run();
    }
}

class Test {
    public static void main(String[] args) {
        AtomicInteger eatCount = new AtomicInteger(0);
    
        Animal lion = PointcutAnimal.builder(new Lion())
            .postEat(eatCount::getAndIncrement)
            .build();
            
        lion.eat();
        lion.eat();
        lion.eat();
        
        System.out.println(eatCount.get());
    }
}