18

What is the best way of manipulating the order things are done based on some conditions (other than writing them again with the different order)?

Let's say there is a Person class and each object of Person represents a different human.

class Person{
    int eatingPriority = 3;
    int sleepingPriority = 2;
    int recreationPriority = 1;

    void eat() {/*eats*/}
    void sleep() {/*sleeps*/}
    void watchTv() {/*watches tv*/}

    void satisfyNeeds() {
        //HOW TO DO THIS
    }
}

How can I make the satisfyNeeds() methods call the other three methods based on their priority?

Note: I want to make it clear that priorities can change from Person to Person.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
WVrock
  • 1,725
  • 3
  • 22
  • 30
  • 2
    The easiest is to create an abstract class with 2 methods : `getPriority()` and `run()`. Then create an insance for each activity. Finally, you can put instances of those activities in a list and sort them by priority. – Arnaud Denoyelle Mar 20 '15 at 12:50
  • 2
    @ArnaudDenoyelle or use a `PriorityQueue` – SpaceTrucker Mar 20 '15 at 13:44
  • I've been asked the purpose of the program. We are trying to find the cleanest way of manipulating the flow of the program. In this example let's assume that we are creating an AI for a game that has simulated people in it. If you are both hungry and tired would you sleep or eat first? If your sleeping priority is higher you sleep first, otherwise you eat before sleeping. Also it is possible to change the priorities on the fly. We can measure the sleep and food needs and set a priority based on this. Note that the code in the question is a simplified version to demonstrate the problem – WVrock Mar 21 '15 at 16:14

6 Answers6

13

You can do this with 1 class and 1 interface.

public class Person {
    int eatingPriority = 3;
    int sleepingPriority = 2;
    int recreationPriority = 1;

    PriorityQueue<Action> actions;

    void eat() { }

    void sleep() { }

    void watchTv() { }

    public Person() {
        actions = new PriorityQueue<Action>(new Comparator<Action>() {
            @Override
            public int compare(Action o1, Action o2) {
                return o2.getPriority() - o1.getPriority();
            }
        });

        actions.add(new Action() {
            @Override
            public int getPriority() {
                return eatingPriority;
            }
            @Override
            public void execute() {
                eat();
            }
        });

        actions.add(new Action() {
            @Override
            public int getPriority() {
                return sleepingPriority;
            }
            @Override
            public void execute() {
                sleep();
            }
        });

        actions.add(new Action() {
            @Override
            public int getPriority() {
                return recreationPriority;
            }
            @Override
            public void execute() {
                watchTv();
            }
        });
    }

    public void satisfyNeeds() {
        for (Action action : actions) {
            action.execute();
        }
    }

    interface Action {
        public int getPriority();
        public void execute();
    }
}
Maksim
  • 264
  • 7
  • 20
  • You can also keep your class structure and call your methods eat(), sleep() and watchTv() from appropriate execute() methods. – Maksim Mar 20 '15 at 13:14
  • 2
    Thanks! Nice idea, it's reasonable to use PriorityQueue here instead of ArrayList. – Maksim Mar 20 '15 at 13:29
  • Yes, sorry I removed my comment above to write a more explaining one. The advantage of PriorityQueue over ArrayList in this case would be that the sorting process is made at each adding of element, and especially "persisted". By using ArrayList, anytime you call the `satisfyNeeds`, you have to sort the list, could be really consuming in some use cases. – Mik378 Mar 20 '15 at 13:30
  • Yes, great like that ;) – Mik378 Mar 20 '15 at 13:38
  • I even would subclass the Action class to have some meaningful terms like `SleepAction`, and keep Open/closed Principle ;) – Mik378 Mar 20 '15 at 13:40
  • I was thinking about that :) But it depends. If we had 100 different Actions, we would make more classes and it might have made the whole structure complicated. – Maksim Mar 20 '15 at 13:47
  • Adding more classes when it makes sense (and that is the case here) is really better than putting various and changing responsibilities to the same class. I enjoy thinking about unit testing. When your class doesn't change, you don't have to worry about break unit tests/ functionalities about all the use cases. Indeed, you just ADD code, leaving the main code intact. Even don't have to replay them. Moreover, your class Person would be reusable => the whole point of OOP. The rule is: "extract all what is dynamic in your class". What is dynamic here is the nature and number of person's actions. – Mik378 Mar 20 '15 at 14:25
  • @Mik378 Java 8 would remove a lot of it. – OrangeDog Mar 21 '15 at 08:16
11

Here is another possible implementation :

abstract class Need {
  abstract void satisfy();
}

class Eat extends Need {
  @Override
  public void satisfy() { /* eat ...*/}
}

class Sleep extends Need {
  @Override
  public void satisfy() { /* sleep ...*/}
}

class DrinkBeer extends Need {
  @Override
  public void satisfy() { /* drink beer ...*/}
}

