Keep out of reach of children: Removing protected fields from inheritance

In the spirit of a well-designed OO, a particular class that I am expanding is marked with one of its protected fields. This class also generously provided a public setter, but did not receive it.

I am expanding this class with a base class, which in turn is distributed by several children. How can I restrict access to a protected variable from my children while still being able to manipulate it privately and publicly publish it?

See an 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 already found that I can revoke access by creating a private final field in MyBaseClass . However, this also interferes with the use of the setter provided by the superclass.

I can get around this limitation with "smartness" below, but it also leads to saving two copies of the same map, as well as the O (n) operation for copying on each item.

 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 to achieve this?

Note: This is just one example of a real question: How to restrict access to protected fields without saving multiple copies?

Note. I also know that if the field was made private primarily with the help of the protected accessor, this would be a problem without problems. Unfortunately, I do not control this.

Note: IS-A reconnection (inheritance) is required.

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

Metaphor for misunderstanding the question:

It looks like grandparents who have a jug of cookies that they leave accessible to all family members and anyone else in their home (sheltered). A parent, with young children, enters the house and, at his own discretion, wants to stop his children from digging in a jar of cookies and nausea. Instead, the child should ask the parent to create cookies with a chocolate chip and see it magically; also for sugar cookies or oreo. They do not need to know that all cookies are stored in one bank or even in a bank (black box). This could be easily done if the jug belonged to parents, if grandparents could be convinced to leave cookies or grandparents themselves do not need access. Without creating and maintaining two identical jars, how can you restrict access for children who are not yet created for parents and grandfathers?

+7
java inheritance encapsulation
source share
8 answers

It may not be possible for you, but if you could get an interface from ThirdPartyClass and make ThirdPartyClass implement it?

Then MyBaseClass acts as a decorator, implementing the interface, delegating to a private member ThirdPartyClassImpl.

those.

 public interface ThirdParty ... public class ThirdPartyClass implements ThirdParty public class MyBaseClass implements ThirdParty { private ThirdParty decorated = new ThirdPartyClass(); public class SubclassOne extends MyBaseClass.... 

etc.

+6
source share

Okay, cheat mode: How about overwriting the public setter and changing the map implementation to the inner class MyBaseClass. This implementation may throw an exception for all map methods that you don’t want your children to have access to, and your MyBaseClass could identify the methods that they should use using your card’s internal implementation method ... You still need to decide how ThirdPartyMethod will get access to these properties, but you can force your code to call finalizationMethod on MyBaseClass before using it ... I just understand 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"); } } 
+2
source share

Unfortunately, there is nothing you can do. If this field is protected , it is either a conscious design decision (bad IMO) or an error. In any case, you can’t do anything now, because you cannot reduce the availability of the field.

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

This is not entirely true. What you do is called variable conjugation. Since you use the same variable name in your subclass, propertyMap variable references now point to your personal variable in MyBaseClass . However, you can easily hide this variable 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" } } 

Thus, you cannot “deny” access to the protected member of the class in the ThirdPartyClass superclass. You do not have many options:

  • If your child class does not need to know the hierarchy of classes above MyBaseClass (i.e. they will not refer to ThirdPartyClass at ThirdPartyClass ), and if you do not need these subclasses of ThirdPartyClass , then you can make MyBaseClass class that does not extend to ThirdPartyClass . Instead, MyBaseClass will contain an instance of ThirdPartyClass and delegate all calls to this object. This way you can control how much of the ThirdPartyClass API you really open up for your subclasses.

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

    If you need direct access to the propertyMap member of ThirdPartyClass from MyBaseClass , you can 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 does not apply to your case, you must specify exactly what subclasses of MyBaseClass can do and what not to do, and hope that they comply with the contract described in your documentation.

+1
source share

I can get around this limitation with "smartness" below, but it also leads to saving two copies of the same map, as well as the O (n) operation for copying on each item.

Laf has already pointed out that this solution can be easily circumvented by dropping child classes into a third-party class. But if this suits you, and you just want to hide the protected parent card from your child classes without saving two copies of the card, you can try the following:

 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); } } 

We also note that this does not matter much if the parent card is protected or not. If you really want to access this field through a child class, you can 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 this really comes down to the question of why you want the field not to be accessible by child classes:

1) This is just for convenience, so it’s immediately clear how to use your api? - then it might just be easy to hide the field from subclasses.

2) Is it due to security reasons? Then you definitely need to look for another solution and use a special SecurityManager, which also prohibits access to private fields through reflection ...

However, there may be another design that you could try: instead of extending the third class, save the last inner instance of this class and provide open access to the inner class as follows:

 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, when you need to access a third-party library with an instance of a third-party class, you do something like this:

 OneOfManyChildren child; thirdpartyLibrary.methodThatRequiresThirdPartyClass(child.asThirdPartyClass()); 
+1
source share

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

So you want the public to have more rights than you? You cannot do this, as they can always just call a public method ... it's public.

0
source share

How to create another protected variable propertyMap ? This should be too shady if for your child classes. You can also implement it in such a way that calling any method on it raises an exception.

However, since access methods are defined in the base class, they will not see your second shadow version and still correctly install it.

0
source share

The visibility of variables is like the visibility of methods; you cannot reduce this visibility. Remember that protected variables are visible outside a direct subclass . Access to it can be obtained from the parent by other members of the package . See this answer for more details.

An ideal solution would be to mess around with a parent-level class. You mentioned that making an object private is not a starter, but if you have access to the class, but it just cannot decrease (possibly due to existing dependencies), you can mix your class structure by abstracting the common interface using methods , and having both ThirdPartyClass and your BaseClass, use this interface. Or you can have in your classroom grandparents two cards, internal and external, that point to the same card, but grandparents always use the internal one. This will allow parents to redefine the external element without disturbing their grandparents.

However, given that you call it a third-party class, I assume that you do not have access to the base class at all.

If you want to break some functions on the main interface, you can get around this with runtime exceptions (mentioned above). Basically, you can override a public variable to throw errors when they do something you don't like. This answer is mentioned above, but I would do it at the variable level (Map) instead of the level of your interface.

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

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

You can obviously replace the Map property with your own map implementation. However, it really really works, if you want to disable for all callers on the card , disconnecting only some subscribers will be a pain. (I am sure there is a way to do it if (the caller is the parent) and then returns, otherwise an error, but that would be very very dirty). This means that parents cannot use the class.

Remember that even if you want to hide it from children, if they add themselves to the same package, they can circumvent any restrictions that you indicated 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") 

So you have two options:

  • Change parent object. If you can change this object (even if you cannot make the field private), you have several structural solutions that you can continue with.
  • Change the property to failure in certain use cases. This will include access to grandparents, as a child can always circumvent parental restrictions.

Again, it’s easiest to think of it as a method: if someone can call him grandfather, he can call him grandson.

0
source share

Use a wrapper. An anti-decorator template that, instead of adding new methods, removes them without providing a method to call it.

0
source share

All Articles