7

In the spirit of well designed OO, a certain class I am extending has marked one of its fields protected. This class has also generously provided a public setter, yet no getter.

I am extending this class with a base class that is in turn extended by several children. How can I restrict access to the protected variable from my children while still being able to manipulate it privately and set it publicly?

See example below:

public abstract class ThirdPartyClass {
  protected Map propertyMap;

  public void setPropertyMap(Map propertyMap){
    this.propertyMap= propertyMap;
  }

  // Other methods that use propertyMap.
}

public abstract class MyBaseClass extends ThirdPartyClass{
// Accessor methods for entries in propertyMap.
  public getFoo(){
    propertyMap.get("Foo");
  }

  public getBar(){
    propertyMap.get("Bar");
  }

 // etc...
}

public class OneOfManyChildren extends MyBaseClass {
// Should only access propertyMap via methods in MyBaseClass.
}

I have already found that I can revoke access by making the field private final in MyBaseClass. However that also hinders using the setter provided by the super class.

I am able to circumvent that limitation with the "cleverness" below yet it also results in maintaining two copies of the same map as well as an O(n) operation to copy over every element.

public abstract class MyBaseClass extends ThirdPartyClass{

  private final Map propertyMap = new HashMap(); // Revokes access for children.

  /** Sets parent & grandparent maps. */
  @Override
  public final void setPropertyMap(Map propertyMap){
    super.setPropertyMap(propertyMap);
    this.propertyMap.clear();
    this.propertyMap.putAll(propertyMap);
  }
}

Are there any better ways of accomplishing this?

Note: This is only one example of the real question: How to restrict access to protected fields without maintaining multiple copies?

Note: I also know that if the field were made private in the first place with a protected accessor, this would be a non-issue. Sadly I have no control over that.

Note: IS-A relatonship (inheritance) required.

Note: This could easily apply to any Collection, DTO, or complex object.

Metaphor for those misunderstanding the question:

This is akin to a grandparent having a cookie jar that they leave accessible to all family members and anyone else in their house (protected). A parent, with young children, enters the house and, for reasons of their own, wishes to prevent their children from digging into the cookie jar ad nauseam. Instead, the child should ask the parent for a chocolate chip cookie and see it magically appear; likewise for a sugar cookie or Oreo. They need never know that the cookies are all stored in the same jar or if there even is a jar (black box). This could be easily accomplished if the jar belonged to the parent, if the grandparent could be convinced to put away the cookies, or if the grandparents themselves did not need access. Short of creating and maintaining two identical jars, how can access be restricted for children yet unimpeded for the parent & grandparent?

Wolf
  • 9,679
  • 7
  • 62
  • 108
AnthonyW
  • 1,910
  • 5
  • 25
  • 46
  • For those with an underdeveloped sense of humor: the first paragraph is entirely sarcastic. Per Effective Java, Second edition (p 67) one should "Minimize the accessibility of classes and members". – AnthonyW Jan 31 '14 at 15:22
  • This can be answered more as a general question about changing visibility than specific to my current use-case. That is to say, please focus more on the question than trying to convince me to use various workarounds outside its scope. Thanks. – AnthonyW Feb 03 '14 at 13:07
  • BTW, you cant hide protected variable, by shadowing it with private final. Protected one is still accessible in children using `((ThirdPartyClass) this).propertyMap.get("");` – Mikhail Feb 04 '14 at 07:21
  • @Mikhail Please read comments below Laf's post. This **is** possible if ThirdPartyClass is in a seperate package. – AnthonyW Feb 04 '14 at 20:39
  • If user has a serious intention to break you API, he can create child class in the same package. As to me, your problem is far-fetched. – Mikhail Feb 05 '14 at 06:41
  • The user could also put it in a java.util package or one of Spring's packages for S&G. Given that the original package is outside the scope of the project (read third party), I find your assertion to be far-fetched. – AnthonyW Feb 05 '14 at 11:22
  • 1
    Why can't you make ThirdPartyClass as member of MyBaseClass? Composition instead of inheritance. – Parvez Feb 09 '14 at 10:49
  • That is a good reason why composition and delegation is often preferable to inheritance (Item 16) – Roman Vottner Feb 10 '14 at 10:47
  • 1
    What you are describing is perfectly legitimate from an OOP perspective, since `protected` members only constitutes a contract made with a class's *immediate* descendants on behalf of the class itself. The reason that a public member of `foo` creates a binding contract on behalf of `foo`'s derivatives is any derivative of `foo` by be identified by a reference of type `foo`. The `super` of a class directly derived from `foo`, however, will never be a `foo` derivative--it can only be a `foo`. – supercat Feb 11 '14 at 03:32

