6 回答
TA贡献1841条经验 获得超3个赞
创建两个扩展父类的新类并覆盖不可访问的方法并从中抛出异常。但是如果第三个客户有不同的要求,我们必须为他们创建新的子类。
这是一个糟糕的解决方案,因为它违反了多态性和里氏替换原则。这种方式会让你的代码不太清晰。
首先,你应该考虑一下你的类,你确定它没有被方法重载吗?您确定所有这些方法都与一个抽象相关吗?也许,将方法分离到不同的抽象和类是有意义的吗?
如果类中存在这些方法,那么您应该对不同的客户端使用不同的接口。例如,您可以为每个客户端创建两个接口
interface InterfaceForClient1 {
public void m1();
public void m3();
public void m5();
public void m7();
public void m9();
public void m11();
}
interface InterfaceForClient2 {
public void m2();
public void m4();
public void m6();
public void m8();
public void m10();
public void m12();
}
并在你的课堂上实施它们
class MyClass implements InterfaceForClient1, InterfaceForClient2 {
}
之后,客户端必须使用这些接口而不是类的具体实现来实现自己的逻辑。
TA贡献1847条经验 获得超7个赞
您可以创建一个Interface1
仅为 定义方法的Client1
和一个Interface2
仅为 定义方法的Client2
。然后,您的班级实现Interface1
和Interface2
。
当您声明时,Client1
您可以执行以下操作Interface1 client1
:通过这种方法,client1
只能访问该接口的方法。
我希望这能帮到您。
TA贡献1784条经验 获得超8个赞
其他答案已经提出了惯用的方法。另一个想法是动态代理通过访问检查来修饰 API。
本质上,您生成一个代理 API,该 API 对方法调用进行额外检查以实现某种形式的访问控制。
实施示例:
package com.example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@FunctionalInterface
public interface ACL<P, Q> {
boolean allowed(P accessor, Q target, Method method, Object[] args);
class ACLException extends RuntimeException {
ACLException(String message) {
super(message);
}
}
@SuppressWarnings("unchecked")
default Q protect(P accessor, Q delegate, Class<Q> dType) {
if (!dType.isInterface()) {
throw new IllegalArgumentException("Delegate type must be an Interface type");
}
final InvocationHandler handler = (proxy, method, args) -> {
if (allowed(accessor, delegate, method, args)) {
try {
return method.invoke(delegate, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
} else {
throw new ACLException("Access denies as per ACL");
}
};
return (Q) Proxy.newProxyInstance(dType.getClassLoader(), new Class[]{dType}, handler);
}
}
用法示例:
package com.example;
import java.lang.reflect.Method;
public class Main {
interface API {
void doAlpha(int arg);
void doBeta(String arg);
void doGamma(Object arg);
}
static class MyAPI implements API {
@Override
public void doAlpha(int arg) {
System.out.println("Alpha");
}
@Override
public void doBeta(String arg) {
System.out.println("Beta");
}
@Override
public void doGamma(Object arg) {
System.out.println("Gamma");
}
}
static class AlphaClient {
void use(API api) {
api.doAlpha(100);
api.doBeta("100");
api.doGamma(this);
}
}
public static class MyACL implements ACL<AlphaClient, API> {
@Override
public boolean allowed(AlphaClient accessor, API target, Method method, Object[] args) {
final String callerName = accessor.getClass().getName().toLowerCase();
final String methodName = method.getName().toLowerCase().replace("do", "");
return callerName.contains(methodName);
}
}
public static void main(String[] args) {
final MyACL acl = new MyACL();
final API api = new MyAPI();
final AlphaClient client = new AlphaClient();
final API guardedAPI = acl.protect(client, api, API.class);
client.use(guardedAPI);
}
}
笔记:
不一定
accessor
是客户端对象本身,它可以是帮助 ACL 识别客户端的字符串键或令牌。这里的 ACL 实现是基本的,更有趣的实现可能是从某个文件读取 ACL 的实现,或者使用方法和客户端注释作为规则的实现。
如果你不想为API类定义接口,可以考虑使用javassist这样的工具来直接代理类。
考虑其他流行的面向方面编程解决方案
TA贡献1840条经验 获得超5个赞
您应该创建一个包含所有方法的超类,然后在从先前定义的超类扩展的相应子类中提供客户端特定的实现。
如果有一些方法是所有客户端共同实现的,请将其实现留给超类。
TA贡献2039条经验 获得超7个赞
看来您对类和接口的用途有点困惑。据我所知,接口是定义软件提供哪些功能的契约。这是来自官方java教程:
在软件工程中的许多情况下,不同的程序员群体同意一份“合同”来阐明他们的软件如何交互是很重要的。每个小组都应该能够编写自己的代码,而无需了解其他小组的代码是如何编写的。一般来说,接口就是这样的契约。
然后,您可以编写一个实现此接口/契约的类,即提供实际执行指定操作的代码。List接口和ArrayList类都是这样的示例。
接口和类具有访问修饰符,但它们并非旨在指定特定客户端的权限。它们指定其他软件的可见内容,具体取决于定义的位置:类、包、子类、世界。例如,私有方法只能在定义它的类内部访问。
再次来自官方Java教程:
访问级别修饰符确定其他类是否可以使用特定字段或调用特定方法。有两个级别的访问控制:
在顶层 - 公共或包私有(无显式修饰符)。
在成员级别 - 公共、私有、受保护或包私有(无显式修饰符)。
也许您想要更强大的东西,例如访问控制列表(ACL)。
TA贡献1843条经验 获得超7个赞
你的问题有点不清楚,导致可能的答案不同。我将尝试涵盖一些可能的领域:
对象封装
如果您的目标是向仅提供某些功能或特定视图的不同客户端提供接口,则有多种解决方案。哪种最匹配取决于您班级的目的:
重构
这个问题在某种程度上表明你的班级负责不同的任务。这可能是一个指示符,表明您可以将其分解为提供不同接口的不同类。
原来的
class AllInOne { A m1() {} B m2() {} C m3() {} } client1.useClass(allInOneInstance); client2.useClass(allInOneInstance); client3.useClass(allInOneInstance);
衍生的
class One { A m1() {} }class Two { B m2() {} }class Three { C m3() {} } client1.useClass(oneInstance); client2.useClass(twoInstance); client3.useClass(threeInstance);
接口
如果您选择将类放在一起(可能有充分的理由),则可以让类实现对不同客户端所需的视图进行建模的接口。通过将适当接口的实例传递给客户端,他们将看不到完整的类接口:
例子
class AllInOne implements I1, I2, I3 { ... }interface I1 { A m1(); }
但请注意,客户仍然可以投射到完整的班级,例如((AllInOne) i1Instance).m2()
.
遗产
这已经在其他答案中概述了。因此,我将在这里跳过这一点。我认为这不是一个好的解决方案,因为它在很多情况下可能很容易崩溃。
代表团
如果转换对您来说存在风险,您可以创建仅提供所需接口并委托给实际实现的类:
例子
class Delegate1 { private AllInOne allInOne; public A m1() { return allInOne.m1(); } }
实现这一点可以通过多种方式完成,具体取决于您的环境,例如显式类、动态代理、代码生成……
框架
如果您使用的是Spring等应用程序框架,您也许可以使用该框架中的功能。
方面
AOP 允许您拦截方法调用,并因此应用一些访问控制逻辑。
安全
请注意,上述所有解决方案都不会为您提供实际的安全性。使用强制转换、反射或其他技术仍然允许客户端访问完整功能。
如果您需要更强的访问限制,我将简要概述一些技术,因为它们可能取决于您的环境并且更加复杂。
类加载器
使用不同的类加载器,您可以确保代码的某些部分无法访问其范围之外的类定义(例如在 tomcat 中用于隔离不同的部署)。
安全管理器
Java 提供了实现您自己的可能性,SecurityManager
这提供了添加一些额外级别的访问检查的方法。
定制安全
当然,您可以添加自己的访问检查逻辑。但我不认为这对于 JVM 方法访问来说是一个可行的解决方案。
添加回答
举报