9

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
    Commented Aug 17, 2020 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? Commented Aug 18, 2020 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
    Commented Aug 18, 2020 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 Commented Aug 18, 2020 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? Commented Aug 21, 2020 at 21:22

4 Answers 4

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. Commented Aug 17, 2020 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. Commented Aug 17, 2020 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
    Commented Aug 17, 2020 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
    Commented Aug 17, 2020 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. Commented Aug 18, 2020 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. Commented Aug 18, 2020 at 13:52
  • 2
    Better to call eatCount++; after then delegate.eat();.
    – Engineert
    Commented Aug 18, 2020 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. Commented Aug 18, 2020 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. Commented Aug 18, 2020 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
    Commented Aug 18, 2020 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. Commented Aug 18, 2020 at 8:13
  • 1
    @VLAZ no it doesn't anymore, since candied_orange did a sneaky edit ;) Commented Aug 18, 2020 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. Commented Aug 18, 2020 at 16:40
  • Just missing the builder method that sets downstream. Commented Aug 18, 2020 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
    Commented Aug 18, 2020 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
    Commented Aug 18, 2020 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. Commented Aug 18, 2020 at 19:55

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