有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java远离子对象:从继承中删除受保护的字段

本着精心设计的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);
  }
}

有没有更好的方法来实现这一点

注意:这只是真正问题的一个例子:如何在不维护多个副本的情况下限制对受保护字段的访问

注意:我还知道,如果该字段首先使用protected访问器生成private,这将不是问题。遗憾的是,我无法控制这一切

注意:需要关系(继承)

注意:这很容易应用于任何集合、DTO或复杂对象

对那些误解问题的人的隐喻:

这类似于祖父母有一个饼干罐,所有家庭成员和家中其他任何人都可以使用(受保护)。一位家长带着年幼的孩子走进房子,出于他们自己的原因,希望防止他们的孩子挖掘饼干罐而令人作呕。相反,孩子应该向父母要一块巧克力饼干,然后看着它神奇地出现;糖饼干或奥利奥也是如此。他们永远不需要知道cookies都存储在同一个罐子里,或者是否有一个罐子(黑匣子)。如果罐子属于父母,如果可以说服祖父母收起饼干,或者如果祖父母自己不需要接触饼干,就可以很容易地做到这一点。除了创建和维护两个相同的jar之外,如何限制儿童访问,而不妨碍家长&;祖父母


共 (6) 个答案

  1. # 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已经指出,通过将子类转换为第三方类,可以很容易地绕过此解决方案。但是,如果这对您来说没有问题,并且您只想对子类隐藏受保护的父映射,而不维护映射的两个副本,那么您可以尝试以下方法:

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

    因此,它实际上归结为一个问题,为什么您不希望子类访问该字段:

    1)这是否只是为了方便起见,因此很快就会清楚您的api应该如何使用那么,简单地对子类隐藏字段也许是好的

    2)是否出于安全原因?然后,您肯定应该搜索另一个解决方案,并使用一个特殊的安全管理器,它还禁止通过反射访问私有字段

    也就是说,您可以尝试另一种设计:不扩展第三方类,而是保留该类的最终内部实例,并提供对内部类的公共访问,如下所示:

    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());
    
  2. # 2 楼答案

    可悲的是,你无能为力。如果该字段为protected,则它要么是一个有意识的设计决策(在我看来是一个错误的决策),要么是一个错误。无论哪种方式,您现在对此都无能为力,因为您无法降低字段的可访问性

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

    事实并非如此。您所做的就是所谓的变量隐藏。由于在子类中使用相同的变量名,因此对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的实例,并将所有调用委托给该对象。通过这种方式,您可以控制ThirdPartyClass的API的哪一部分真正公开给您的子类

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

      如果需要从MyBaseClass直接访问ThirdPartyClasspropertyMap成员,则可以定义私有内部类并使用它访问该成员:

      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;
                  }
              }
          }
      
    • 如果第一个解决方案不适用于您的案例,那么您应该准确地记录MyBaseClass的子类可以做什么,不应该做什么,并希望它们尊重您的文档中描述的契约

  3. # 3 楼答案

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

    所以你想让公众拥有比你更多的权利?你不能这么做,因为他们总是可以调用公共方法。。。这是公开的

  4. # 4 楼答案

    创建另一个名为propertyMap的受保护变量怎么样?如果你的孩子们上课的话,那应该超过阴影。您还可以实现它,以便对其调用任何方法都会导致异常

    但是,由于访问器方法是在基类中定义的,因此它们不会看到第二个阴影版本,并且仍然会对其进行适当的设置

  5. # 5 楼答案

    好的,作弊模式在: 覆盖反公共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");
        }
    
    
    }
    
  6. # 6 楼答案

    这对您来说可能是不可能的,但您是否可以从ThirdPartyClass派生一个接口并使ThirdPartyClass实现它

    然后让MyBaseClass充当装饰器,通过将接口委托给私有成员ThirdPartyClassImpl来实现接口

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