1

I'm a beginner and the lecturer told me to reduce the duplicate code in these two functions. This is a library. I guess I need one more method for checking through the array. All I need is some advice/principle I need to use. I'll write the method myself. Thanks! getBooks() returns the ArrayList where Books are stored.

public List<Book> getAvailableBooks() {
    List<Book> result = new ArrayList<Book>();
    for (int i = 0; i < this.getBooks().size(); i++) {
        if (this.getBooks().get(i).getPerson() == null) {
            result.add(this.getBooks().get(i));
        }
    }
    return result;
}

public List<Book> getUnavailableBooks() {
    List<Book> result = new ArrayList<Book>();
    for (int i = 0; i < this.getBooks().size(); i++) {
        if (this.getBooks().get(i).getPerson() != null) {
            result.add(this.getBooks().get(i));
        }
    }
    return result;
}
Bob Gilmore
  • 12,608
  • 13
  • 46
  • 53

5 Answers5

2

You already have two methods. You can't reduce by adding one more.

But you can have one method instead of two. e.g.

public List<Book> getBooks(boolean available) {

Now you have one method, and you can tell it whether you want available or unavailable books.

Bhesh Gurung
  • 50,430
  • 22
  • 93
  • 142
  • It is not a good idea to use `boolean` type to denote availability. Who knows what `getBooks(true)` will give you? – Rohit Jain Mar 25 '14 at 17:09
  • @RohitJain: That's a good point and I had thought about it. But didn't put it in there because I thought it might shift the focus away from the main point of the question. – Bhesh Gurung Mar 25 '14 at 17:13
1

Pass in a boolean parameter that should indicate what

(this.getBooks().get(i).getPerson() == null)

Should evaluate to, and add a condition to check that. I.e. should this expression return true or false.

Simon
  • 2,840
  • 2
  • 18
  • 26
1

Here're some advice:

  • Use enhanced for loop instead of indexed one. This will avoid using this.getBooks().get(i) twice in each method.

  • Unavailable books + Available books = Total books. Use this equation to avoid writing all the codes in both the methods. You might want to use a Set<Book> instead of List<Book> to make this easier to work with. [HINT: Set Difference].

  • Also, rather than doing the null check in those methods, I'll add a method isAvailable() inside the Book class only, which will return true if it is available, else false.

Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
1

In the general case, if you see a very common pattern repeated in your code, there is often an opportunity to reduce duplication. The first step is to look at your code and identify the actual differences. You have:

public List<Book> getAvailableBooks() {
    List<Book> result = new ArrayList<Book>();
    for (int i = 0; i < this.getBooks().size(); i++) {
        if (this.getBooks().get(i).getPerson() == null) {
            result.add(this.getBooks().get(i));
        }
    }
    return result;
}

public List<Book> getUnavailableBooks() {
    List<Book> result = new ArrayList<Book>();
    for (int i = 0; i < this.getBooks().size(); i++) {
        if (this.getBooks().get(i).getPerson() != null) {
            result.add(this.getBooks().get(i));
        }
    }
    return result;
}

Ignoring the method names, let's do a line-by-line comparison. Doing this, we can spot the difference:

if (this.getBooks().get(i).getPerson() == null) {
// vs:
if (this.getBooks().get(i).getPerson() != null) {

The only difference there is == vs. !=; the condition is inverted. After identifying the differences, the next step is to see if you can parameterize the behavior, so that the logic itself is exactly the same and depends only on the value of a few outside variables. In this case, we can see a transformation like this:

a == x    =>    (a == x) == true
a != x    =>    (a == x) == false
both:     =>    (a == x) == <variable>

So we can make those two lines equivalent:

boolean available = true;
if ((this.getBooks().get(i).getPerson() == null) == available) {
// vs:
boolean available = false;
if ((this.getBooks().get(i).getPerson() == null) == available) {

Now the logic is equivalent, and we can select between the two by simply changing available. Make that a method parameter and, voila!

public List<Book> getBooksByAvailability (bool available) {
    List<Book> result = new ArrayList<Book>();
    for (int i = 0; i < this.getBooks().size(); i++) {
        if ((this.getBooks().get(i).getPerson() == null) == available) {
            result.add(this.getBooks().get(i));
        }
    }
    return result;
}

Note that another way to approach this problem is to make "availability" itself be a property of a book. For example, if you move the availability test into a new method Book.isAvailable(), the solution becomes a bit more obvious:

public List<Book> getBooksByAvailability (bool available) {
    List<Book> result = new ArrayList<Book>();
    for (int i = 0; i < this.getBooks().size(); i++) {
        if (this.getBooks().get(i).isAvailable() == available) {
            result.add(this.getBooks().get(i));
        }
    }
    return result;
}

And that has the added bonus of letting you change the internal definition of "availabilty" without modifying code anywhere else (e.g. if, in the future, you decide that getPerson() == null is not a sufficient indication of "availability", you only need to change it in Book.isAvailable()).

As for clarity, you could, as Rohit Jain mentioned in this answer, switch to enhanced for loops to improve readability a bit, e.g.:

public List<Book> getBooksByAvailability (bool available) {
    List<Book> result = new ArrayList<Book>();
    for (Book book : this.getBooks()) {
        if (book.isAvailable() == available) {
            result.add(book);
        }
    }
    return result;
}

To keep your two existing functions, if that's necessary, you can use something like the above as a private utility method, called by the two public ones.

Community
  • 1
  • 1
Jason C
  • 38,729
  • 14
  • 126
  • 182
  • That is terrible way of refactoring I would say. Certainly doesn't look cleaner, and it's also a bit tricky to understand that `if` condition on first look. – Rohit Jain Mar 25 '14 at 17:13
  • @RohitJain I don't know if it's a terrible way of refactoring, but I think the original code was odd to begin with. I just edited to add a comment about moving "availability" into `Book` at the same time you made your comment. That approach ends up with debatably clearer results. – Jason C Mar 25 '14 at 17:15
  • @RohitJain Oh and, yes, everything in your answer is great advice. I didn't want to include too much here; just minimal changes to remove duplicate code from the existing code. The goal was a general "how to spot and remove duplicate code". – Jason C Mar 25 '14 at 17:16
  • 1
    I see what you are trying to avoid with your answer. And trust me, the condition - `this.getBooks().get(i).isAvailable() == available` is hard to get at first time. A beginner might not get that, it is based on `true == true` and `false == false` thing. A bit confusing, that is what my concern is. – Rohit Jain Mar 25 '14 at 17:18
  • @RohitJain That's a valid concern. Unfortunately, this question has exceeded my attention span (I just spotted a shinier object elsewhere). I'll delete this answer if it starts causing confusion. – Jason C Mar 25 '14 at 17:22
  • 1
    Lol.. Shiner object :) – Rohit Jain Mar 25 '14 at 17:23
0

If you don't want to change the method signature, I think you cannot do much better than you already have. You can just rewrite the loops to foreach and possibly do the substractions of the lists. Dirty solution, but the lecturer may take it.

public List<Book> getAvailableBooks() {
    Set<Book> result = new HashSet<>(getBooks());
    result.removeAll(getUnavailableBooks());
    return new ArrayList<Book>(result);
}

public List<Book> getUnavailableBooks() {
    List<Book> result = new ArrayList<Book>();
    for (Book b: getBooks()) {
        if (b.getPerson() != null) {
            result.add(b);
        }
    }
    return result;
}

Probably not the fastest solution, but quite short

NeplatnyUdaj
  • 6,052
  • 6
  • 43
  • 76