This site uses Cookies to help improve it Learn More

Object-oriented programming with Java

Editor

public class LibraryBook { }

Output

Designing Classes

Overview

A class represents the blueprint for the state and behaviour of objects which derive from it.

This task is designed to help you develop a class to represent a library book (or more accurately a library book record) whereby a book can be loaned, renewed, returned and have a hold placed upon it.

In order to carry out operations like checking the book out or renewing it, the class will also need to be aware of the length of a loan and how many times the book can be renewed, as these values might vary from book to book.

It will also make use of the LocalDate class for handling date related operations (like storing when the book is due to be returned).

All the code you write should go in the editor below, and with the sole exception of any 'import' statements, the code should be placed between the existing braces, that is between { and }

Adding some fields

Although a library book might have various bits of data associated with it, such as title, publisher, edition, publication date, ISBN etc., we will keep our implementation of the 'book' part simple and use just store its title. This will be stored in a field which is a variable that belongs to each object derived from the class. Another name for a field, more commonly used in other programming languages, is an instance variable or ivar.

  • Add the following code, inside the class to create a String field called title:
    private String title;
    Note the use of the private keyword. This ensures that the title field is not accessible from outside the class.
  • Run this test and ensure it passes. If it does not, check the output message for feedback. The first time you run at test it may take 15 seconds or more, but subsequent runs shoud be much quicker
  • Add another field named loanLength which will store an integer value
  • Run this test, and check it passes, referring to any feedback in the output window if it fails
  • Add another field named maxNumberOfRenewals, again to store an integer and once you have done this, run the test.

Creating a constructor

Our LibraryBook class has fields for name, loanLength and maxNumberOfRenewals, but they have not been assigned any values (and, as different books have different titles, loan lengths and limits on how many times they can be renewed, we do't want them to have default values) however we want to ensure that those fields contain the correct values when a new LibraryBook is created (rather than being left as null). To do this we need to create a constructor. A constructor is used when we create a new instance of a class to configure it ready for use. For our LibraryBook we might use a constructor as follows:

//do not add this code to your project
int daysPerLoan = 7;
int maxRenewals = 5;
LibraryBook warAndPeace = new LibraryBook("War and Peace", daysPerLoan, maxRenewals);

  • Add the following, partially complete code to create a constructor:
    public LibraryBook(String title, int loanLength, int maxNumberOfRenewals) {
        this.title = title;
        //todo: set loanLength
        //todo: set maxNumberOfRenewals
    }
    You will notice that there is a parameter called title in the constructor which has the same name as one of the fields we created earlier. To differentiate between the field and the parameter with the same name, we prefix the name of the field with the keyword this. The line this.title = title; takes the value from the parameter and stores it in the field
  • Run the tests to verify this has been added correctly
  • Neither the loanLength or the maxNumberOfRenewals parameters are stored in the field by the constructor. Add code in place of the comments to set these fields
  • Run the test and verify that it passes
  • Finally, once the LibraryBook is created and has a value set by the constructor, we want to ensure the title cannot be changed. Add the keyword final so the title field appears as follows
    private final String title;
  • Run the test to ensure that the field is correctly specified as final

Accessors

Each of our fields are private (which is normally how fields should be), however we will want to be able to access each of the fields we've added so far from outside the class. We will add accessors or 'getters', which are methods which retrive the value of a field. These methods are declared public so they can be accessed from anywhere else

The convention for naming a getter is the word get followed by the name of the field, but where the first character of the first word of the field name is uppercase

  • Add the following code to create a getter for the title field:
    public String getTitle() {
        return title;
    }
    
  • Run the test
  • Add the following code to create a getter for the loanLength field:
    public int getLoanLength() {
        return loanLength;
    }
    
  • Run the test
  • Finally, using the same conventions as above, add a getter for the maxNumberOfRenewals field
  • Run the test

Mutators

It should be possible to change the value in some of the fields after an instance of LibraryBook has been created. In particular the loanLength and maxNumberOfRenewals fields. To do this we need to create a mutator (or setter). Like getters, these are methods which belong to the class and are declared as public

The convention for naming most setters is the word set followed by the field name, but where the first character of the first word of the field name is uppercase

  • Add a public method called setLoanLength which will take an int as a parameter, and store it in the loanLength field as shown here:
    public void setLoanLength(int loanLength) {
        this.loanLength = loanLength;
    }
  • Run the test
  • Add another method named setMaxNumberOfRenewals which also takes an int as a parameter and stores it in the maxNumberOfRenewals field
  • Run the test

Additional fields for the class

In order for our class to function as desired, it needs to have certain additional fields to keep track of the state of loans. This includes - how many times the book has been renewed, when the book is due back, and whether someone has placed a hold on the book (i.e. to prevent it from being renewed again)

  • Create a field named currentRenewals which should store an int and have a default value of 0
  • Run the test and verify this has been done correctly
  • Create another field to hold a boolean named held and set its initial value to false
  • Run the and verify that this has been created correctly
  • Finally create a variable named dueDatewith the type LocalDate but set its initial value to nullThe reason the initial value is set to null in this case, is because a book that is not on loan will not have a due date.

    Note that you will also need to add an import statement for the LocalDate class as follows:

    import java.time.LocalDate;

  • Run the test to verify this has been done correctly

Additional getter

  • Using the same approach and naming convention used for getters previously, add a getter for the held field. Note that convention allows getters for boolean values to be named using the prefix 'is' or 'has' rather than 'get', so name this method isHeld
  • Run the test

Additional methods for the class

Check out

  • We'll start by adding method to try to check out the book. This will be one of the more complex methods as we need to first check the book is not already on loan (by checking the dueDate field is null, and if so set dueDate so that isloanLengthdays in the future, by adding that many days to the current date and then returning true. If however the dueDate field is not null, the book must already be on loan, so we will return false (to indicate the checkOut was unsuccessful)

    Add the following method which does what was described above:

    public boolean checkOut(){
        if (dueDate == null){
            dueDate = LocalDate.now().plusDays(loanLength);
            return true;
        }
        return false;
    }

  • Now the checkout process is implemented, add a getter for the dueDate field
  • Run the test
  • Run the test

On loan

  • Add a method named onLoan which will check if a book is on loan (i.e. by verifying the dueDate is not null) and return true or false accordingly
  • Verify the solution is correct by running the test

Check in

  • Checking a book back in is straightforward, we set the currentRenewals field's value to 0 and the dueDate field's value back to null. Add a method called checkIn to do that
  • Run the test called test

Placing a hold

  • It should only be possible to place a hold if a book is on loan, and not already being held. Add a method called placeHold to set the held field to true if the appropriate conditions are met, and then return either true or false, depending on whether or not a hold could be placed
  • Test it using the test

Cancelling a hold

  • Add a method called cancelHold which will cancel the hold on a book
  • Run the test

Renewing the book

  • Finally, it should be possible to renew the book if it hasn't already reached (or exceeded) the maximum number of renewals and it is not being held. Add a method called renew which will attempt to renew a book and return true or false if it can be renewed. Don't forget to increment the value of the currentRenewals field. The code you you will need to increment the date is as follows:
    dueDate = LocalDate.now().plusDays(loanLength);
  • Run the test

Checking if the book is overdue

  • It should also be possible to verify if a book is overDue. Create a method called isOverDue which will return true if the book is overdue, false otherwise. Be careful as it should also return false if the book is not on loan (and not throw an exception). A book is considered overdue if it is on loan and the due date is in the past
  • Run the test

The End

Your LibraryBook class should now be complete. Choosing 'LibraryBookTest' from the test dropdown will run all the tests. Verify that they all pass.