1

I have a Java class that has some private variable that I don't intend to create setters and getters for; I want these variables to remain inaccessible. But there is one class that needs access to these variables. This class is a visitor in a different package (and I'd prefer to keep it in a different package). Is it bad practice to allow this class to provide the visitor with Consumers and Suppliers, that act as setters and getters, so that the visitor could read and modify these variables? If yes, please state the reasons.

Example:

A.java

public class A {
    private int x;
    private Consumer<Integer> setter;
    private Supplier<Integer> getter;
    public A(int v) {
        x = v;
        setter = new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                x = t;
            }
        };

        getter = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return x;
            }
        };
    }

    public void accept(SomeVisitor visitor) {
        visitor.setSetter(setter);
        visitor.setGetter(getter);
        visitor.visit(this);
    }
}

SomeVisitor.java

public class SomeVisitor extends ParentVisitor {
    private Consumer<Integer> setter;
    private Supplier<Integer> getter;

    public SomeVisitor() {
        setter = null;
        getter = null;
    }

    public void setSetter(Consumer<Integer> setter) {
        this.setter = setter;
    }

    public void setGetter(Supplier<Integer> getter) {
        this.getter = getter;
    }

    @Override
    public void visit(A a) {
        // Code that will, possibly, read and modify A.x
        ...
    }
}

This way the variable A.x remains inaccessible to every class except the visitor.


More Details:

I have some classes that will make use of the visitors. These classes have private variables that are dependent on one another. If these variables had setters, inconsistencies could arise as users change these variables, that should be dependent on one another, without respecting these dependecies.

Some of these variables will have getters, others won't as they will only be used internally and shouldn't be accessed elsewhere. The reason the visitors are an exception and should get read/write access to these variables is that the functionality the visitors are intended to implement were meant to be implemented within methods in these classes. But I thought it will be cleaner if I used visitors. And these functionalities do need read/write access to these variables.

