(skip ahead to download here)
This blog’s thusfar most popular post, The Getter Setter Debate, spawned this comment from Wolter:
“Just put them in the same package and set them to package-private.
Or put warnings in the API documentation that the getters/setters are for internal use only, and are likely to change.”
While this is a viable solution, I strive for maximal generality. Granted, Java and .NET support the concept of package or “friend” access. Even so, this is language and/or platform dependent, and it can force some awkward package organization in some situations.
Since the thought-provoking idea of avoiding getters occurred to me, I’ve come to the following conclusion: yes, they should be avoided as much as possible, but no, you can’t avoid them completely. They are needed for legitimate uses, such as UI mapping and persistence.
And now, the natural question arises: if you are on a large team, how do you enforce this policy?
There is the possibility of manual intervention. Indeed, many teams employ code reviews and walkthroughs, and every instance of an accessor method being used can be scrutinized. A small example of domain-driven design called TimeAndMoney takes this approach in the file Money.java:
/**
* How best to handle access to the internals? It is needed for
* database mapping, UI presentation, and perhaps a few other
* uses. Yet giving public access invites people to do the
* real work of the Money object elsewhere.
* Here is an experimental approach, giving access with a
* warning label of sorts. Let us know how you like it.
*/
public BigDecimal breachEncapsulationOfAmount() {
return amount;
}
public Currency breachEncapsulationOfCurrency() {
return currency;
}
|
Certainly, as the author of this class, you are discouraging other developers from using a method in their code with the substring “breachEncapsulation”. And giving the power to the class author is a step in the right direction.
Approach
But you know me. I’m never happy with anything. I’m especially not content with relying on others to behave. It rarely works.
This got me thinking of a pattern-based solution, one where only certain classes are authorized to use another class’s getters and setters, and violation of such would cause run-time problems like exceptions.
I began tinkering around with this approach and came up with the Method Regulator Pattern:

The heart of this setup is the Regulator. It keeps track of a type that is being regulated and a list of types that are authorized to use regulated methods of that type.
What is a regulated method? It is up to the class that wants to be regulated to, first off, inherit from the Regulated Object class, and second, call the inherited authorizeCaller method as the first statement in every method that should be regulated.
The Regulated Object base class uses the process-global Regulated Types class to look up all of the Regulators for the class that called authorizeCaller, including those inherited from base classes and interfaces. Then, for each Regulator, if at least one of them authorizes the method call, the authorization succeeds. Otherwise, indicate failure somehow, like with an exception.
The burning question remains: how does authorizeCaller know the type of the caller? This will depend on your platform, but your platform must have some kind of reflective capability. Both Java and .NET support the ability to query the activation stack: getStackTrace in Java and StackTrace in .NET.
Example
I would never be one to just describe something very abstractly, wave my hands at it, and declare that it works. What am I, a professor? I went ahead and implemented this pattern in C#. You can download it here. Included is a set of NUnit tests.
Another burning question: what prevents some mischievous or misinformed coder from highjacking an instance of a Regulator, twiddling with it to allow access to a specific type to their new class, and rendering this scheme impotent?
This is an implementation issue, and I chose to deal with it by providing methods to close or “lock” a regulator once we’re finished authorizing types. I also do the same for trying to replace an entire Regulator for a given type with a new Regulator.
Some of you are confused. I’m confused as well. I think an example will clear up what is going on.
Suppose you have the following relatively complicated class hierarchy of five classes, each of which has some getters that you’d like to regulate:

Attached to each class in that diagram is a note indicating which classes are allowed to call getters on each class. It is important to note that authorized callers in a base class should be inherited by a subclass to preserve the Liskov Substitution Principle.
Speaking of which, suppose there are five calling classes like so:

Therefore, the classes authorized to use the accessors of class Sub1 are Caller2, Caller1, and Caller5, the latter two inherited. Also, since Caller3 and Caller4 implement Interface1, we could just as easily authorize Interface1 for Sub3 instead of the two classes individually.
Here is some code for setting up who can call accessors on who:
public void Setup()
{
MethodAccessRegulator superReg = new MethodAccessRegulator(typeof(Super));
superReg.addAuthorizedType(typeof(Caller1));
superReg.closeRegulator();
RegulatedTypes.addRegulatedType(superReg);
MethodAccessRegulator sub1Reg = new MethodAccessRegulator(typeof(Sub1));
sub1Reg.addAuthorizedType(typeof(Caller2));
sub1Reg.closeRegulator();
RegulatedTypes.addRegulatedType(sub1Reg);
MethodAccessRegulator subInterfaceReg = new MethodAccessRegulator(typeof(SubInterface));
subInterfaceReg.addAuthorizedType(typeof(Caller5));
subInterfaceReg.closeRegulator();
RegulatedTypes.addRegulatedType(subInterfaceReg);
MethodAccessRegulator sub2Reg = new MethodAccessRegulator(typeof(Sub2));
sub2Reg.addAuthorizedType(typeof(Caller2));
sub2Reg.closeRegulator();
RegulatedTypes.addRegulatedType(sub2Reg);
MethodAccessRegulator sub3Reg = new MethodAccessRegulator(typeof(Sub3));
sub3Reg.addAuthorizedType(typeof(Caller3));
sub3Reg.addAuthorizedType(typeof(Caller4));
sub3Reg.closeRegulator();
RegulatedTypes.addRegulatedType(sub3Reg);
}
|
Here is some code for Super:
public class Super: MethodAccessRegulatedObject
{
private int _x;
public int x
{
get
{
authorizeCaller(this);
return _x;
}
}
}
|
Boring; only one member to protect, but it gets the point across.
Now if, say, Caller2 is defined in this way:
public class Caller2
{
public void useSub1()
{
int i = (new Sub1()).x;
}
public void useSub2()
{
int i = (new Sub2()).z;
}
// inherited access
public void useSub3()
{
int i = (new Sub3()).x;
}
// should throw exception
public void useSuper()
{
int i = (new Super()).x;
}
}
|
and then we use Caller2 like this:
public void TestSingleInheritance()
{
(new Caller2()).useSub1();
(new Caller2()).useSub2();
try
{
(new Caller2()).useSuper();
Assert.Fail();
}
catch (MethodAuthorizationException ex)
{
Console.WriteLine(ex.Message);
}
}
|
the first two calls will succeed, while the second one will throw a MethodAuthorizationException with message “An object of type Caller2 attempted an unauthorized method call on class Super”.
Catches
- First, we make use of this StackTrace class, and we kind of assume where we are in the stack. This approach, as I have implemented it, breaks if you build the download in Release mode, because calls to InvokeMethodFast are inserted instead of the actual calling class.
- Second, a much better way for client classes to declare their desire to be regulated would be to use Aspect-Oriented Programming. I do not have experience with AOP yet, so this is a future enhancement. I also wanted a lightweight approach for the blog, instead of telling you to download something like NAspect.
- The “locking” aspect mentioned above (all the code in that Setup() function) is only effective if you can, within your organization, centralize the creation and instantiation of the Regulated Types object and the constituent Regulators.
- Finally, all of this has to happen within a single process, though I think if you are distributing your objects, you might have more pressing problems.
- This entire scheme might be rendered impotent by reflection.
I look forward to some feedback on the practicality, usability, and security of this approach.