2009-04-28

Object states, roles, and access modifiers

Johan sometimes says that he thinks objects should have their life cycles explicit. When you first create an object, only some calls are valid (e.g. for a socket object, only calls for setting up how it should connect and the connect method). Later on, more functionality is available (in our case, methods for reading, writing, and closing the socket). This should be explicit. Something like this:

public interface SocketFactory
{
UnconnectedSocket createSocket()
}
public interface UnconnectedSocket
{
void bind(IPEndPoint localAddress);
ConnectedSocket connect(IPEndPoint remote);
}
public interface ConnectedSocket
{
int read(byte[] buf, int offset, int count);
int write(byte[] buf, int offset, int count);
ClosedSocket close();
}
public interface ClosedSocket
{
}
class Socket : UnconnectedSocket, ConnectedSocket, ClosedSocket
{
// ...
}

This is similar to a way of writing three-layer web applications that I've used in the past: the presentation layer requests an obejct from the logic layer, in which it writes the parameters for the query. It then passes it to the logic layer, which can augment the query, cache, etc. If necessary, the query is sent to the data layer along with a response object. The data layer uses the request object to look up some data, and writes it into the response object and returns. The logic layer then augments, caches (etc.) the response, and gives it to the presentation layer. The easy way of doing it it to just have a single class for creating the query (presentation), for reading the query (data), for writing the response (data), and for reading the response (presentation). In some cases, you might need to use different classes. A solution is to have the class in the presentation layer, and let it implement four interfaces for the different ways of using it. Something like:

interface WritableUserRequest
{
void setUsername(String username);
}
interface ReadableUserRequest
{
String getUsername();
}
interface WritableUserResponse
{
void setRealName(String realName);
}
interface ReadableUserResponse
{
String getRealName();
}
class User implements WritableUserRequest, ReadableUserRequest, WritableUserResponse, ReadableUserResponse
{
private String userName;
private String realName;
public void SetUserName(String userName)
{
this.userName=userName
}
// Similar for all getters and setters
}

In essence, this is about having one object let different objects interact with it in different ways at different times. We can group the methods in different little packets and hand some packets to some clients, and others to others. Some methods will be in many packets, some in only one.

Which brings us to the final part of the title: access modifiers. Access modifiers lets us do something like this, but in a very primitive way. We get four different groups of methods, one for everyone, one for only the same class, one for all the subclasses, and the funny, nameless exact-same-package-only group. A more flexible mechanism for grouping methods would save us from all the needless typing that creating interfaces for each method group. Come to think of it, there is another way: annotations. A follow-up with some annotation processing goodness forthcoming, some time after I've written about error handling and published my other little hacks which are the reason Johan got me into writing here in the first place.

5 comments:

  1. Hardly surprising I agree with the gist of the blog post :-)
    I also believe that it points to a design "pattern" for doing web applications that is not that common. I would suggest that the common domain modelling of web applications is orthagonal to good object oriented design.
    The design pattern magnus points out is a better model for doing web applications since the domain modell concerns request/response rather than some business entities like "customer" or some such.
    But I think I'll save that for a separate blog post real soon now...

    ReplyDelete
  2. This is getting off-topic, but yes. I've seen far too many classes in web applications that either only have fields or only have methods.

    I'm not convinced, though, that the solution is to model it differently using object orientation. Maybe the correct tool for web applications really is Ada Server Pages. Looking forward to changing my mind after you've written that post...

    ReplyDelete
  3. Yes you are probably right about ASP being the correct web technology :-)
    I was thinking about the interfaces you describe in terms of annotations.
    So an example could perhaps be:
    @AllowedState("connected")
    public String getResponse()
    So instead of having a "Connected"-interface you could describe the state in annotations.

    Although in this case it will only be of documentation value since the annotation can not be enforced.

    ReplyDelete
  4. With annotation processing, those annotations can be turned into interfaces. Taking it one step further, delegating classes could be generated, which would prevent casting (which is necessary for things like emulating an extending the public/package/protected/private modifiers). It will be interesting to see how far this can be taken in terms of both what can be expressed and how much the syntax can be simplified.

    ReplyDelete
  5. Yeah, almost at the same time as I submitted the comment I thought "time to write another code weaver to actually generate those interfaces". About the delegating; yeah, I got that too, but then again it isn't the first time we write an abstract delegating parent class :-)

    ReplyDelete