8

The question is:

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

The requirement is to calculate how many times the eat method is called without modifying the interface and the class itself.

One way is to extend the lion class and get the results but for every object extending the class we will have to create such classes.

Is there any optimized way to do this.

Publish Subscribe is one way but we don't have the permissions to modify the interface or the Lion class itself.

5
  • 4
    I assume "OOPS" is a typo. – bolov Aug 17 '20 at 17:07
  • Just to clarify: Is your problem that you want to extend the behavior of all Animal classes, but cannot modify the code of Animal or its existing subclasses? – cmaster - reinstate monica Aug 18 '20 at 7:21
  • 2
    If the title was useful, I would have upvoted this question, when I saw the title in the question list I assumed this question needed a downvote until I read the question. Sorry I can't think of a good title. – Ian Aug 18 '20 at 11:47
  • Sorry that the title is disappointing a lot here , i am changing it to a more suitable one here, adding tags for the terms instead. OOPS was for Object Oriented Programming System. Correct me if that is incorrect – beginner_coder Aug 18 '20 at 12:31
  • "One way is to extend the lion class and get the results but for every object extending the class we will have to create such classes" Huh? You would just replace all instantiations of Lion with instantiations of you Lion descendant. How is that a lot of work? – Martin Maat Aug 21 '20 at 21:22
25

You could use the Decorator Pattern to add additional responsibilities to an Animal without subclassing.


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

}

UPDATE

If we want to be more faithful to the Decorator Pattern we can not use the getEatCount() getter at all, and instead inject a Counter object in the 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());


    }

}

Same solution with no pattern-disrupting getter

22
  • 5
    Yes, but: 1. Don't call classes "Decorator". Decorator is a technical term, classes are named using the domain. It's an Animal, not a Decorator. 2. Don't have getters. To be composable (and maintainable) your "decorator" should conform to the interface exactly. Otherwise you'll won't be able to stack these things. – Robert Bräutigam Aug 17 '20 at 10:38
  • 4
    If you use composition over inheritance, you'll often have more then one "decorator". To use more then one, i.e. to plug multiple layers of them together, you can't add methods, because those will not be visible on the top. – Robert Bräutigam Aug 17 '20 at 11:28
  • 5
    @RobertBräutigam How else would you expect to access the count if not by a getter? Would you expect to pass a Counter into the new EatCountingAnimal(theLion, myCounter) from which you could later get the count? – Bergi Aug 17 '20 at 17:04
  • 2
    @RobertBräutigam Ah, I see what you mean from your answer, but that just logs a count three times, it cannot really produce (once) a count of how often something happened to the lion because it wouldn't know when the "test sequence" ends. – Bergi Aug 17 '20 at 18:24
  • 5
    @RobertBräutigam: I like your advice, but I prefer YAGNI. I'll start with a getter to extract the count, and refactor later if I need something more sophisticated. – Matthieu M. Aug 18 '20 at 12:34
22

Perfect time for composition. Create a new implementation of Animal that does the counting, but also delegates the "real" function. Like this:

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

You don't have to modify any of the existing classes, and you can plug this together with any implementation of Animal you want. Use this way:

Animal lion = new LoggingAnimal(new Lion());
lion.eat();
2
  • 1
    Totally looks like a Decorator/Wrapper for me. While it technically could be called a Composition, I'm somewhat against using this term when only a single object is 'composed' into a new thing. – quetzalcoatl Aug 18 '20 at 13:52
  • 2
    Better to call eatCount++; after then delegate.eat();. – Engineert Aug 18 '20 at 14:33
1

Create a new class with the new behavior. Then update main in the Test class.

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

or just read the Test file and count the number of times you called eat(). I'm guessing the answer is gonna be three.

5
  • 1
    How is this being upvoted, this goes totally against the actual question at hand. – Tristan Maxson Aug 18 '20 at 3:43
  • @TristanMaxson really? Nothing in the requirements given asked us to maintain the current lion behavior. We're free to make an animal that does whatever we like when it's told to eat so long as it counts meals and uses the same interface. In fact every answer here followed this answers method of overriding the behavior. Which is fine. Since it's a test I stuck with a simple mock. Others wanted to show off the decorator pattern. Which also works. – candied_orange Aug 18 '20 at 7:30
  • 1
    @TristanMaxson the question asks to maintain the interface and the class. Namely, I read that as the Animal interface and the Lion class. This answer changes neither, so I don't see how it's failing the requirement. – VLAZ Aug 18 '20 at 7:59
  • 1
    Anyway, decorator or mock, what ends up getting tested isn't Lion. These answers test the lion.eat() calling code. In it's current form, not really worth testing. – candied_orange Aug 18 '20 at 8:13
  • 1
    @VLAZ no it doesn't anymore, since candied_orange did a sneaky edit ;) – TheHowlingHoaschd Aug 18 '20 at 8:15
0

Kind of a different approach, that will allow you to avoid having 50 classes for tasks only done a few times. It's a more generic one, very useful in technical aspects, not as much in business ones, can still be very useful tho.

btw Builder is just to illustrate, of course you don't have to use it like that, preEat/postEat are important here

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());
    }
}
5
  • Impressive. Aspect oriented programming done manually. – candied_orange Aug 18 '20 at 16:40
  • Just missing the builder method that sets downstream. – candied_orange Aug 18 '20 at 16:43
  • Not sure if you are being sarcastic :D I wouldn't dare using Aspects on POJO/model objects etc. I don't like Aspects in general, I use them only for pure background tasks like logging in library code, but when I need an output (the 'eat count') - no no no. – Shadov Aug 18 '20 at 19:46
  • As to the builder - .builder(new Lion()) sets the downstream, done this way because it is required here, so to even get the builder you need to pass that object (as opposed to pre/post eat). Didn't write the whole builder, not the clue of this answer. – Shadov Aug 18 '20 at 19:48
  • no sarcasm at all. As specified this is a logging problem. I like aspect solutions that don’t force what they’re advising to know about them. Here Lion doesn’t know a thing. But without a wide spread need for that logging, rather than an aspect library, I’d prefer to see a focused solution like yours. – candied_orange Aug 18 '20 at 19:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.