JavaRanch Home    
 
This page:         last edited 16 February 2014         What's Changed?         Edit

First Factory Methods   

[this page is under construction]

This page is a follow-up to the FirstClasses tutorial, and shows you how to create factory methods for your Person class.

First off, we should probably describe what a factory method is:

For the purposes of this discussion, a factory method is simply a static method that returns an instance of our type - ie, it's basically an alternative to a constructor.

There is a bit more to it than that; but for the moment the above description should suffice.

Factory methods can be a very useful because:

  • They have a name, so you can make them very descriptive. For example, if we have a Pizza class, we could write a dailySpecial() factory method that returns us a Pizza with the "toppings of the day".

  • They are just regular methods, so they are not restricted by the same rules as constructors. Specifically:
    • They don't have to be chained.
    • They don't have to return the same type as an equivalent constructor. They can return a more general one, or indeed a hidden subclass.
    • They can perform extra initialization or validation before they return an object to you.
    • They can return more than one instance. You'll see an example of this below.
    • They don't even have to return a new instance. Integer.valueOf(1), for example, will return the same Integer object every time it's called.

Indeed many programmers use them to hide constructors from clients.

This page contains some useful information about factory methods.



fromString(String)  

One thing you will probably want to be able to do - at least early on - is to be able to create a number of instances of your class from a text file of Person records, where each "record" is a line in the file.

The first thing you need to do is to decide what that "record" will look like. Sometimes, you will be given a format that you have to work with, such as CSV, in which case you'll have to write it to spec; other times you will be able to create a format for yourself.

.

Since CSV can get a bit involved, I'm going to assume that we've been allowed to decide our own format - and since we already have one from our toString() method, ie:

Joe Bloggs;47

why not use it?

Tip #13:  

If you write a fromString() method (or equivalent), and you get to decide its input format, make it compatible with your toString() method.

.

OK, lets parse that string. The first method you need to know about for something like this is String.split() - an incredibly useful method that you should become familiar with ASAP.

There's quite a bit to know about it, but in it's simplest form it breaks up a String into an array of "fields" (also Strings), based on a 'delimiter'. In our case the delimiter is ";" so the call will look something like:

String[] fields = s.split(";");

which will put "Joe Bloggs" into fields[0] and "47" into fields[1].

Tip #14:  

String.split() takes a regular expression (regex), and they have quite a few "metacharacters". If, as in our case, the delimiter only has one character, and you're not sure if it's a metacharacter or not, you can put it in square brackets, viz:

String[] fields = s.split("[;]");

which will remove any special meaning it has for the regex. In fact, ";" doesn't, so you don't need to do it, but I'll leave it in to remind you.

.

Right, so let's use it:

public static final Person fromString(String s) {
  String[] fields = s.split("[;]");

  if (fields.length != 2)
    throw new IllegalArgumentException(
      "String has wrong format");

  return new Person(fields[0], fields[1]);
}

Simple, eh? Do you see what's happening? We've already done most of the work, and all we're doing is re-using it. And that's how to write properly modular code: you build it up, bit by bit.

The check is simply to make sure that the supplied string contains exactly 2 fields, separated by a single ";".



fromFile(String)  

Our next factory method builds on the previous one to return a bunch of Person objects from a file whose path is supplied to the method as a String.

Here it is:

public static final List<Person> fromFile(String path)
  throws IOException
{
  BufferedReader br = new BufferedReader(
    new FileReader(path) );

  List<Person> list = new ArrayList<Person>();
  String line = "";

  while ((line = br.readLine()) != null) {
    list.add( fromString(line) );
  }

  br.close();

  return list;
}

I don't propose to go through the code in minute detail, since I/O is beyond the scope of this tutorial; however, there are a few points worthy of note:

  • The throws clause: Java I/O notoriously throws IOException on practically everything it does, and IOException is a checked Exception, so rather than worry about every possible eventuality, just declare that our method can also throw the exception, and let someone else deal with it (or not) .
  • The while expression: This is the standard way to read "lines" in a file, and the brackets around
     line = br.readLine()
ARE necessary.
  • The close(): Remember to close files and streams after you've used them.
  • The return type: Note that it's a List, not an ArrayList, and it's a very important rule to remember: Program to interfaces rather than classes.



fromUser()  

This final factory method does something you'll need to do a LOT early on in your programming study: create an instance of a class by prompting the user for input and taking what they type in from the keyboard. Unfortunately, this is quite a fiddly process, and most beginners create enormous piles of procedural code in their main() methods to do it.

DON'T.

It's a class's responsibility to know how it is created, and that includes via user input. Furthermore, you can re-use a lot more code if you write it in the class.

There is a LOT to know about user input, which I've covered in a tutorial called UserInput. The following is just a taster of the sort of thing you'll need to do:

public static final Person fromUser() {
  Scanner s = new Scanner(System.in);

  System.out.println("Please enter the Person's name: ");
  String name = s.nextLine();

  System.out.println(""Please enter an age in years"
      + " between 0 and 125: ");
  String age = s.nextLine();

  s.close();

  return new Person(name, age);
}

It's not particularly brilliant, because it will throw an Exception if the user doesn't follow orders but, as I said above, an exhaustive look into the business of UserInput is beyond the scope of this tutorial.

However, do you now see why we wrote the constructor that takes Strings? We've now used it twice to write some very useful methods.

.

And just in case you didn't know, calls to our factory methods should use the class name because they're static; viz:

List<Person> people = Person.fromFile(
   "/home/winston/data/ourPeople.txt");

Person joe = Person.fromString("Joe Bloggs;47");

Person anotherJoe = Person.fromUser();



So that's our factory methods done. Don't worry if their significance isn't immediately obvious, just make sure you understand the building process - making a class that can do great things from small pieces of code, built piece by piece.



CategoryWinston

JavaRanchContact us — Copyright © 1998-2014 Paul Wheaton