Java 远离子级:从继承中删除受保护的字段

Java 远离子级:从继承中删除受保护的字段,java,inheritance,encapsulation,Java,Inheritance,Encapsulation,本着精心设计的OO的精神,我正在扩展的某个类标记了它的一个受保护的领域。这个类还慷慨地提供了一个公共setter,但没有getter 我用一个基类扩展这个类,这个基类又由几个孩子扩展。如何限制我的孩子对受保护变量的访问,同时仍然能够私下操作和公开设置它 见下例: public abstract class ThirdPartyClass { protected Map propertyMap; public void setPropertyMap(Map propertyMap){

本着精心设计的OO的精神,我正在扩展的某个类标记了它的一个受保护的领域。这个类还慷慨地提供了一个公共setter,但没有getter

我用一个基类扩展这个类,这个基类又由几个孩子扩展。如何限制我的孩子对受保护变量的访问,同时仍然能够私下操作和公开设置它

见下例:

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
    将保存
    ThirdPartyClass
    的实例,并将所有调用委托给该对象。通过这种方式,您可以控制第三方类的API的哪一部分真正公开给您的子类

    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")