• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Tim Cooke
  • Campbell Ritchie
  • paul wheaton
  • Ron McLeod
  • Devaka Cooray
Sheriffs:
  • Jeanne Boyarsky
  • Liutauras Vilda
  • Paul Clapham
Saloon Keepers:
  • Tim Holloway
  • Carey Brown
  • Piet Souris
Bartenders:

Simplifying classes that are very similar

 
Ranch Hand
Posts: 71
3
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have these two classes, Desktop and Laptop. They are practically the same aside from one having an extra instance variable and the toString method having a modified return value. Is there a way to encapsulate these classes?

Desktop:


Laptop:
 
Saloon Keeper
Posts: 28656
211
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Easily. Use inheritance.

Have a base class named Computer. Subclass it as the classes Desktop and Laptop. Put the common resources in Computer and extend/modify them in the subclasses. It's basic OOP.

If you like, later you might also add a Server subclass, as an example.
 
Saloon Keeper
Posts: 11057
88
Eclipse IDE Firefox Browser MySQL Database VI Editor Java Windows ChatGPT
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
"storageType" is not a very good descriptive name. Would be better as "isStorageTypeSSD".
 
Carey Brown
Saloon Keeper
Posts: 11057
88
Eclipse IDE Firefox Browser MySQL Database VI Editor Java Windows ChatGPT
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Better yet... seeing as how a large portion of new machines these days contain both SSD and HDD storage allocate a separate variable for each one.

I suggest getting rid of the constructor that takes no arguments. If you use it you'll end up creating an object that is not valid.
 
G Atwal
Ranch Hand
Posts: 71
3
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
This is what I put together, for now to focus on the inheritance aspect I left the storage type as a single variable.

Computer:


Desktop:


Laptop:
 
Tim Holloway
Saloon Keeper
Posts: 28656
211
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Carey Brown wrote:I suggest getting rid of the constructor that takes no arguments. If you use it you'll end up creating an object that is not valid.



Not really. Every one of my servers once met those criteria. Then I installed a CPU, RAM, storage, and a graphics card.

Besides, you can't have a decent POJO without a no-element constructor.
 
Carey Brown
Saloon Keeper
Posts: 11057
88
Eclipse IDE Firefox Browser MySQL Database VI Editor Java Windows ChatGPT
  • Likes 2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
With String.format() you can only use %s for String variables. Use %d for int, Integer, long, and Long. Use %f for float, double, Float, and Double.

"ghz" should be  "GHz".

I would make the Computer's toString() method return a String with the basic specs. Then you can have the Laptop's toString()
return String.format(  "%f\" Laptop with %s", screenSize, super.toString() );
 
Marshal
Posts: 80634
471
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:. . . Besides, you can't have a decent POJO without a no-element constructor.

Please explain. I agree with Carey.
 
Tim Holloway
Saloon Keeper
Posts: 28656
211
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Campbell Ritchie wrote:

Tim Holloway wrote:. . . Besides, you can't have a decent POJO without a no-element constructor.

Please explain. I agree with Carey.



As it happens, I got a very real-world reinforcement of why no-argument constructors are desirable just this past week. More in a minute.

First (and somewhat related) has to do with Inversion of Control (IoC) frameworks.

In a typical IoC system, you often have an inventory of instantiable classes and a factory/warehouse to dispense instances. So generally, the factory is going to have to construct instances.

If all your classes have no-element constructors, you have a virtually zero-knowledge system. Need an instance? Get the classname and do a "newInstance()" on it. Or actually, use Constructor, since newInstance() is now deprecated.

If your constructors have arguments, you are going to need more knowledge. You are going to need to know the number and types of the constructor arguments and sources for the values you'll feed it.

That's one of the selling points of the Spring Framework, incidentally. That you can setup bean definition files and manually wire source values to constructors. On the other hand, note the word "manually". If your classes are all no-element constructors, you can scan directories and/or annotations to build up the bean list. On the other hand, if you need constructor arguments, you're going to have to handle that the hard way.

Note that this is different to injection. Injection mechanisms add data to an instance AFTER the constructor has terminated - although before normal application code gets a crack at it. So injection is a favoured system for frameworks like CDI and the JavaServer Faces bean management system (which has been supplanted by CDI, but is still around). JSF doesn't, in fact, have any support for constructor arguments at all. If a JSF backing bean cannot be constructed by a simple "newInstance()" it's not a usable backing bean.

So that's reason #1. It's not really a "POJO" if it doesn't have a no-element constructor and in fact, I think that the technical definition of POJO does in fact REQUIRE either a no-element constructor or implicit construction (which is effectively the same thing).

There's another situation where no-element constructors are important. Object Relational Management (ORM) systems. Again, we need to be able to construct an object without knowing where/what construction values are. When an ORM goes to fetch a record from a database, having a blank record that it can simply pour all the column field values in is much more preferable to having to take some of them and feed them to a constructor and some populated directly. I could expand on this, but I think you get the point.

