User Input Part Deux (Wiki forum at Coderanch) (original) (raw)

(level: Beginner/Intermediate)

This page is "part 2" of the UserInput tutorial, so if you haven't read the first part. I suggest you do before you go any further.

.

UserInput focused on rationalizing the process of user input, which is notoriously fiddly and verbose, into methods that you can reuse. In this chapter, we plan to show you how to build on that by creating your own utility class (or classes).

There are two sections to this part - a "simple" one, and a more "object-oriented" one:

After which you may be interested in going on to UserInputPartIII, which shows a way to build a proper input framework that you can extend and configure any way you like. Needless to say, this part is MUCH longer, and will take more time to write; so if you see a notice at the start of it saying "[under construction]", please be patient.


The simple way - A basic utility class

You will probably have run across utility classes in your lessons already - for example: Math - and their structure is almost always the same:

and the method for setting one up always follows the same pattern:

There are three things to note about the above declaration:

.

So...now what do we do?

Well, the UserInput page showed you how to set up generic input methods, so now you simply move (note: move, not copy) them to your new class, viz:

Hopefully, most of the methods should be familiar from the UserInput page. We've simply transferred them to a class that combines them all, so that they can be used by anyone. I've also added a rangeString() helper method for creating the "range" part of a message.

Note also the special comments that I've included for all the public methods. These are javadoc comments, and I advise you to get to know them, because they provide the wonderful documentation you get in the API.

And now we change our old class to use the utility methods - ie, something like this:

And you can now use those Input methods in any class that needs input.


Improving the Input class (slightly more advanced)

You may have noticed that there's still quite a lot of duplicated code in our Input class. The getInteger()/getDouble() and inRange() methods, for example, are virtually identical except for the type-specific calls they make. Wouldn't it be nice if we could write ones that simply take the type of thing we want to convert to?

At this point, a lot of people (especially beginners) immediately start looking at reflection, because it seems like an answer to all your prayers for dynamic type-checking.

My advice: DON'T.

Reflection can be a very powerful tool in the right situations, but it should be used very sparingly because it's verbose, error-prone, difficult to test, and SLOW.

Furthermore, it should only be used in cases where clients really don't know what a type is going to be until runtime, and that's not the case here. When we call an Input method, we DO know what type we want; we simply want a way to generalise our methods.

So what can we do instead? Answer: Create a class or interface that encapsulates our "type". That's how Object-Orientation works.

In our case, the only thing that's specific in our input process is the conversion; everything else is generic. And for types that don't need converting? Simple: don't convert; just return them.

.

So, putting that into practise, we might come up with something like this:

and then we can write member types for our Input class as follows:

and for types that don't need conversion:

or indeed, just leave it out altogether. After all, a String can be obtained by simply calling Input.input().

And just in case you're not familiar with the constructs above, they are anonymous classes - a very useful thing to get to know.

.

If the above isn't very clear yet, let's see what happens when we put it in our Input class. Pay particular attention to the get() and inRange() methods that replace the type-specific ones we had before:

Note the static qualifier on the Type interface. To be honest, I'm not sure it's absolutely necessary, but I always put it in to remind myself that the definition is nested; and it IS important when you're defining classes.

.

Do you see what's happened? No more overloaded methods, and no (or very little) duplicated code. Furthermore, we can add new member types (for example, a LONG type) as we find a need, and we shouldn't need to change anything else. Indeed, clients can supply their own types if they want to, by simply writing conversion classes that implement Input.Type. And that is what Object-orientation is all about.

And now our calling class will look like this:

.

If you find the definitions of the generic methods confusing, don't worry about it too much for the moment. The first Input style will probably do you just fine until you learn a bit more about generics.

There is also a slight problem with what we've written so far: The error message. It simply says "Invalid input", when we probably want something a bit more descriptive. This could easily be remedied by adding a getType() method to our Type interface, but I'll leave that up to your ingenuity.

It should also be added that this is only ONE way of doing it; there are many others.

.

However, there are still a few drawbacks to the "utility class" approach. Try and see if you can work out what some of them are before you read on.


Drawbacks

Basically, the class is rather brittle, and a lot of that is due to the fact that it can't be instantiated, and its methods are all static:

All the above can be easily remedied, but you need to use an object rather than a utility.


A more 'Objective' way - An Input object

Right, so we want to to include the things mentioned above in the "Drawbacks" section. The simplest way to do that is to make Input a class that we can instantiate, rather than a utility, and add them as fields that can be set up at construction time. Let's have a look at what that might look like:

Pretty simple, no? The main difference so far is that now our constructors are public. We still keep the class final because we really don't want anyone extending it (yet).

And the rest of the Input class doesn't need to change too much, except that its methods will no longer be static. We also need to add logic to keep track of the number of tries on each input.

Let's have a look at what it might look like. I've used the second version as my basis:

Note that we've had to add quite a bit of extra logic to keep track of the number of attempts. This is because the value is independent of all the other things we're doing, which makes things a bit tricky. However, the "business" methods are still pretty much the same, apart from the fact that they are no longer static, and we don't have to supply a Scanner to them.

Note also:

.

And now our calling class will look something like this:

As you see: very little different from before apart from the initial setup.

.

Phew. A lot of code. I bet you never thought you would be writing this much back when you started, but I go back to what I said right at the beginning of this tutorial:

User input is tough.

However:

.

However, we're still not finished yet. There's no doubt that our input object is a lot more flexible than before, but that "try count" logic is pretty cumbersome - and kind of ugly.

UserInputPartIII shows you how to adapt what we've written into a proper input framework, but it does assume some knowledge of Java Generics. If you don't feel you're quite ready yet, you should still be able to use what you've read so far to write a decent utility class that will last you until you're ready for the "next stage of evolution".


CategoryWinston