The intention behind this approach was to emulate the friend feature in C++. I could place the visitors within the same package as these classes (which I would do if I didn't find a neat solution to this problem); But I think the package will look messy if it had the visitors as well (and there will be many visitors).


The functionality the visitors will implement will also have something to do with these classes relations to one another.

Sameh Hany
  • 113
  • 1
  • 4
  • Wouldn't it be easier to just have a normal setter and getter? – assylias Nov 11 '16 at 22:08
  • It'd be _much_ easier to just have a normal setter and getter. – Louis Wasserman Nov 11 '16 at 22:29
  • The description sounds like there may be a reasonable intention behind this, but I think the question lacks some details that are necessary for a decision. The first point that came to my mind: Couldn't you solve this my making the respective setter/getter **package private** (i.e. use no modifier), and place the visitor into the same package? – Marco13 Nov 11 '16 at 23:43
  • @assylias It would be, but I need the private variables to be inaccessible elsewhere. – Sameh Hany Nov 12 '16 at 00:28
  • @Marco13 I'd prefer to keep the visitors in a separate package; but if I couldn't find a neat way around this problem, I will place the visitors in the same package and do as you just said. – Sameh Hany Nov 12 '16 at 00:29
  • @SamehHany I have updated my answer, but think that there is no "silver bullet" for what you want to achieve. – Marco13 Nov 12 '16 at 18:56

2 Answers2

1

I tried to squeeze it into a comment, as it technically does not answer the question about whether this is a "Bad Practice™", but this term is hard to define, and thus, it is nearly impossible to give an answer anyhow...

This eventually seems to boil down to the question of how to Make java methods visible to only specific classes (and there are similar questions). The getter/setter should only be available to one particular class - namely, to the visitor.

You used very generic names and descriptions in the question, and it's hard to say whether this makes sense in general.

But some points to consider:

  • One could argue that this defeats the encapsulation in general. Everybody could write such a visitor and obtain access to the get/set methods. And even though this would be a ridiculous hack: If people want to achieve a goal, they will do things like that! (sketeched in Appendix 1 below)

  • More generally, one could argue: Why is only the visitor allowed to access the setter/getter, and other classes are not?

  • One convincing reason to hide getter/setter methods behind Supplier/Consumer instances could be related to visibility and the specificness of classes (elaborated in Appendix 2). But since the visitor always has the dependency to the visited class, this is not directly applicable here.

  • One could argue that the approach is more error prone. Imagine the case that either the setter or the getter are null, or that they belong to different instances. Debugging this could be awfully hard.

  • As seen in the comments and other answer: One could argue that the proposed approach only complicates things, and "hides" the fact that these are actually setter/getter methods. I wouldn't go so far to say that having setter/getter methods in general already is a problem. But your approach is now to have setter-setters and getter-setters in a visitor. This extends the state space of the visitor in a way that is hard to wrap the head around.

To summarize:

Despite the arguments mentioned above, I would not call it a "bad practice" - also because it is not a common practice at all, but a very specific solution approach. There may be reasons and arguments to do this, but as long as you don't provide more details, it's hard to say whether this is true in your particular case, or whether there are more elegant solutions.


Update

For the added details: You said that

inconsistencies could arise as users change these variables

It is usually the responsibility of a class to manage its own state space in a way that makes sure that it is always "consistent". And, in some sense, this is the main purpose of having classes and encapsulation in the first place. One of the reasons of why getters+setters are sometimes considered as "evil" is not only the mutability (that should usually be minimized). But also because people tend to expose properties of a class with getters+setters, without thinking about a proper abstraction.

So specifically: If you have two variables x and y that depend on one another, then the class should simply not have methods

public void setX(int x) { ... }
public void setY(int y) { ... }

Instead, there should (at best, and roughly) be one method like

public void setState(int x, int y) { 
    if (inconsistent(x,y)) throw new IllegalArgumentException("...");
    ...
}

that makes sure that the state is always consistent.


I don't think that there is a way of cleanly emulating a C++ friend function. The Consumer/Supplier approach that you suggested may be reasonable as a workaround. Some (not all) of the problems that it may cause could be avoided with a slightly different approach:

The package org.example contains your main class

class A {
    private int v;
    private int w;

    public void accept(SomeVisitor visitor) {
        // See below...
    }        
}

And the package org.example also contains an interface. This interface exposes the internal state of A with getter+setter methods:

public interface InnerA {
    void setV(int v);
    int getV();
    void setW(int w);
    int getW();
}

But note that the main class does not implement this interface!

Now, the visitors could reside in a different packakge, like org.example.visitors. And the visitor could have a dedicated method for visiting the InnerA object:

public class SomeVisitor extends ParentVisitor {

    @Override
    public void visit(A a) {
        ...
    }

    @Override
    public void visit(InnerA a) {
        // Code that will, possibly, read and modify A.x
        ...
    }

The implementation of the accept method in A could then do the following:

    public void accept(SomeVisitor visitor) {

        visitor.accept(this);

        visitor.accept(new InnerA() {
            @Override 
            public void setX(int theX) {
                x = theX;
            }
            @Override 
            public int getX() {
                return x;
            }
            // Same for y....
        });
    }        

So the class would dedicatedly pass a newly created InnerA instance to the visitor. This InnerA would only exist for the time of visiting, and would only be used for modifying the specific instance that created it.

An in-between solution could be to not define this interface, but introduce methods like

@Override
public void visit(Consumer<Integer> setter, Supplier<Integer> getter) {
    ...
}

or

@Override
public void visit(A a, Consumer<Integer> setter, Supplier<Integer> getter) {
    ...
}

One would have to analyze this further depending on the real application case.

But again: None of these approaches will circumvent the general problem that when you provide access to someone outside of your package, then you will provide access to everyone outside of your package....


Appendix 1: A class that is an A, but with public getter/setter methods. Goodbye, encapsulation:

class AccessibleA extends A {
    private Consumer<Integer> setter;
    ...
    AccessibleA() {
        EvilVisitor e = new EvilVisitor();
        e.accept(this);
    } 
    void setSetter(Consumer<Integer> setter) { this.setter = setter; }
    ...
    // Here's our public setter now:
    void setValue(int i) { setter.accept(i); }
}

class EvilVisitor {
    private AccessibleA accessibleA;
    ...
    public void setSetter(Consumer<Integer> setter) {
        accessibleA.setSetter(setter);
    }
    ...
}

Appendix 2:

Imagine you had a class like this

class Manipulator {
    private A a;
    Manipulator(A a) {
        this.a = a;
    }
    void manipulate() {
        int value = a.getValue();
        a.setValue(value + 42);
    }
}

And now imagine that you wanted to remove the compile-time dependency of this class to the class A. Then you could change it to not accept an instance of A in the constructor, but a Supplier/Consumer pair instead. But for a visitor, this does not make sense.

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
-1

As getters and setters are evil anyway, you'll be better off making things not more complicated than ordinary getters and setters.

Christine
  • 5,617
  • 4
  • 38
  • 61