The final test-related issue is something called The Law of Demeter, "Talk only to your friends." This means that your code should use only those objects that it can see directly: local variables, fields, and method arguments. Code like this:
dog.getBody().getTail().wag();
is considered bad form because it requires dependencies on too many interfaces to get work done. Translating that code into properties:
dog.tail.body.wag();
just obscures the problem.
In the test-driven-development (TDD) world, this sort of chain is called a train wreck. A program that follows The Law of Demeter would restate the chain like this:
dog.expressHappiness();
You'd leave it up to the dog to figure out how to tell you it's happy.
This sort of structure is particularly important in TDD because it lets you bring your mocking architecture under control. If you follow The Law of Demeter, the object that you're testing is always three layered:
- The outer layer (objects that you are passed).
- The object under test.
- The inner layer (objects that you create).
When you build the mocks, you may need to mock classes in layers 1 and 3, but that's it. In the train-wreck example, you'd have to mock every car in the train, which can be a very difficult and time-consuming process.
Mock Roles, Not Objects [PDF] is a great formal paper on this subject. In fact, I'd strongly recommend spending some time perusing http://static.mockobjects.com/. There's a lot of good information there.
Getting Rid of Getters/Setters
So, how to you eliminate the get/set
s? The main key is rethinking the approach to objects. An object should be defined by what it does, not by what it contains. In fact, you shouldn't know what the object contains.
The first step, then, is to stop thinking of an object as a sort of fancy data structure that contains fields and methods that manipulate those fields. Instead, think of an object as something that does work. Money isn't a value and a currency. Rather, it's an abstract object that supports certain operations, such as addition. A dog's job is to express happiness. The way that it does it (by wagging its tail) is unimportant.
These work-centric objects will, of course, have to contain fields in order to do their work, but the outer world shouldn't care about that. The objects that have the information do the work, and if they can't do the work, they delegate to another object by calling one if its methods.
Some information has to pass between objects, of course, but a push (that is, an argument to the target object's method) is usually considered better than a pull (a get
method on another object).
This approach is at the core of OO design and is too big a subject for a single article, but I have illustrated the basic concept. When you follow good design principles, the get/set
methods (or the equivalent properties) tend to wither away. They don't creep into the code to begin with because getting something is rarely a bit of work that's worth modeling. Instead, you simply ask the object that has the information to do the work for you. Ken Arnold put it as "ask for help, not for information." The formal term for this sort of organization id delegation.
Conclusion
So, to put things succinctly. Properties are rarely necessary, and in the worst case, they make your classes hard to extend and hard to debug. They make client code much larger and more complex than it needs to be. That's not to say that there's never a valid application for properties, but they should be used judiciously, knowing what the downside is. The way that you go about eliminating the use of properties is by following well-established design practices, looking at your objects as black boxes that do work, not as data structures. I hope that's not stunning.
Allen Holub is a speaker, lecturer, and writer on software development. His recent books have discussed Java, multithreading, and the use of patterns. He is a frequent contributor to Dr. Dobb's.