• Post Reply Bookmark Topic Watch Topic
  • New Topic

Trying to think in Java and properly use subclasses and enums.  RSS feed

 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'm trying to sort out the best way to handle a situation where I will want to run some subset of operations, where the subset is likely to come from a file. The operations are related, so I was thinking I would define them from a common interface. Then I need to match up the strings in the file to the right implementation of the interface.

Bear with me for a minute, I wanted to go through my logic. Below is my first step toward satisfying the requirements. The code is just made up to have something that compiles and runs. The real operations are a lot more complex, I tried to make these simple to reduce the lines of code.





This works. But it seems I am not making use of the fact that I declared an interface. I need to know when I get to running the tests, which kind of test to make an instance of. So, I made BenchTest and FieldTest abstract and added a few new classes extending them. In this example, I made them really simple, in real life there would be more differentiation, but most of the methods in FieldTest are used (not overridden) in the xxxFieldTest classes.


And changed my main routine to:

I am now making more use of my interface. But this is not quite enough, as I will still be reading strings from a file to determine what subset I am actually going to use.

So, I tried introducing enums to the mix:


I'm thinking I haven't got things quite right yet. If I have a new test, a) I need to create a new test class (which is a good thing), b) I need to add another entry in the enum class and c) update my switch. The latter problem I can fix and it simplifies my main:


Am I headed in the right direction? I still have two files I need to update if I create a new class. Is there something I am missing that would be cleaner? Am I actually thinking in Java or have I gone off track?
 
Maxim Karvonen
Ranch Hand
Posts: 121
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Jon.

You have correctly identified a problems in using enum. Using Enum requires to update it (and not only it) when something changes.

Maybe using reflection will be a better approach. Using it you can write a qualified class name in config file and create it's instances. No aliases for classes (in a enum), no switch/cases to instantiate a proper value. Even no lookup to find a enum value for a config string! Just a qualified class name. Read an Oracle reflection tutorial. Look at Class.forName, Class.newInstance, Class.getConstructors, Constructor.newInstance() javadocs. Of course, you should correctly handle any possible errors (no class found, constructor exceptions, no suitable constructor, object is not assignable to an interface, classloaders issues in complex scenarios, etc...). But this way is more flexible than using enums.

Rule of thumb. You should use enums when you want some guarantees from a compiler. Such as "value can be only from a give set", "all inputs are handled in this switch", "we want to pass only a limited set of values to a method" or something like that. When some constraints/decisions are not available at a compile time, using reflection may be a good approach. Other approach may be to create an instance using limited number of base "building blocks" (it is another approach and it does not fit for creating tests at all!).
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks Maxim. It will take me a little while to understand reflections, but I am working on it.

Is going this direction any better than enums?




It requires initializing my set of possible test cases, but that might be more maintainable that keeping the enum up-to-date. The way I did it above, I have exactly one instance of each kind of test.

Still reading about reflection. Any suggestions for reading about it besides the Oracle Java tutorial?
 
Winston Gutkowski
Bartender
Posts: 10575
66
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Jon Swanson wrote:Thanks Maxim. It will take me a little while to understand reflections, but I am working on it.

I hate to disagree with my esteemed colleague Maxim here, but I think you should be VERY careful about using reflection.

Reflection is ONLY required when you cannot reasonably predict the nature (or structure) of the data you are receiving. And that doesn't sound to me like the case with your tests. Reflection, in Java, is also complex, arcane, inherently insecure without a properly configured SecurityManager (which you may need to propagate), difficult to test, and SLOW (up to 50 times slower than normal code), and so usually best confined to small and strictly defined operations.

There are also patterns available that allow you to use normal Java code in many situations that, on the surface, seem like they need reflection. The only problem is that they require a bit more initial thought and prep-work - very likely no bad thing.

If I see anything more specific in your code I'll let you know, but I thought I'd kick off with this: Beware of reflection; it sounds great in theory, but it can be an absolute pig in practise. I avoid it like the plague, and I've been at this for quite a while.

Winston
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks Winston,

What I have been reading about reflection echoes your thoughts. I've gotten into some reading on using annotations that might be helpful through my investigation of reflection. I don't quite have my head around it yet.