8 Answers8

6

This might not be possible for you, but if you could derive an interface from ThirdPartyClass and make ThirdPartyClass implement it ?

Then have MyBaseClass act as a decorator by implementing the interface by delegating to a private member ThirdPartyClassImpl.

I.e.

public interface ThirdParty ...


public class ThirdPartyClass implements ThirdParty



public class MyBaseClass implements ThirdParty {

    private ThirdParty decorated = new ThirdPartyClass();



 public class SubclassOne extends MyBaseClass....

etc

Woody
  • 7,578
  • 2
  • 21
  • 25
  • 1
    Surprised this solution did not come up before. Yesterday I did some digging into the code and found that there *is* an interface in place. With a bit of refactoring to method calls to request the interface instead of the impl, I will no longer need an IS-A relationship and a world of doors open up :-) – AnthonyW Feb 05 '14 at 11:19
  • Interface or composition... always better than inheritance for complex solutions (especially in Java not allowing multiple inheritance) – Falco Feb 10 '14 at 13:21
2

Ok, cheating mode on: How about you overwrite de public setter and change the map implementation to a inner class of MyBaseClass. This implementation could throw a exception on all methods of map you dont want your children to access and your MyBaseClass could expose the methods they should use by using an internal method your map implementation... Still has to solve how the ThirdPartyMethod will access those properties, but you could force your code to call a finalizationMethod on your MyBaseClass before use it... I'm just divagating here

EDIT

Like This:

public abstract class MyBaseClass extends ThirdPartyClass{

    private class InnerMapImpl implements Map{
       ... Throw exception for all Map methods you dont want children to use

       private Object internalGet(K key){
           return delegate.get(key);
       }
    }

    public void setPropertyMap(Map propertyMap){
        this.propertyMap= new InnerMapImpl(propertyMap);
    }

    public Object getFoo(){
        return ((InnerMapImpl) propertyMap).internalGet("Foo");
    }


}
Plínio Pantaleão
  • 1,229
  • 8
  • 13
  • This solution would neccessitate two versions of the variable: one for the adults and one with a "child safety lock" for the kids. – AnthonyW Feb 03 '14 at 20:19
  • nop, you can wrap the original map in your inner class implementation. – Plínio Pantaleão Feb 03 '14 at 20:20
  • Hmm.. Interesting implementation. I had not thought to leverage a private getter on an inner class. This serves to circumvent maintaining two maps yet, as you pointed out, it locks out the grandparent. – AnthonyW Feb 03 '14 at 20:57
1

Sadly, there's nothing you can do. If this field is protected, it is either a conscious design decision (a bad one IMO), or a mistake. Either way, there's nothing you can do now about it, as you cannot reduce the accessibility of a field.

I have already found that I can revoke access by making the field private final in MyBaseClass.

This isn't exactly true. What you are doing is called variable hiding. Since you are using the same variable name in your subclass, references to the propertyMap variable now point to your private variable in MyBaseClass. However, you can get around this variable hiding very easily, as shown in the code below:

public class A
    {
    protected String value = "A";

    public String getValue ()
        {
        return value;
        }
    }

public class B extends A
    {
    private String value = "B";
    }

public class C extends B
    {
    public C ()
        {
        // super.value = "C"; --> This isn't allowed, as B.value is private; however the next line works
        ((A)this).value = "C";
        }
    }

public class TestClass
    {
    public static void main (String[] args)
        {
        A a = new A ();
        B b = new B ();
        C c = new C ();

        System.out.println (new A ().getValue ()); // Prints "A"
        System.out.println (new B ().getValue ()); // Prints "A"
        System.out.println (new C ().getValue ()); // Prints "C"
        }
    }

