Coding Standards and Breaking the Rules - Part 2, Beans
Computer independance implies an interface such as that defined for Corba or COM/DCOM. This means methods only, not direct data access. If a calling class asks for data it is a method call that could be serviced locally or sent down the wire to another computer.
The bean provides the same facilities by convention, not by language restriction. The convention requires that data always be private and accessed by getter and setter methods. Groovy, a Java spinnoff that creates Java class files generates getter and setter calls whenever the data is accessed.
Originally beans were created for reusable components - often SWING GUI - such as buttons. Later the concept was extended to server code and the EJB (Enterprise JavaBean).
The militant took the concept of the bean and made it law:
- Thou shalt not allow outside access to data. Always make it private.
- Thou shalt always access data with getters and setters. No exceptions, heathen!
- Thou shalt burn in oil anyone who does not follow rules one and two at all times. Toss in some fries will you? I do so love those.
The claimed benefits are almost universally believed today, and yet hold less water than the average funnel.
- Making data public is a security violation. Anyone could change it. Yep, that's true. Then why does everyone add setters to all data as a matter of course? And if you choose not to have public setters, how do you set the fields? You could load them in the constructor, but this quickly becomes unwieldy unless there are 6 or less fields to set. You could make the setters package private, but this is limiting in the wrong way. The C++ concept of 'friend' would be useful here.
- You can provide a consistent interface that will not change with implementation changes. Again there is a kernel of truth. A getter that retrieves data could do so from a field, make a call to a database, or cache data from an external source. Very cool, except that everyone assumes that a getter is retrieving from a field with minimal overhead. I don't know how often I have seen code that calls a getter multiple times in a method rather than calling it once and saving it in a local variable. Yes it's bad practice, but it's commonplace and has some real risks. If the getter is converted to a more complex retrieval (or down a wire as in COM), the calling method that calls it multiple times becomes very inefficient. I prefer a system that separates simple retrievals from the more complicated. See below.
Class Differentiation
I don't treat all classes the same. At a gross level, I divide classes into groups:- Beans. These are classes that interact with other packages, libraries or frameworks that expect that standard bean protocol. Giving credit where it is due - the bean protocol is clear and works well.
- Data Transfer Objects. Commonly called DTOs or DAOs for Data Access Objects. When talking between sub-systems, tiers or layers, it's almost always necessary to pass specific information back and forwards. Because this information is passed between disparate groups they neither want nor need processing internals. Sometimes a single DTO can be used for multiple interfaces. Mostly this will lead to DAO classes that are only partially filled or relevant to the receiving class. I commonly make the DAO a static inner class of the owning logic class and use deep copy routines where possible to ease data transfer.
- Implementation Objects. These are the objects that do the work - be it define business logic or provide validation. For these items, being an object is just a convenience thing - allowing for working data encapsulation.
- Object Objects. Early OO design documention talked about object that represented a car, with object that extend that for a type of car. In the 'real' world of appplication development, it is not all that common to create objects that truly represent something.
How I Break the Rules
- Beans. I don't, obviously. A bean has a rigid interface definition that must be adhered to if it's to work with outside components. I would go so far as to say that when I use beans, they are only beans. They're an interface object that does just that and only that - interface. Beans should use implementation and object objects to do the 'real' work. A clear separation of functionality is a clear and very useful architectural principle to apply. It would be acceptable for the bean to consolidate information and implementation from various parts of the system to provide the results required. A bean that responds to the press of a GUI button may validate and then act.
- Data Transfer Objects. The clear architectural imperative to separate functionality applies double to DTOs. This is because you may want to send a DTO down the wire or write it to a file or database. To help this, minimise the use of sub-objects and make sure they are also DTOs. If they are to be persisted or used remotely, make very sure they do not contain time and space sensitive data such as handles or security packets. One of the funniest I've seen was a system that kept a reference to a session in a DTO send to a JMS queue. Everything was fine until the system was restarted with items still on the queue. Then, kaboom. Back to breaking the rules... For me, a pure DTO has no methods whatsoever - just public data. I can hear the pundits screaming from here! But what benefit getters and setters? If you have both then you might as well have public fields. If you only provide getters and set in the constructor, then this is the same as using final public fields.
- Implementation Objects. In simpler cases the are often just a collection of static methods, such as the library Collections class. Often an implementation object is more complex where the client needs to call multiple methods on common data to produce the correct results. In these cases a new instance is created - but only for convenience. It provides a container for working information. If you accept the definition of an Implementation Object, then I do not break the rules. Methods are an action and often return a reference to the object so that calls can be chained:
new ManufactureProcess() .rawMaterial( m).cut( c) .bend( b).paint( colour)
More often they will be separate statements to account for optional operations. - Object Objects. Remember your early training in OO. I treat data storage and retrieval the same for Implementation and Object objects. Firstly I make it private when I can, package private when it is only needed in that sphere and only public when I must.This isn't for security reasons - it just makes the class easy to use. Private and package private are implementation artifacts. When writing a client you need not have them cluttering the documentation nor feel you can use them inappropriately. So, what happens when setting data is a little more complex than copying its reference? It may be cached or it may require unit conversion. I'll make the field private and create a setter and getter, but using the same name as the field without prepending it with 'set'. This way I know that it's not a bean and that working with this field takes work. Oh dear, I can hear those pundits screaming again. What, they say, now every client that uses my class will need to be changed. That is exactly what I want. The writer of the client will have to investigate how the change effects their code. It all depends on the data lifetime that the client requires. They may be able to use the getter to retrieve one copy in the running of the program. More likely they will take a local reference and a single call outside all loops and passes. But, only the client writer can be sure of the best valid lifetime. It may be that they do have to retrieve it directly every time because the returned result is just that volatile. By using the same name for the data and the getter and setter methods, the changes made to the client are minimal and obvious.
private int idleTimeInSeconds = -1;
public int idleTimeInSeconds()
{
if (idleTimeInSeconds < 0)
idleTimeInSeconds(
Properties.get( "idleTimeInSeconds"));
return idleTimeInSeconds;
}
public void idleTimeInSeconds( int newIdleTime)
{
if (newIdleTime <= 0 || newIdleTime > 1000)
throw new Exception( "Oops"):
idleTimeInSeconds = newIdleTime;
}









<< Home