class Person{
  // TreeMap will sort the map in the key's natural order (a int here)
  private Map<Integer, Need> needs = new TreeMap<>();    

 Person() {
   add(new Eat(), 3);
   add(new Sleep(), 2);
   add(new DrinkBeer(), 1);
 }

 void add(Need need, int priority) {
   needs.put(Integer.valueOf(priority), need);
 }

 void satisfyNeeds() {
    for(Need need : needs.values())
      need.satisfy();
  }
} 
baraber
  • 3,296
  • 27
  • 46
  • `needs.put(Integer.valueOf(priority), need)` can be written as simply `needs.put(priority, need)`. – VGR Mar 20 '15 at 20:28
10

This solution would require Java 8:

class Person {

    void eat() {};
    void sleep() {};
    void watchTv() {};

    // Being in a List you can easily reorder the needs when you want to
    List<Runnable> needs = Arrays.asList(this::eat, this::sleep);

    // Alternatively, you can use a Map<Runnable, Integer> where the value is your
    // priority and sort it (see http://stackoverflow.com/q/109383/1296402)

    void satisfyNeeds() {
        needs.forEach(Runnable::run);
    }
}
steffen
  • 16,138
  • 4
  • 42
  • 81
  • Can you elaborate? Am I supposed to write `needs = Arrays.asList(() -> eat(), () -> sleep());` with the desired order whenever I want to reorder it? – WVrock Mar 20 '15 at 15:34
  • @WVrock You can initially define the order and later reorder it whenever it changes. Or use a `Map` if you have static priority values as integers (see the link). Maybe you have "presets", like `final List needsDuringDayTime = ....; final List needsDuringNightTime = ...` and then you set them in `satisfyNeeds()`: `List needs = isDayTime() ? needsDuringDayTime : needsDuringNightTime`. – steffen Mar 20 '15 at 16:06
  • @WVrock Or imagine you have counters or timestamps for last meals, sleep times or TV sessions and so the needs change dynamically. You'd sort the `List` with a custom `Comparator` like `Collections.sort(needs, customComparator);` – steffen Mar 20 '15 at 16:14
  • Is `() -> eat()` different from `new Runnable() { public void run() { eat() } }` or just nicer to type? – Random832 Mar 20 '15 at 19:11
  • @Random832 It's the same. I updated my answer to make it yet a little better to read. – steffen Mar 20 '15 at 19:45
7

You can use this code

import java.util.Arrays;  // must be imported
int[] priorities = {sleepPriority, eatPriority, recreationPriority};
Arrays.sort(priorities);
for (int i=priorities.length-1; 0<=i; i--) {
    int priority = priorities[i];
    if (priority == sleepingPriority) { sleep(); }
    if (priority == eatingPriority) { eat(); }
    if (priority == recreationPriority) { watchTv(); }
}

Basically, it puts the priorities in an array, sorts the array and runs a for loop on it to run the functions.

hichris123
  • 10,145
  • 15
  • 56
  • 70
GenuinePlaceholder
  • 685
  • 1
  • 10
  • 25
5

Finding the right order of three elements can be done simply like this:

 void satisfyNeeds() {
     boolean eatFirst = eatingPriority>Math.max(sleepingPriority,recreationPriority);
     if(eatFirst) eat();
     if(sleepingPriority>recreationPriority) {
         sleep();
         watchTv();
     }
     else {
         watchTv();
         sleep();
     }
     if(!eatFirst) eat();
  }

Of course, it won’t scale if you raise the number of actions. For a higher number you might look at one of the other answers.

Holger
  • 285,553
  • 42
  • 434
  • 765
4

You should introduce a map property into Person class, where prioritize methods, for example:

class Person {

...
private Map<Integer, Method> methodsPriority = new HashMap<>();
...
 public Person setEatingPriority(int priority) {

  methodsPriority.put(priority, /* put 'eat' method reference here*/);
  return this;
 }

 public Person setSleepingPriority(int priority) {

  methodsPriority.put(priority, /* put 'sleep' method reference here*/);
  return this;
 }

 public Person setWatchingTVPriority(int priority) {

  methodsPriority.put(priority, /* put 'watch TV' method reference here*/);
  return this;
 }

 public void satisfyNeeds() {

  Collection<Integer> keys = methodsPriority.keySet();
  Collections.sort(keys);
  for(Integer key: keys)
   methodsPriority.get(key).invoke(this);
 }



...
}

And it can be used in next manner:

Person Anna = new Person()
.setEatingPriority(1)
.setSleepingPriority(2)
.setWatchingTVPriority(3);

Person Bob = new Person()
.setEatingPriority(3)
.setSleepingPriority(2)
.setWatchingTVPriority(1);

Anna.satisfyNeeds();
Bob.satisfyNeeds();
nndru
  • 2,057
  • 21
  • 16