So, there's no way you can "revoke" access to the protected class member in the super class ThirdPartyClass. There aren't a lot of options left to you:

  • If your child class do not need to know about the class hierarchy above MyBaseClass (i.e. they won't refer to ThirdPartyClass at all), and if you don't need them to be subclasses of ThirdPartyClass then you could make MyBaseClass a class which does not extend from ThirdPartyClass. Instead, MyBaseClass would hold an instance of ThirdPartyClass, and delegate all calls to this object. This way you can control which part of ThirdPartyClass's API you really expose to your subclasses.

    public class MyBaseClass
        {
        private ThirdPartyClass myclass = new ThirdPartyClass ();
    
        public void setPropertyMap (Map<?,?> propertyMap)
            {
            myclass.setPropertyMap (propertyMap);
            }
        }
    

    If you need a direct access to the propertyMap member of ThirdPartyClass from MyBaseClass, then you could define a private inner class and use it to access the member:

    public class MyBaseClass
        {
        private MyClass myclass = new MyClass ();
    
        public void setPropertyMap (Map<?,?> propertyMap)
            {
            myclass.setPropertyMap (propertyMap);
            }
    
        private static class MyClass extends ThirdPartyClass
            {
            private Map<?,?> getPropertyMap ()
                {
                return propertyMap;
                }
            }
        }
    
  • If the first solution doesn't apply to your case, then you should document exactly what subclasses of MyBaseClass can do, and what they shouldn't do, and hope they respect the contract described in your documentation.

Laf
  • 7,965
  • 4
  • 37
  • 52
  • Couple notes of distinction. The field in my example has a public SETTER (which really should have been a contructor arg IMHO) yet no public GETTER. While I recognize that it is needed for your test class, it changes the scope of the problem. Without a public accessor method (getter), the ONLY way to access the variable is directly, so "hiding" is sufficient. – AnthonyW Jan 31 '14 at 16:00
  • Further, I just attempted to use your work around by casting `this` and it I got an error stating that the variable is not visible. Make sure you declare `private FINAL value` in class `B`; without the `final`, you can indeed ignore the field in `B`. – AnthonyW Jan 31 '14 at 16:01
  • I also agree that composition would be a lovely solution to this problem if I did not have to utilize ThirdPartyMethod that takes ThirdPartyClass as a parameter. (By specifying third party, I am noting that these things are beyond my control). – AnthonyW Jan 31 '14 at 16:04
  • The code example is just an example of hiding, I agree it doesn't exactly reflect your case. I still think it was a cool example :P As for your problem, since you absolutely need a is-a relationship, then I don't think there's a lot you can do then :( – Laf Jan 31 '14 at 16:08
  • Marking the field in `B` as being final has no impact of the ability of `C` to access the protected field in parent class `A`. I have just tested it with the code I had provided in this answer and it still does the exact same thing. – Laf Jan 31 '14 at 16:12
  • After some further testing I figured out why we are getting different results here. Put your class A in a different package than C and redo your test. `value` will no longer be accessible even with the same inheritance. – AnthonyW Jan 31 '14 at 17:34
1

I am able to circumvent that limitation with the "cleverness" below yet it also results in maintaining two copies of the same map as well as an O(n) operation to copy over every element.

Laf already pointed out, that this solution can easily be circumvented by casting the child classes into the third party class. But if this is ok for you and you just want to hide the protected parent map from your child classes without maintaining two copies of the map, you could try this:

public abstract class MyBaseClass extends ThirdPartyClass{

    private Map privateMap;

    public Object getFoo(){
            return privateMap.get("Foo");
    }

    public Object getBar(){
            return privateMap.get("Bar");
    }

    @Override
    public final void setPropertyMap(Map propertyMap) {
            super.setPropertyMap(this.privateMap =propertyMap);
    }
}

Note also, that it doesn't really matter, if the parents map is protected or not. If one really wants to access this field through a child class, one could always use reflection to access the field:

public class OneOfManyChildren extends MyBaseClass {
    public void clearThePrivateMap() {
        Map propertyMap;
        try {
            Field field =ThirdPartyClass.class.getDeclaredField("privateMap");
            field.setAccessible(true);
            propertyMap = (Map) field.get(this);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
            return;
        }
        propertyMap.clear();
    }
}

So it actually comes down to the question, why you want the field not to be accessible by the child classes:

1) Is it just for convenience, so it is immediately clear how your api should be used? - then it is perhaps fine to simply hide the field from the sub classes.

2) Is it because of security reasons? Then you should definitely search for another solution and use a special SecurityManager that also prohibits accessing private fields through reflection...

That said there is perhaps another design you could try: Instead of extending the third party class, keep a final inner instance of this class and provide public access to the inner class like this:

public abstract class MyBaseClass {
    private Map privateMap;
    private final ThirdPartyClass thirdPartyClass = new ThirdPartyClass(){
        public void setPropertyMap(Map propertyMap) {
            super.setPropertyMap(MyBaseClass.this.privateMap = propertyMap);
        };
    };

    public Object getFoo(){
        return privateMap.get("Foo");
    }

    public Object getBar(){
        return privateMap.get("Bar");
    }

    public void setPropertyMap(Map propertyMap) {
        thirdPartyClass.setPropertyMap(propertyMap);
    }

    public final ThirdPartyClass asThirdPartyClass(){
        return this.thirdPartyClass;
    }

}

Then, whenever you need to access the third party library with an instance of the third party class, you do something like this:

