[This page is under construction, and should hopefully be ready in a couple of weeks (timeline: mid-Jan 2014). Please disregard any information you see until this message is removed]
(Level: intermediate - in particular, it assumes a basic knowledge of Java Generics)
In UserInputPartDeux we showed you how to build an Input utility class, and how to expand that into a more flexible input object.
However, there are still some drawbacks, even with the latter approach, the main of which is the rather cumbersome logic we have to write to implement the "try count". What if we could create an input object tailored exactly to our needs? Then we could make the number of tries part of that object, rather than having to pass around a "count" between methods.
Using a "month" value as an example, the process then becomes:
Create an input object for a range (in this case: 1-12).
Use it to get a month value.
Forget about it, or re-use it as needed.
A second benefit of a tailor-made object is that it is inherently Thread-safe because we create individual objects for each value we want to input. Before, we created one object for all our input requirements.
Now this may sound a bit OTT for getting a simple value but, as you'll see, it allows us to create an extremely dynamic framework for the process, which we can extend almost any way we like.
So what if we end up creating a few extra objects? That's what Object-Oriented languages are designed for; and even a few thousand objects is nothing to a modern computer.
Furthermore, it simplifies the API enormously, since we only have one public method to worry about - get(). We can also make use of generics, both for compile-time safety and as a documentation aid.
However, such a design does require us to re-think our model a bit.
First: There are some things, like the input and output streams, our 'Please enter' prompt, and our generic "types", that we probably only want to set up (or define) once. It's only when we have specifc logic (eg: ranges, or limiting the number of tries) that we want to create tailor-made objects to do the work.
This implies that our structure needs to be tiered: first, we need an Input object of the kind we saw at the end of UserInputPartDeux; second, a new tier of objects that we create from our initial Input one. This makes our Input object a factory - quite a common pattern in software design.
Furthermore, if we design it properly, we can allow clients to create their own tailor-made classes, much as we did with our Type interface, and then simply use our Input factory to plug in its own streams, etc to create the final object.
If all this seems like a fabulous amount of effort simply to get a number or a string: you're right. I'll say it one more time:
User input is tough.
If you're happier off going back to simply writing code that you have to repeat every time you want to input something, or with the simple utility class we already covered, then the likelihood is that you're not ready for this. However, if you want a framework that you write once, stick in a package, and forget about, safe in the knowledge that it will probably handle anything you ever want to input - including things we haven't even thought about yet - read on.
Whenever you embark on a new project, the first thing you should do is come up with a description of the problem. And that description should be what you want to do, not how you want to do it. You are NOT coding yet. For more information, have a look at the WhatNotHow page.
Furthermore, when you're doing this stuff, start simple and build on it bit by bit. The first thing you write is sometimes called a "Mission statement", and it should be very short - two or three sentences at most.
So: what do we want to do? How about this:
"We need a framework for user input that accepts data from a source such as a keyboard, and converts it to any type we want, including any constraints we may wish to impose on its validity. The framework needs to allow users to abort the process at any time, and/or specify a maximum number of attempts, after which it exits in a recognisable manner."
Looks simple, huh? Don't be fooled. That one took me about an hour, and I've worked on projects where they took days, because getting it right is important. After all, it's going to be your primary source document. The main things to remember:
It must be short.
It must be simple.
It must be complete.
and that last requirement can be a real bastard.
Did you also notice that last sentence? It says that we need to allow the user to abort the process themselves. Note that we haven't said how we're going to do this, but allowing them to enter something like "quit" seems like a reasonable idea - and something we haven't yet talked about in our tutorial up to now.
So, what next? One thing I suggest is to start a list of assumptions. This can be very useful to make sure that you're not getting off track, and you can add to it as you see fit. There are only a few I can think of at the moment:
The framework is for "user input", so it will be dealing with a person, not a file or a stream.
The process will be interactive (ie, a dialogue).
The "source" will be character-oriented (eg, a keyboard).
Output for prompts and messages will also be character-oriented (eg, a terminal).
that last one could be modified later on, but lets leave it in for now to save speculation.
Next: we probably want to expand on a few of the keywords in our mission statement, so that their meaning is unambiguous. For example:
TYPE Defines the type of object we will be needing. This may include any conversion that needs to be done on the text that the user enters, and any basic validation to ensure that we can in fact convert it.
CONSTRAINT A validation constraint on the value the user types in, over and above that required for simple conversion. For example: a number may have to be in a certain range.
Do you see what's happening? We're building on our initial statement, filling it out as we go along. And also note that we haven't ONCE mentioned a Java class yet, or indicated HOW we're going to code this.
OK, that's enough design stuff for the moment (actually, in the real world it probably isn't; but you're here to see some code - right?).
First, let's gut our Input class and reduce it to only what we need:
Notice that maxTries is no longer a member variable, because we will be adding that to our 'tailor-made' objects in due course; but we have added an abortString field. This is so we can allow users to enter a string that means "get me out of here!".