Java 远离子级:从继承中删除受保护的字段
本着精心设计的OO的精神,我正在扩展的某个类标记了它的一个受保护的领域。这个类还慷慨地提供了一个公共setter,但没有getter 我用一个基类扩展这个类,这个基类又由几个孩子扩展。如何限制我的孩子对受保护变量的访问,同时仍然能够私下操作和公开设置它 见下例:Java 远离子级:从继承中删除受保护的字段,java,inheritance,encapsulation,Java,Inheritance,Encapsulation,本着精心设计的OO的精神,我正在扩展的某个类标记了它的一个受保护的领域。这个类还慷慨地提供了一个公共setter,但没有getter 我用一个基类扩展这个类,这个基类又由几个孩子扩展。如何限制我的孩子对受保护变量的访问,同时仍然能够私下操作和公开设置它 见下例: public abstract class ThirdPartyClass { protected Map propertyMap; public void setPropertyMap(Map propertyMap){
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.
}
我已经发现,我可以通过在
MyBaseClass
中设置字段private final
来撤销访问权限。然而,这也妨碍了使用超类提供的setter
我可以用下面的“聪明”来绕过这个限制,但它也会导致维护同一地图的两个副本以及在每个元素上复制的O(n)操作
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);
}
}
有没有更好的方法来实现这一点
注意:这只是真正问题的一个例子:如何在不维护多个副本的情况下限制对受保护字段的访问?
注意:我还知道,如果该字段首先使用受保护的
访问器设置为私有
,这将不是问题。遗憾的是,我无法控制这一切
注意:需要关系(继承)
注意:这很容易应用于任何集合、DTO或复杂对象。
对那些误解问题的人的隐喻:
这类似于祖父母有一个饼干罐,所有家庭成员和家中其他任何人都可以使用(受保护)。一位家长带着年幼的孩子走进房子,出于他们自己的原因,希望防止他们的孩子挖掘饼干罐而令人作呕。相反,孩子应该向父母要一块巧克力饼干,然后看着它神奇地出现;糖饼干或奥利奥也是如此。他们永远不需要知道cookies都存储在同一个罐子里,或者是否有一个罐子(黑匣子)。如果罐子属于父母,如果可以说服祖父母收起饼干,或者如果祖父母自己不需要接触饼干,就可以很容易地做到这一点。除了创建和维护两个相同的JAR,如何限制儿童访问,而不妨碍父母和祖父母
如何限制我的孩子对受保护变量的访问,同时仍然能够私下操作和公开设置它
所以你想让公众拥有比你更多的权利?你不能这么做,因为他们总是可以调用公共方法。。。这是公开的。遗憾的是,你无能为力。如果此字段受到
保护
,则它可能是有意识的设计决策(在IMO中是错误的),也可能是错误的。无论哪种方式,您现在对此都无能为力,因为您无法降低字段的可访问性
我已经发现我可以通过在MyBaseClass中将字段设为private final来撤销访问权
事实并非如此。您所做的就是所谓的变量隐藏。由于在子类中使用相同的变量名,因此对propertyMap
变量的引用现在指向MyBaseClass
中的私有变量。但是,您可以非常轻松地绕过隐藏此变量,如下代码所示:
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"
}
}
因此,您无法“撤销”对超级类ThirdPartyClass
中受保护类成员的访问。您没有太多选择:
- 如果您的子类不需要知道上面的类层次结构
(即他们根本不会引用MyBaseClass
),如果您不需要它们成为ThirdPartyClass
的子类,那么您可以使ThirdPartyClass
成为一个不从MyBaseClass
扩展的类。相反,ThirdPartyClass
将保存MyBaseClass
的实例,并将所有调用委托给该对象。通过这种方式,您可以控制第三方类的API的哪一部分真正公开给您的子类ThirdPartyClass
public class MyBaseClass { private ThirdPartyClass myclass = new ThirdPartyClass (); public void setPropertyMap (Map<?,?> propertyMap) { myclass.setPropertyMap (propertyMap); } }
- 如果第一个解决方案不适用于您的案例,那么您应该准确地记录
的子类可以做什么,不应该做什么,并希望它们尊重您的文档中描述的契约MyBaseClass
- 创建另一个名为
propertyMap的受保护变量怎么样?如果你的孩子们上课的话,那应该超过阴影。您还可以实现它,以便对其调用任何方法都会导致异常
但是,由于访问器方法是在基类中定义的,因此它们不会看到您的第二个阴影版本,并且仍然会对其进行适当的设置。确定,欺骗模式为:
覆盖反公共setter并将映射实现更改为MyBaseClass的内部类怎么样。此实现可能会在您不希望您的孩子访问的map的所有方法上引发异常,并且您的MyBaseClass可以通过使用您的map实现的内部方法公开他们应该使用的方法。。。
仍然需要解决第三方方法如何访问这些属性,但是您可以在使用MyBaseClass之前强制代码调用finalizationMethod。。。我只是在这里跳
编辑
像这样:
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");
}
}
我可以用下面的“聪明”来绕过这个限制,但它也会导致维护同一地图的两个副本以及在每个元素上复制的O(n)操作
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);
}
}
Laf已经指出,通过将子类转换为第三方类,可以很容易地绕过此解决方案。但是,如果这对您来说没有问题,并且您只想对子类隐藏受保护的父映射,而不维护映射的两个副本,那么您可以尝试以下方法:
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);
}
}
另请注意,第
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();
}
}
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;
}
}
OneOfManyChildren child;
thirdpartyLibrary.methodThatRequiresThirdPartyClass(child.asThirdPartyClass());
public interface ThirdParty ...
public class ThirdPartyClass implements ThirdParty
public class MyBaseClass implements ThirdParty {
private ThirdParty decorated = new ThirdPartyClass();
public class SubclassOne extends MyBaseClass....
protected Map innerPropertyMap = propertyMap;
propertyMap = Collections.unmodifiableMap(innerPropertyMap)
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")