I'm in the beginning of writing a simple library, primarily written in Java, with the intention of open sourcing it when it is in a reasonably functional state. As I began work on this project, I decided to try out a new way of designing my classes.
Ruby is my favorite programming language, and one of my favorite aspects of the language is that anything goes. Any object is extensible, even its private methods. Not even the built in classes are safe from extension. This is what's called giving you enough rope to hang yourself. Sure, you could hang yourself with this much power... but you could also use that rope to do some pretty cool things.
Nothing irks me more than non-extensible code. Many times I have run across Java code in the wild that either made a method final that I felt the need to override, or didn't expose a critical bit of API that prevented me from extending it in a key way I needed. It is fine if it is code I can change... I can just make something more open and extend as needed. The problem lies in third party libraries that I don't want to maintain private patches for. This, combined with my love of Ruby's openness, has led me to the goal of making all my Java classes as extensible as possible. The final keyword will only be used in dire circumstances (aka likely never), or to fake a closure in an anonymous class. The private keyword will be reserved only for fields and methods of no consequence.
Before you prepare to write a nasty comment about how ignorant and stupid this design will be, let me jump the gun and tell you why I think it is a bad idea. Either the API becomes locked into potentially poor choices, or the API changes underneath the feet of users of my code. The former is a serious problem if I want to have the freedom to evolve my code, so I opt for the latter. I see exposing a bunch of protected methods as a way to let the users of my code extend my classes in ways I didn't anticipate, yet also imply that it is fair game to change as I see fit. My goal would be to keep public methods as stable as possible, but even those I would be willing to shift around in the name of a better design (be it for code clarity, performance, or whatever).
Granted, this exposes implementation details that aren't really necessary for the client code. However, if someone abuses the details enough to cause themselves pain... I feel the pain is necessary to teach them a lesson. After all, we grow by making mistakes.
The benefit is code that can be essentially patched without needing to modify the source directly, and the ability to use the code in whatever way the client author can imagine. I'm always in favor of more freedom. If it were really that bad of an idea, I don't think Ruby (and other dynamic languages) would be as successful and popular as they are.
As I begin to try out this design mentality, I have run across some important discoveries immediately. It is very important to not depend on fields, because if you do, you limit the ways your code can be extended. Consider:
class Person {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
}
The above code is fairly extensible, but what if I wanted to change how getFirstName() works? Maybe I want to prepend a prefix, for example. Regardless, I can override getFirstName() and getLastName(), but toString() will not follow suit to the newly defined behavior in the subclass. Instead, I should have written Person as follows:
class Person {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return getFirstName() + " " + getLastName();
}
}
I could have also resolved this by making the fields protected, or exposing setters. Exposing fields beyond private still doesn't typically sit well with me in Java land (perhaps that's foolishly old school, but I'm stuck in
some of my ways). Exposing setters seems to just expose complexity that isn't needed. I may not
want my Person to have changeable names. Besides, the ability to redefine a getter by appending, prepending, replacing, or mangling the result is quite different from needing to set the value ahead of time.
The other key aspect I have discovered is to be very cautious when designing your constructors. Constructors in Java pose one of the biggest ways to accidentally lock your code into specific details. For example:
public class Person {
public Person(String username) {
// Connect to a database and load the person
...
}
}
Do you see the problem? Yeah... you have made it so that you cannot construct a Person without connecting to a database and loading a person. Even if you subclass this, you are still stuck with that behavior no matter what. This has a couple possible solutions. You could extract the database connection details like so:
public class Person {
public Person(String username) {
loadFromDatabase(username);
}
protected void loadFromDatabase(String username) {
// Connect to a database and load the person
...
}
}
This will allow a subclass to override loadFromDatabase(String) so that it does nothing. This feels a bit hacky to me, but it works perfectly. The other way around this is to expose an alternative constructor (such as one without parameters). This other constructor could have an empty body, and you could even make it protected if you only want to expose it to give more power to subclasses.
So, I will see how this design works for me, but I already like it. Also, don't take my meaning to say that all methods will be exposed as protected or public. For example, a method that has a very well defined behavior might grow rather large and require some breaking apart. There is no need to make all the pieces protected if they really only make sense as a means of implementing the bigger method. There might be no serious harm in exposing them, but I don't believe in absolute rules either.
There is no One True Way to design. The flaws in this plan might prove more serious than I anticipate, but it doesn't negate that there is some value in allowing free extension. Design is also a bit of a personal adventure, so if this doesn't float your boat, look for alternatives that give you the Happy Feeling.
Any thoughts? Am I destined to fail? Is this something you've tried and enjoy?