【问题标题】:Is this a good practice to use the "default" Java access to hide classes and methods from client这是使用“默认”Java 访问对客户端隐藏类和方法的好习惯吗
【发布时间】:2011-10-10 04:37:47
【问题描述】:
  • 在类的情况下:

如果我们使用工厂方法,我们必须将创建的实现作为实现接口的类型返回。

public class Factory {

  public Product getProduct() {
    return new ProductA();
  }
}

public interface Product {
}

class ProductA implements Product {
}

为了避免客户将返回的 Product 转换为 Product{A, B, C... etc.} 的具体实现,我们必须:

  1. 分别打包客户和工厂的代码(比如说com.example.clientcom.example.factory
  2. 声明具有默认(“包”)访问权限的具体实现(工厂可见,客户端不可见)

    package com.example.client;
    ...
    public class Client {
      public static void main(String[] args) {
        Product i = new Factory().getProduct();
        ProductA a = (ProductA) i; // the type of ProductA isn't visible.
      }
    }
  • 在方法的情况下:

例如我们需要使用与隐藏方法相同的工厂

public class Factory {

  public Product getProduct() {
    return new ProductA();
  }

  Product[] getCreatedProducts() {
    ...
  }
}

我在这里看到两个问题:

  • 错误的包结构:隐藏的类和方法必须与调用代码在一个包中。
  • 糟糕的代码:不太直观和易于理解。将 java 文件替换为另一个包很容易中断。

【问题讨论】:

  • 我以为和protected一样,但我不确定。我总是定义一个访问关键字。
  • @Martijn protected 可以被包和子类访问。包可以访问默认值(无关键字),但子类不能访问。
  • @Aghasted 我认为你在做正确的事。但是我不明白你的“两个问题”。另外我不太清楚你为什么这样做:你为什么要对客户端隐藏不同的Product 类型?
  • @toto 客户的类必须不知道他们使用的产品的实现。排除 ClassCastException 需要避免客户端的强制转换。没关系,上面的例子是虚构的。它不处理我的真实代码,但它是相似的。假设这只是用例。上面提到的这种代码组织的问题是我们不能在包之间共享 Product 的实现。例如,我们在另一个包(比如说 com.example.manager)中有一个类,它想知道客户正在使用的产品的具体类型是什么。
  • @Aghasted 哦...您希望某些包可以访问各种Products,但其他包则不能。我不确定是否有一些安全机制可以做到这一点。

标签: java oop access-modifiers


【解决方案1】:

“默认”访问不能保证任何事情,因为任何流氓程序员都可以在你的包中声明他们的类。此外,无论您的包结构如何,在 java 中,您几乎总是可以进行“instance of”检查,然后向下转换为“instance of”类型。因此,如果您的目标是防止任何向下转换,则必须使用 private 关键字。例如,您可以将Product 接口的具体实现声明为private staticFactory 中的匿名内部类。事实上,在 Bloch 的“如何设计一个好的 API”文章中,他提出了一个观点,即您应该“最小化所有内容的可访问性”。

也就是说,我认为你在这里有点偏执。如果有人沮丧,对你来说真的那么重要吗?您编写的任何代码都可能被滥用,当然,如果您包含一个有据可查的工厂,那么您已经提供了有关如何正确使用 API 的明确信息。另外,如果你构建一个真正的工厂方法,它接受参数并且具有明确的方法名称,而不是这个不接受参数的玩具Factory 示例,那么我想你会发现你正在广播什么是公开相关的部分无论如何都在创建。

【讨论】:

    【解决方案2】:

    我没有看到两个包的优势。我建议这个替代方案:

        package com.example.client ;
        public interface Product
        {
             /* stuff */
        }
    
        package com.example.client ;
        public interface ProductFactory
        {
             Product make ( X1 x1 , X2 x2 , /* parameters */ , Xn xn ) ;
        }
    
        package com.example.manager;
        interface ManagedProduct extends com.example.client.Product
        {
             /* management methods */
        }
    
        package com.example.manager ;
        public final class DefaultProductFactory implements com.example.client.ProductFactory
        {
             public static final DefaultProductFactory instance = new DefaultProductFactory ( ) ;
    
             private DefaultProductFactory ( )
             {
                  super ( ) ;
             }
    
             public ManagedProduct make ( final X1 x1 , final X2 x2 , /* parameters */ , final Xn xn )
             {
                   return new ManagedProduct ( )
                   {
                        /* implementation logic here */
                   } ;
             }
    
             /*
                  possibly other methods
                  The Product implementation class is invisible.
              */
        }
    
    1. 使用两个包不必要地将实现 Product 类暴露给 com.example.manager.DefaultProductFactory 类。我认为我的方法优于Bringer128's private inner class Factory。使用我的方法,实现 Product 类甚至对实现 Factory 类中可能存在的其他方法是不可见的。
    2. 如果你将参数设为final,那么你可以在实现Product类中直接从方法参数中使用它们(不需要(1)创建X1 x1,X2 x2,...,Xn xn成员;(2) this.x1=x1, this.x2=x2, ..., and this.xn=xn 在构造函数中;和 (3) 使用 ProductImpl (x1,x2,...,xn) 调用构造函数。这是不可否认的小,但可以节省您的击键次数。
    3. 我非常同意philwb。这不应被视为安全。
    4. 这允许 com.example.manager 中的类在同一对象上拥有比其他包中的类更多的方法 - 正如 Is this a good practice to use the "default" Java access to hide classes and methods from client 中所要求的那样。

    【讨论】:

      【解决方案3】:

      在您的情况下,您让客户知道知道实现类的工厂。如果它们都在同一个进程中,那么客户端和实现类都被加载到同一个进程中,这意味着客户端可以通过反射访问实现类的底层方法。这假设您无法完全控制客户端运行时,即采取措施防止反射。但是,如果您这样做了,那么您可能不需要担心客户端无法转换为实现类。

      因此,如果您认为这是针对不受信任的客户端进程的潜在安全机制,那么我不会相信它。如果您可以控制客户端,那么这可能足以防止错误的程序员无意中造成混乱。

      【讨论】:

        【解决方案4】:

        我真的不明白为什么要将工厂和类放在单独的包中。

        我通常在同一个包中创建公共接口、公共工厂类和包保护实现。因此客户端只能使用工厂创建实例并且不能向下转换,因为具体类在其他包中不可见。

        【讨论】:

        • 问题是“默认访问是否足够好使用”。我认为不是,我们必须始终定义访问关键字。我只是想知道是否有更好的方法来避免客户端投射和隐藏客户端的方法。
        • @Aghasted 为什么您在使用包保护访问时遇到问题?这并不是真正的“默认”,只是包保护访问不需要关键字。此外,您唯一的其他选择是拥有一个私有的 Factory 内部类。
        猜你喜欢
        • 2016-06-27
        • 2016-09-23
        • 1970-01-01
        • 2017-06-14
        • 1970-01-01
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多