One benefit of JavaRanch is it tends to enforce the habit of thinking through what you are trying to do before doing. Just preparing a simple example to illustrate a question is a very beneficial exercise. I'm trying to be in the habit of making these simple examples for myself to check the feasibility of my design choices before committing to them, which includes exploring existing design patterns.

I thought of a slightly different way to describe what I am puzzling over. Let's say I want to be able describe zoos and start out thinking about the animals that will be in the zoo. I create an interface Animal. I decide that I will be dealing either with Carnivores or Herbivores and make these abstract classes that implement Animal. At which point I extend the abstract classes and make Lions, Tigers, Bears, Goats and Chickens. At this stage, I decide I would like a configuration file that allows me to build the components of a specific zoo. For example, I want to create a PettingZoo. No problem, that's just a case of aggregation. The PettingZoo will contain some number of Animals. At some point I have to write code that populates the PettingZoo, which reads my configuration file and adds the animals. I don't see a way to do this without PettingZoo having something like a switch statement to determine from the line in the configuration file which class to instantiate.

Now let's say I add another class Rabbits. I was trying to experiment with ways that would allow me to add a Rabbits class and only need to update my configuration file to add Rabbits to the PettingZoo. My concern being that the code that populates the PettingZoo would get out of synch with the code that defines the classes of Animals. So if I didn't need to change the PettingZoo code, things would be more robust.

I tried enums, since I can associate a name with an enum. But that just seemed to pass the buck to a different class that had to keep in synch with what Animal classes I created. Which is how reflection got into the discussion, how do I instantiate a class, just given its name (or an alias for it).

The answer might be I am thinking about this all wrong, but given I am asking the question to try and learn this stuff, that is a good answer.
 
Maxim Karvonen
Ranch Hand
Posts: 121
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Jon.

Yes, your solution with a hash map is better than using enums. It does not tie list of test to a list of predefined enum values. And you want to read test names during a runtime so no compile-time checks (for config values, for example) anyway.

I also admit that your approach with a hash-map is way much more flexible than using reflection. Reflection will require to implement an interface in each test. With a registration approach you may create more complex tests at runtime. For example, provide a converter from another test interface (for example, from a JUnit test) to StatsTest and register "wrapped" tests in a hash map. Or you may register different instances of a same class with a different parameters (passed as constructor arguments). Little overhead (one line to register a test) is a good price for that kind of flexibility. Moreover, said lack of that flexibility is the reason I don't like JUnit library implementation. As far as I know it does not provide a handy API to compose/register tests at runtime.

So I suggest to use your approach with manual test registration at this point. Maybe not in a static map but in a TestTypes instance (even more flexibility, you may create several tests configuration). While you have no much tests it is pretty convenient to register them manually. And this approach is extensible. If later a need arises, you may write some code which read some other config file and registers test instances (using dependency injection containers, reflection, reading input parameters for several "similar" tests, searching for some annotation, etc...). It still will have a same "core" beneath.

In this way your Zoo example is also became pretty simple. A piece of code, which creates an animal by it's name is called factory (pet factory in this case). Your hash-map with a getTest method is an exactly that kind of factory. So you can create or configure that factory at a runtime. With a good factory API you may add new factories (or factory configurators) as needed. Manual code configuration (most flexible and unavoidable for a complex cases), reflective/annotation based configurations, etc...

A word on annotations. They are even more complex than reflection. There is no robust way to find classes with a specific annotation (primarily to a reason that classes are loaded on-demand). So you not only need to deal with all reflection complexities, but also with a class discovery.

P.S. I don't know other tutorials on a reflection (because don't used them). So I cannot suggest other reading except Oracle tutorial and javadocs.
 
Winston Gutkowski
Bartender
Posts: 10575
66
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Jon Swanson wrote:I thought of a slightly different way to describe what I am puzzling over. Let's say I want to be able describe zoos and start out thinking about the animals that will be in the zoo.

Sorry for the delay in replying, I was kind of busy yesterday.

My first suggestion would be not to start complicating things with all sorts of theoretical scenarios like zoos (although I understand the motive).

