如何防止客户端在Android库中看到内部私有类?

Cro*_*roc 23 java android jar android-library

我有一个包含几个包的图书馆 -

让我们说
包a;
包b;

在包内我有公共a_class
包里面b我有公共b_class
a_class使用b_class.

我需要从中生成一个库,但我不希望客户端看到b_class.

我所知道的唯一解决方案是将我精美易懂的软件包展平为单个软件包,并使用b_class的默认软件包访问.还有另一种方法吗?也许使用接口或某种形式的设计模式?

Lit*_*nti 11

如果拒绝将代码移动到受控服务器上,那么您在尝试使用API​​时就会阻止客户端程序员.让我们开始将好的做法应用到您的设计中:

  1. 让您的包裹按原样组织.
  2. 对于您想要"隐藏"的每个班级:

    • 让它非公开.
    • 将其公共API解压缩到新的公共接口:

    public interface MyInterface {...}

    • 创建公共工厂类以获取该接口类型的对象.

    public class MyFactory { public MyInterface createObject(); }

到目前为止,您现在已经将软件包松散耦合,并且实现类现在是私有的(正如良好实践所述,您已经说过了).尽管如此,它们仍然可以通过接口和工厂获得.

那么,如何避免"陌生人"客户端执行您的私有API?接下来是基于阻碍客户程序员的创意,一个复杂但有效的解决方案:

修改工厂类:向每个工厂方法添加一个新参数:

public class MyFactory
{
    public MyInterface createObject(Macguffin parameter);
}
Run Code Online (Sandbox Code Playgroud)

那么,是什么Macguffin?它是您必须在应用程序中定义的新接口,至少有一个方法:

public interface Macguffin
{
    public String dummyMethod();
}
Run Code Online (Sandbox Code Playgroud)

但是不要提供此接口的任何可用实现.在代码的每个位置,您需要提供一个Macguffin对象,通过匿名类创建它:

MyFactory.getObject(new Macguffin(){
    public String dummyMethod(){
        return "x";
    }
});
Run Code Online (Sandbox Code Playgroud)

或者,甚至更高级,通过动态代理对象,因此即使客户端程序员敢于反编译代码,也不会找到此实现的".class"文件.

你从这里得到什么?基本上是劝阻程序员不要使用需要未知,无证,不可理解的对象的工厂.工厂类应该只关心不接收null对象,并调用dummy方法并检查返回值它也不是null(或者,如果你想要更高的安全级别,添加一个未记录的密钥规则).

因此,此解决方案依赖于对API 的微妙混淆,以阻止客户端程序员直接使用它.Macguffin界面及其方法的名称越模糊越好.


Ste*_*vie 6

我需要从中生成一个库,但我不希望客户端看到b_class.我所知道的唯一解决方案是将我精美易懂的软件包展平为单个软件包,并使用b_class的默认软件包访问.还有另一种方法吗?

是的,make b_classpackage-private(默认访问)并通过反射实例化它以供使用a_class.

既然你知道完整的类名,反过来加载类:

Class<?> clz = Class.forName("b.b_class")
Run Code Online (Sandbox Code Playgroud)

找到要调用的构造函数:

Constructor<?> con = clz.getDeclaredConstructor();
Run Code Online (Sandbox Code Playgroud)

允许自己通过使其可访问来调用构造函数:

con.setAccessible(true);
Run Code Online (Sandbox Code Playgroud)

调用构造函数以获取您的b_class实例:

Object o = con.newInstance();
Run Code Online (Sandbox Code Playgroud)

华友世纪,现在你有一个实例b_class.但是,你不能b_class在一个实例上调用方法Object,所以你有两个选择:

  1. 使用反射来调用b_class方法(不是很有趣,但很容易,如果你只有几个参数很少的方法可能没问题).
  2. b_class落实,你不介意的客户看到投下您的实例的接口b_class到接口(我怀疑你可能已经有了这样的接口字里行间?).

您肯定希望使用选项2来最小化您的痛苦,除非它再次让您回到原点(使用您不希望公开客户端的类型来污染命名空间).

有关完整披露,请注意两点:

1)使用反射与直接实例化和调用存在(小)开销.如果你转换为接口,你只需支付实例化反射的成本.在任何情况下,除非您在紧密循环中进行数十万次调用,否则它可能不是问题.

2)没有什么可以阻止一个坚定的客户找到类名并做同样的事情,但如果我正确地理解你的动机你只想暴露一个干净的API,所以这不是一个真正的担心.