And then there's my own personal experience. Very recently I ran into a situation where a LOT of objects needed to be constructed. Even better, it was an interface-based system where an entirely different situation (and class) was required for the testing environment versus the production environment. Since the objects in question did NOT have no-element constructors, I ended up having to introspect each and every class definition for the objects in question so that I could do a "newInstance-with-arguments". You saw some of the echoes of my pain in the form of pleas for help on the Ranch.

Mind you, what I did was extremely crude. Thanks to things like subclassing and interfaces, I couldn't simply throw a list of values at the class definition and get what I wanted. I had to scan the entire list of available constructors and match the values supplied to the constructor argument lists to see which constructor to invoke. And I was definitely NOT in the mode to apply full disambiguation rules as laid down in the JLS. So it was crude and brutal and wouldn't have worked if the environment had been much more complex.

So there you have 3 arguments in favour of no-argument default constructors.
 
Ranch Hand
Posts: 271
15
Android Angular Framework Spring AngularJS Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I would like to offer my two cents on the no-args constructor.  These have been disdained in recent years because of the issues pointed out recently of making invalid instances.  They would also tend  to make instances that are not thread safe.  That's partly because the creational thread could be part-way through completing the instance' data when the instance is used by another thread, but also because you can never be sure if the completed instance might get changed by one thread while another is using it.

One alternative offered is an "all-args" constructor, which I think you mentioned.  That's a term used in the Lombok framework.  That is fine, but at a certain point you can get an unwieldy number of arguments, or you can end up with multiple "many-arg" constructors (as the OP mentioned).  There is an excellent discussion of this in Bloch's "Effective Java".   What Bloch suggests is that above a certain number (4?), you employ a Builder pattern.  That one has a builder supplying the created object (which can have an all-args constructor with appropriate defaulting for variations you might run into).  This builder is responsible for having setters (which don't have to be called set*) for any parameter you might want, as well as doing validation before returning your completed object from a "build()" method at the end.  This is usually done with a fluent coding style.  He also mentions the use of static factory methods, which can return an instance of an interface, so that for things like testing (maybe) you could return a different implementation.  Under other circumstances, you could return the same object multiple times, etc.

That said, if you are going to be making objects with some kind of automated tooling (you mentioned introspection and newInstance) doing the classic "bean" pattern with no-args + many setters might be just fine.  If you setup packages just right, you might even be able to avoid having anything with access for changing it once it has been populated.  Bean pattern is how many of the older frameworks do it.  It is what Java Beans were initially intended for.

This part the OP mentioned about

I couldn't simply throw a list of values at the class definition and get what I wanted. I had to scan the entire list of available constructors and match the values supplied to the constructor argument lists to see which constructor to invoke.


is something Bloch also describes in his book.  It is what I alluded to above--variations in the arguments needed to make a complete object.  His solution was multiple different factory methods with names like "of(arg1,arg2)" or "of(arg3,arg4)", and each of those is responsible for completing and returning a validated object.  Builder could still work for that as well.  Different names for factory methods specific to the situation could be chosen as well.

I hope that helps

 
Tim Holloway
Saloon Keeper
Posts: 28656
211
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
If you'll note, though, the examples I mentioned all had one thing in common. The beans were not constructed in wild-thread application code, they were manufactured by some sort of management facility. And no well-designed management facility is going to decant beans before they have been fully constructed and initialized. The best way for ensuring that is by pairing an injection service into the factory and not simply turn a bean out into a multi-thread environment without regard to how the sharks will attack it.

Also consider that I have not precluded constructors with arguments from the discussion, simply stated that for maximum flexibility there should also be a no-argument constructor. And yes, if it's an issue, a bean should be self-validating in any event. But, as I said, for my particular example, the base computer is, in fact a bare motherboard in a box so the defaults are themselves valid.

Incidentally, I often have several constructors for my ORM Entity beans. The no-args one is essential to the framework, but often I application logic that would just as soon set up (or constuct up) multiple fields in a shot. Call me lazy, but field-by-field setting when you have a known collection of property values to set as a unit has no attraction to me.

I don't believe in an all-elements constructor not only because they can get quite unwieldly, but also for the exact same reason that "SELECT *" is discouraged in application logic. You can break unrelated system components too easily. Whereas a no-args constructor is lowest-common denominator and thus less likely to wreak havoc. Again, expecting beans to be self-validating. And, of course, generous use of unit tests.

Putting expected argument types on a factory is, based on my recent experience, a recipe for trouble. Not only does it complicate the factory interface, but resolving ambiguities is a serious problem. I once was writing a C++ compiler and disambiguating C++ at the time was, as far as I could tell, not a determinative process. You could score relative matches, but there was no mechanism to avoid getting tied scores for different overloads and I didn't like the "pick one and go with it" approach so I let the project drop. Java, thankfully, won't permit that as static characteristics go, but based on the code I just worked with, I'm not so sanguine about dynamic disambiguation. At least without making a much more complex mechanism than I wanted to spend time on.
reply
    Bookmark Topic Watch Topic
  • New Topic