You have a basic problem: Tests that need to be run, triggered by a file that supplies the type of test required and presumably the order in which they need to be run (wasn't too sure about that). Work with that and don't embellish it too much.

First - My first instinct seems to be right: You don't need reflection for this. This would appear to be a basic polymorphic problem, and seems to involve a simple translation of a record in a file to a type (or rather, a subtype) of StatsTest. You might want to think about a TestFactory class that does precisely that.

Second - I'm not quite sure what these "quick" and "slow" divisions are. Are they something that applies to all tests? Do they carry any data of their own, or do they simply regulate how a test gets done? If the answer to those questions is 'yes' and 'the latter', then the choice of an enum seems perfectly reasonable to me; but I would also make it an attribute of your tests. You might also want to think about an abstract skeleton class (AbstractStatsTest?) that incorporates the things common to all your tests and is a parent to your Fieldtest and BenchTest classes.

Third: A lot of programmers forget this, but enums can implement interfaces; and it's sometimes very useful. Since I don't have a complete handle on your problem quite yet, I'm not sure whether it'll help, but it's certainly worth remembering.

Finally: As you seem to have worked out, the idea behind a polymorphic solution is to get to a List of StatsTests, which you then run. The subtypes will take care of doing the job correctly without any dispatch code, and without reflection.

HIH

Winston
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
All my tests have to do certain things, like run and report. So I created an interface. There are several fundamentally different ways the tests run. So I created an abstract class for each basic type of test, represented by FieldTest and BenchTest. The abstract classes don't really share much code, other than they should run and report. But there are some more subtle differences that modify how the tests run. Which I tried to represent as SlowFieldTest and QuickFieldTest. That's the level where you actually have all the info to run a test.

I probably shouldn't have said slow and quick. There isn't a whole series of slow tests. It was just supposed to represent that it used the basic field algorithm for doing its tests, but with different options than the one I called quick. The bench algorithm is totally different, so if I created a SlowBenchTest class, that name would just be confusing, since it would have nothing in common with SlowFieldTest except the ability to run a test and report a result.

I thought about not making FieldTest and BenchTest abstract and just passing in parameters. But the things they need to know are different, which conflicted with my idea of creating a list of Tests and methods that just needed to know the interface, not which kind of parameters to use. So I went with the idea of making all these tests as instances of the abstract classes with the differences handled inside. I wanted to connect them to user-friendly names for the people that will actually run them. Then got to thinking there could be a lot of different tests I end up creating and figured if I just had a HashMap or a switch to map the user friendly name to the class, at some point I'd leave one out. So tried to work out a way where I would just create a class that implements StatsTest and Java would magically know which subtype to instantiate without my adding any extra code that mentioned that exact class by name, beyond a name in a config file. The magic part is not working out so well.

I'm not quite happy with my HashMap, as the way I did it, there is exactly one instance of each test subtype. That is not a limitation of the enum I created. But I believe I should just create a TestFactory and accept the idea that having to maintain it to include any new subtypes I create is smarter than trying to be too clever.

These discussions are helping me sort things out, so thanks much.
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I was thinking about what you were saying about interfaces. I could, in theory have an interface StatsTests that defines run and report. I could have the constructor take an argument defined by a StatsTestsParameters interface. For each different run algorithm, I could implement the StatsTest interface. If the parameters were common to all the tests, I could implement the StatsTestsParameters interface as a enum TestType. Then I could do something like this:

Which seems very clean, but it implies:

are both valid, which is not the case for my immediate problem, which is clearly why you asked the questions you did. So I think the direction of extending FieldTest and BenchTest is more appropriate right now. But I need to keep the above in mind for the future. I'll post my attempt at TestFactory once I think about that a bit more.
 
Winston Gutkowski
Bartender
Posts: 10575
66
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Jon Swanson wrote:I was thinking about what you were saying about interfaces. I could, in theory have an interface StatsTests that defines run and report. I could have the constructor take an argument defined by a StatsTestsParameters interface. For each different run algorithm, I could implement the StatsTest interface. If the parameters were common to all the tests, I could implement the StatsTestsParameters interface as a enum TestType.

NOW you're starting to think like an OO programmer.

However, I'm not quite convinced about setting up parameters as an interface - although there's nothing wrong with it in theory. Surely these could just be defined by the constructors of each concrete test class? If there are values that are common to ALL tests, then these could be passed to a skeleton parent class as I described above. However, many of these decisions will depend on exactly what information is supplied by your file.

For example: does the file state whether the FieldTest should be "slow" or "quick" or is this something that is calculated from the other information you supply?

Also: it would seem that these distinctions only apply to your FieldTest variant, so my advice would be to make that enum a static nested member of your FieldTest class.

Another possibility might be to look at the Builder pattern. This can be very useful when you have a number of possible values that can be supplied to a constructor, with each one (or most) having a "default". The classic example of this is a Pizza class, which can have a variety of combinations of toppings. Instead of overloading constructors with all sorts of combos, you create a Builder class, add the toppings you want, and then use that to create your Pizza.

Could you give us an example of what your file looks like? Maybe a few records showing what info it contains for each type of test? Otherwise it's hard to give specific advice.

However, from what I see, you're definitely on the right lines; it's just a case of refinement and - I suspect - not overengineering (and don't worry, I do it all the time too ).

Winston
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks again. I will put together some examples and I am actually still thinking about the factory pattern. I've got to spend the rest of the week on non-programming stuff. So don't think I am ignoring your request, I just need to tackle some deadlines on other stuff.
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Haven't quite got back to putting an example together, hopefully tomorrow.

In the meantime, I believe this is what was meant by using a factory pattern for my problem?


and if I were to use reflection, I think the code below is the limit to which it would make sense here?


the only change I made in main for the second example is that instead of using arbitrary strings, the strings are the exact names of the classes. I actually like that implementation in the sense that if I make another class that extends FieldTest, I don't need to change any code in the TestTypesFactory. But would be happy to hear about problems. One I guess would be the compiler won't be able to tell me that I am trying to instantiate a class that does not exist, it will fail at run time.
 
Maxim Karvonen
Ranch Hand
Posts: 121
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yes, this is a factory.

I can identify following problems with reflection (your asked for them):
1. Yes, compiler can't check if class exists and has proper acces at a compile time. And not only a compiler. Some tools may find implementation "unused" and produce warnings (or even remove from a jar file if configured improperly).
2. Compiler can't check constructor existence and it's access modifiers.
3. If you use classloaders in a wrong way, may find "strange" exceptions like "StatsTest cannot be cast to StatsTests" in some cases. However, if you need to use custom classloader instances, you usually cannot go without reflection anyway. You do not use them yet, so it's ok.
4. Some tools may skip your config files during refactorings (such as class renaming or moving class from one package to another). And if your ship your application to clients they will be forced to update their configs. You may create a map from a "simple name" to a "qualified class name" and use that map in your factory. It won't help in identifying "broken lines" in your "factory config" but clients' life will be a bit easier.
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks Maxim,

I find the discussion interesting. I can have a set of common names that are mapped in the factory to the classes that are to be instantiated, requiring the factory class to be updated when a new class is added or use reflection and add the concerns you and Winston have laid out for me. I believe I will not try reflection at this time.

As promised to Winston, if I were to have a StatsTests interface and the abstract classes FieldTest and BenchTest, the concrete FieldTests would have the following variability.

and the concrete BenchTests would have the following variability

So I was thinking I would simple create concrete classes representing the permutations that I will need. There would be about six FieldTests and four BenchTests. The number will increase over time. If later I need a third type of StatsTest, it will have different variable components.

I did consider the idea of making FieldTest and BenchTest concrete and passing in the variables. That seemed harder to maintain, but would be happy to hear arguments for/against this latter approach.
 
Ulf Lindqvist
Ranch Hand
Posts: 36
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Jon Swanson wrote:I'm trying to sort out the best way to handle a situation where I will want to run some subset of operations, where the subset is likely to come from a file. The operations are related, so I was thinking I would define them from a common interface. Then I need to match up the strings in the file to the right implementation of the interface.


Don't overdesign.

Just read the strings, associate them with operations and perform the operations.

It's Occam's razor. Simple is best.

Don't think in Java. Java is just a language. Think in program design.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!