Example 2 is quite bad for testing... and I don't mean that you can't test the internals. You also can't replace your XmlReader
object by a mock object as you have no object at all.
Example 1 is needlessly hard to use. What about
XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();
which is not any harder to use than your static method.
Things like opening the URL, reading XML, converting bytes to strings, parsing, closing sockets, and whatever, are uninteresting. Creating an object and using it is important.
So IMHO the proper OO Design is to make just the two things public (unless you really need the intermediate steps for some reason). Static is evil.
Note: Edited to reflect Dragon's modified requirement to make it work upon selection.
A common pattern to generalize this sort of thing is an adapter. Have an abstract class called GUIAdapter which can tinker with the individual gui aspects of your program according to the settings:
public abstract class GUIAdapter {
public abstract void adapt(GUIFrame frame);
public abstract void remove(GUIFrame frame);
}
Then override GUIAdapter with adapters suitable for each type of situation:
public class WindowsGUIAdapter extends GUIAdapter {
public void adapt(GUIFrame frame) {
// Things to do when windows operating system
}
public void remove(GUIFrame frame) {
// Remove trace of WindowsGUIAdapter
}
}
public class LinuxGUIAdapter extends GUIAdapter {
public void adapt(GUIFrame frame) {
// Things to do when linux operating system
}
public void remove(GUIFrame frame) {
// Remove trace of LinuxGUIAdapter
}
}
public class TomcatGUIAdapter extends GUIAdapter {
public void adapt(GUIFrame frame) {
// Things to do when using tomcat server engine
}
public void remove(GUIFrame frame) {
// Remove trace of TomcatGUIAdapter
}
}
// et cetera ...
These adapters have as much or as little control as you let them have over your frame. When the operating system or server is known, you call "remove" on all existing adapters, add the appropriate adapter for the current selection, and then call "adapt" to apply the configuration for current selection. I would recommend that you would add a panel to the frame and remove it when "remove" is called. Keep a reference to JPanel as a private member of your extended class of GUIAdapter in order to be able to remove it easily afterwards.
Notice that I didn't make a WindowsTomcatGUIAdapter. The idea is that your GUIFrame class (I'll assume that's what it's called) houses a list of adapters which get called to modify the state of the frame according to the selection without knowing how they work.
public class GUIFrame extends JFrame {
// Other protected variables here
private List<GUIAdapter> adapters = new ArrayList<GUIAdapter>();
private JList serverList, osList;
private SERVERS selectedServer = null;
private OS selectedOS = null;
public GUIFrame() {
initialize();
}
private void initialize() {
// Do things common to all here
// Operating system select event
ListSelectionListener refreshOptionsListener = new ListSelectionListener() {
public void valueChanged(ListSelectionEvent listSelectionEvent) {
selectedOS = (OS)osList.getSelectedValue();
selectedServer = (SERVERS)serverList.getSelectedValue();
refreshAdapters();
}
};
// Instantiate osList
osList = new JList();
// ...
osList.addListSelectionListener(refreshOptionsListener);
// Instantiate serverList
serverList = new JList();
// ...
serverList.addListSelectionListener(refreshOptionsListener);
}
private void refreshAdapters() {
for(GUIAdapter adapter : adapters) {
adapter.remove(this);
}
adapters.clear();
if(os == OS.WINDOWS) {
adapters.add(new WindowsGUIAdapter());
} else if (os == OS.LINUX) {
adapters.add(new LinuxGUIAdapter());
} // else if () ...
if(server == SERVERS.TOMCAT) {
adapters.add(new TomcatGUIAdapter());
} // else if () ...
for(GUIAdapter adapter : adapters) {
adapter.adapt(this);
}
}
// Other class logic here
}
Now you've decoupled all logic pertaining to operating system or engine with the classes which perform this logic. Whenever a new operating system or a new server is selected, current adapters are removed and new adapters are added. If no operating system or server is selected, then no adapter is added (no condition is met in if/else chain since selectedOS or selectedServer would be null), which is fine. The frame is not adapted since no adaptation is required as you would expect.
This pattern works well only if server adapters do not really need to know what operating system is being used or inversely, if operating system adapters do not really need to know what server is being used. However, if you find yourself in that situation, you should create a WindowsTomcatGUIAdapter which deals with just that particular case rather than attempt to make WindowsGUIAdapter and TomcatGUIAdapter work well together (as that would create coupling once again). Future additions are literally as simple as adding a new class and plugging them in on initialization.
Presumably, you could extend this logic to include validation or whatever other type of logic particular to that particular adapter.
Best Answer
IMO, "making my method more "generic" and not reliable on the class" is not a good goal, since it promotes classes with low cohesion.
If your method is generic enough to serve for other classes, too, then take it out of the class and move it to a "function library" class. (But beware of the functional decomposition antipattern).