How to ensure that a field is properly encapsulated?

Viewed 554

I wonder if there's a simple way to to find all methods accessing a field directly. More precisely:

I'd like to assure that there's exactly one method writing a field and exactly one method reading it. All other accesses should use these two.

Background: When a field gets written, I need to record the fact somewhere I can do this easily using a generated setter, but I'd like to assure that I don't circumvent it somewhere.

It's for mobile rather than server, so I don't want / can't use interfaces or run-time bytecode rewriting...

I know, there's ASM, but AFAIK using it means more work that I'd like to spend. I hope, there's a better way.

Update

I didn't think of it, but have to state that code changes are allowed, but memory is tight. So encapsulating fields (e.g., Java FX style) or making a backup is too bad. There are quite a few fields, so actually anything requiring to touch them all is not good.

I could imagine parsing the sources, which is either complicated or prone to false positives as the same identifier has different meanings depending on the context. It may be even shadowed (e.g., in a nested class declaring an equally-named variable), but then I'd gladly change the code to avoid the problem.

Getting a structured information from the class file would surely be better.

3 Answers

Encapsulation in Java is at the object or class level; the strictest access control modifier is private, but even then, every method within the same class can access the private fields. So, if you want to encapsulate behaviour of fields, this can be achieved by representing the fields as objects.

Here's a class representing a mutable field:

public class MyField<T> {
    private T value;

    MyField(T initialValue) {
        value = initialValue;
    }

    public T get() {
        return value;
    }

    public void set(T newValue) {
        // any logging goes here
        value = newValue;
    }
}

Then if you want this logging behaviour to apply to a field name, declare name as type MyField<String> instead of String:

public class Person {
    private final MyField<String> name;
    private final MyField<Integer> age;

    public Person(String name, int age) {
        this.name = new MyField<>(name);
        this.age = new MyField<>(age);
    }

    public String getName() {
        return name.get();
    }

    public int getAge() {
        return age.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public void setAge(int age) {
        this.age.set(age);
    }
}

Advantages:

  • Since MyField.value is private, it's easy to verify across all instances that its value is never set without the logging behaviour being triggered.
  • The Java compiler's static checks are sufficient for checking that the field's value is only accessed via its get and set methods; there is no need for a separate verification stage.
  • You can apply this to just the fields you want the logging behaviour on.

Disadvantages:

  • Each of those MyField objects has some overhead in memory use.
  • The extra method calls to get and set will have some overhead in running time.
  • The extra code to call .get() is not much, but it could harm readability in more complex expressions.

I could imagine parsing the sources, which is either complicated or prone to false positives as the same identifier has different meanings depending on the context. It may be even shadowed (e.g., in a nested class declaring an equally-named variable), but then I'd gladly change the code to avoid the problem.

That's why modern IDEs exist -- they can analyze your code based on context instead of just grepping sources.

Take IntelliJ IDEA, for example: enter image description here

Usages of field, method class, or other things are one shortcut away: enter image description here Not only in your project, but in dependencies and other linked projects as well!

This is also available in Eclipse-based IDEs and NetBeans, probably others too.

If you don't use any IDE, you can still use linters like Checkstyle. Just mark the field deprecated and ignore warnings in methods where you know usage is permitted, more about ignoring Checkstyle warnings in this answer: https://stackoverflow.com/a/1706844

If i had to check if a property is or not modified by other method than the right setter, I would do like this :

public class Target {

    private int property1;
    private int property1Contoller ;

    public Target() {
    }

    public int getProperty1() {
        return property1;
    }

    public void setProperty1(int property1) {
        this.property1 = property1;
        this.setProperty1Contoller(property1);
    }

    private int getProperty1Contoller() {
        return property1Contoller;
    }

    private void setProperty1Contoller(int property1Contoller) {
        this.property1Contoller = property1Contoller;
    }

    public boolean checkProperty1() {
        return (this.getProperty1() == this.getProperty1Contoller());
    }
}

The idea : insert a shadow field with private setter, and this private setter of the shadow field is only called in the setter of the regular field. Hence, if the regular field is modified outside its setter, then regular field and shadow field will be different.

Related