4.11 Improving structure—the Track class
We have seen in a couple of places that using strings to store all of the track details is not en- tirely satisfactory and gives our music player a rather cheap feel. Any commercial player would allow us to search for tracks by artist, title, album, genre, etc., and would likely include further details, such as track playing time and track number. One of the powers of object orientation is that it allows us to design classes that closely model the inherent structure and behaviors of the real-world entities we are often trying to represent. This is achieved through writing classes whose fields and methods match those of the attributes. We know enough already about how to write basic classes with fields, constructors, and accessor and mutator methods that we can easily design a Track class that has fields for storing separate artist and title information, for instance. In this way, we will be able to interact with the objects in the music organizer in a way that feels more natural.
So it is time to move away from storing the track details as strings, because having a separate Track class is the most appropriate way to represent the main data items—music tracks—that we are using in the program. However, we will not be too ambitious. One of the obvious hur- dles to overcome is how to obtain the separate pieces of information we wish to store in each Track object. One way would be to ask the user to input the artist, title, genre, etc., each time they add a music file to the organizer. However, that would be fairly slow and laborious, so, for this project, we have chosen a set of music files that have the artist and title as part of the file name, and we have written a helper class for our application (called TrackReader) that will look for any music files in a particular folder and use their file names to fill in parts of the cor- respondingTrack objects. We won’t worry about the details of how this is done at this stage.
(Later in this book, we will discuss the techniques and library classes used in the TrackReader class.) An implementation of this design is in music-organizer-v5.
Here are some of the key points to look for in this version:
■ The main thing to review is the changes we have made to the MusicOrganizer class, in going from storing String objects in the ArrayList to storing Track objects (Code 4.7).
This has affected most of the methods we developed previously.
a while loop that divides n by all numbers between 2 and (n–1) and tests whether the divi- sion yields a whole number. You can write this test by using the modulo operator (%) to check whether the integer division leaves a remainder of 0 (see the discussion of the modulo operator in Section 3.8.3).
Exercise 4.34 In the findFirst method, the loop’s condition repeatedly asks the files collection how many files it is storing. Does the value returned by size vary from one check to the next? If you think the answer is no, then rewrite the method so that the number of files is determined only once and stored in a local variable prior to execution of the loop. Then use the local variable in the loop’s condition rather than the call to size. Check that this version gives the same results. If you have problems completing this exercise, try using the debugger to see where things are going wrong.
■ When listing details of the tracks in listAllTracks, we request the Track object to return aString containing its details. This shows that we have designed the Track class to be re- sponsible for formatting the details to be printed, such as artist and title. This is an example of what is called responsibility-driven design, which we cover in more detail in a later chapter.
■ In the playTrack method, we now have to retrieve the file name from the selected Track object before passing it on to the player.
■ In the music library, we have added code to automatically read from the audio folder and some print statements to display some information.
import java.util.ArrayList;
/**
* A class to hold details of audio tracks.
* Individual tracks may be played.
*
* @author David J. Barnes and Michael Kửlling * @version 2011.07.31
*/
public class MusicOrganizer {
// An ArrayList for storing music tracks.
private ArrayList<Track> tracks;
// A player for the music tracks.
private MusicPlayer player;
// A reader that can read music files and load them as tracks.
private TrackReader reader;
/**
* Create a MusicOrganizer */
public MusicOrganizer() {
tracks = new ArrayList<Track>();
player = new MusicPlayer();
reader = new TrackReader();
readLibrary("audio");
System.out.println("Music library loaded. " +
getNumberOfTracks() + " tracks.");
System.out.println();
} /**
* Add a track to the collection.
* @param track The track to be added.
*/
Code 4.7 UsingTrack in the MusicOrganizer class
4.11 Improving structure—the Track class | 121
public void addTrack(Track track) {
tracks.add(track);
} /**
* Show a list of all the tracks in the collection.
*/
public void listAllTracks() {
System.out.println("Track listing: ");
for(Track track : tracks) {
System.out.println(track.getDetails());
}
System.out.println();
} /**
* Play a track in the collection.
* @param index The index of the track to be played.
*/
public void playTrack(int index) {
if(indexValid(index)) {
Track track = tracks.get(index);
player.startPlaying(track.getFilename());
System.out.println("Now playing: " + track.getArtist() + " - " + track.getTitle());
} }
Other methods omitted.
} Code 4.7
continued UsingTrack in the MusicOrganizer class
While we can see that introducing a Track class has made some of the old methods slightly more complicated, working with specialized Track objects ultimately results in a much better structure for the program as a whole and allows us to develop the Track class to the most ap- propriate level of detail for representing more than just music file names.
With a better structuring of track information, we can now do a much better job of searching for tracks that meet particular criteria. For instance, if we want to find all tracks that contain the word “love” in their title, we can do so as follows without risking mismatches on artists’ names:
/**
* List all tracks containing the given search string.
* @param searchString The search string to be found.
*/
public void findInTitle(String searchString) {
for(Track track : tracks) {
String title = track.getTitle();
if(title.contains(searchString)) {
System.out.println(track.getDetails());
} } }
Exercise 4.35 Add a playCount field to the Track class. Provide methods to reset the count to zero and to increment it by one.
Exercise 4.36 Have the MusicOrganizer increment the play count of a track whenever it is played.
Exercise 4.37 Add a further field, of your choosing, to the Track class, and provide acces- sor and mutator methods to query and manipulate it. Find a way to use this information in your version of the project; for instance, include it in a track’s details string, or allow it to be set via a method in the MusicOrganizer class.
Exercise 4.38 If you play two tracks without stopping the first one, both will play simulta- neously. This is not very useful. Change your program so that a playing track is automatically stopped when another track is started.