这是一个很好的做法,使用“默认”Java访问来隐藏客户端的类和方法

时间:2011-07-21 12:17:32

标签: java oop access-modifiers

  • 如果是班级:

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

public class Factory {

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

public interface Product {
}

class ProductA implements Product {
}

为了避免客户能够将退回的产品投射到产品{A,B,C ...等}的具体实施中,我们必须:

  1. 分别打包客户端和工厂代码(假设为com.example.clientcom.example.factory
  2. 使用默认(“包”)访问声明具体实现(对Factory可见,对客户端不可见)

  3.     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文件替换为另一个软件包很容易打破。

4 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分: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,...和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中所要求的那样。

答案 2 :(得分:2)

“默认”访问并不保证任何东西,因为任何流氓程序员都可以在你的包中声明他们的类。另外,无论你的包结构如何,在java中,你几乎总是可以做一个“实例”检查,然后向下转换为“实例”类型。因此,如果您的目标是阻止任何向下转发,则必须使用private关键字。例如,您可以将Product接口的具体实现声明为private static,或将Factory声明为匿名内部类。事实上,在布洛赫的“如何设计一个好的API”文章中,他指出你应该“尽量减少一切的可访问性。”

那就是说,我觉得你在这里有点偏执。如果有人垂头丧气,对你来说真的很重要吗?您编写的任何代码都可能被滥用,当然如果您包含一个记录良好的工厂,那么您已经提供了有关如何正确使用API​​的明确信息。此外,如果你构建一个真正的工厂方法,它接受参数并且具有清晰的方法名称,而不是这个没有参数的玩具Factory例子,那么我认为你会发现你正在广播与公共相关的部分无论如何,正在创造什么。

答案 3 :(得分:1)

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

因此,如果您将此视为针对不受信任的客户端流程的潜在安全机制,那么我不会对此抱有任何信任。如果您可以控制客户端,那么这可能足以让错误的程序员无意中发生混乱。