OneOfManyChildren child;
thirdpartyLibrary.methodThatRequiresThirdPartyClass(child.asThirdPartyClass());
Balder
  • 8,623
  • 4
  • 39
  • 61
  • I upvoted this response for being so complete yet I really wish people would read comments. Directly below Laf's post we concluded my method *does* actually hide the variable, even from casting, so long as the grandparent is in a different package. – AnthonyW Feb 05 '14 at 11:16
  • Thanks. And sorry, I missed the last two comments. Still - if you really want to rely on this behavior (i.e. that subclasses in other packages are unable to overwrite the protected field), then you must somehow enforce, that subclasses cannot be declared in the same package. Not sure if this is possible to do for compile time checks though... – Balder Feb 05 '14 at 11:50
0

How can I restrict access to the protected variable from my children while still being able to manipulate it privately and set it publicly?

So you want the public to have more rights than you do? You can't do that since they could always just call the public method... it's public.

Sled
  • 18,541
  • 27
  • 119
  • 168
0

What about creating another protected variable called propertyMap ? That should over shadow if for your child classes. You can also implement it such that calling any method on it will cause an exception.

However, as accessor methods are defined in the base class, they will not see your second shadowed version and still set it appropriately.

Sled
  • 18,541
  • 27
  • 119
  • 168
  • It is not that I don't want the kids touching the accessor methods; on the contrary, that's what I want them to use. I don't want them playing with the variable **directly** (as in propertyMap = null or some other such nonsense). Improper manipulation of that var can lead to alot of problems in alot of places (again, not my design). – AnthonyW Jan 31 '14 at 19:59
  • Shadowing the propertyMap variable with another protected variable just moves the problem from pilar to post... – AnthonyW Jan 31 '14 at 20:00
  • How? the original method will go to the original variable, but the children now have raw access to the original prevented by the shadow. And if you implement a version of `Map` where everymethod call `throw`s a `UnsupportedOperationException` then this will notify them they are doing things wrong. Sure, it'll only let them know at runtime, but they'll notice as soon as they start testing. – Sled Jan 31 '14 at 21:18
  • The solution I currently have removes ALL raw access, as intended, yet has the nasty side effect of needing to maintain two maps. The two maps is what I am trying to avoid. – AnthonyW Feb 03 '14 at 13:04
0

Visibility on variables is just like visibility on methods, you are not going to be able to reduce that visibility. Remember that protected variables are visible outside the direct subclass. It can be accessed from the parent by other members of the package See this Answer for Details

The ideal solution would be to mess with the parent level class. You have mentioned that making the object private is a non-starter, but if you have access to the class but just cannot downscope (perhaps due to existing dependencies), you can jiggle your class structure by abstracting out a common interface with the methods, and having both the ThirdPartyClass and your BaseClass use this interface. Or you can have your grandparent class have two maps, inner and outer, which point to the same map but the grandparent always uses the inner. This will allow the parent to override the outer without breaking the grandparent.

However, given that you call it a 3rd party class, I will assume you have no access at all to the base class.

If you are willing to break some functionality on the master interface, you can get around this with runtime exceptions (mentioned above). Basically, you can override the public variable to throw errors when they do something you do not like. This answer is mentioned above, but I would do it at the variable (Map) level instead of your interface level.

If you want to allow READ ONLY access top the map:

protected Map innerPropertyMap = propertyMap;
propertyMap = Collections.unmodifiableMap(innerPropertyMap)

You can obviously replace propertyMap with a custom implementation of map instead. However, this only really works if you want to disable for all callers on the map, disabling for only some callers would be a pain. (I am sure there is a way to do if(caller is parent) then return; else error; but it would be very very very messy). This means the parents use of the class will fail.

Remember, even if you want to hide it from children, if they add themselves to the same package, they can get around ANY restrictions you put with the following:

ThirdPartyClass grandparent = this;
// Even if it was hidden, by the inheritance properties you can now access this
// Assuming Same Package
grandparent.propertyMap.get("Parent-Blocked Chocolate Cookie")

Thus you have two options:

  1. Modify the Parent Object. If you can modify this object (even if you can't make the field private), you have a few structural solutions you can pursue.
  2. Change property to fail in certain use-cases. This will include access by the grandparent and the child, as the child can always get around the parent restrictions

Again, its easiest to think about it like a method: If someone can call it on a grandparent, they can call it on a grandchild.

Community
  • 1
  • 1
0

Use a wrapper. A anti decorator pattern, that instead of adding new methods removes them by not providing a method to call it.

Hannes
  • 2,018
  • 25
  • 32