【问题标题】:Initialize member of abstract class without subclasses having write access初始化抽象类的成员,没有子类具有写访问权限
【发布时间】:2013-01-01 05:45:15
【问题描述】:

我有一个抽象类:

public abstract class AbstractCommand {

    private static State state;
}

意图

  • State 类的对象由一些“控制类”提供,提供每个 AbstractCommand 子类所需的数据
  • 每个子类都需要对其进行读取访问
  • 子类不允许更改字段

目前的做法

字段state 应该由程序的“控制类”初始化,以便子类(定义命令)可以使用它(只读)。子类是内部定义的,应该用作用户的接口。此用户不应拥有对 state 的写入权限。

问题

  • AbstractCommand 中添加公共setState() 方法将使其可供所有子类以及用户访问
  • 将字段设为 final 将强制在抽象类中创建对象,“控制类”必须使用该对象,而且它不可替换

你如何处理这样的事情?

再试一次

因为一些答案建议使用包可见性的解决方案,我想知道这是否会做得很好:

通过将来自“控制类”(来自包外部)的调用委托给抽象类,在同一个包中拥有一个提供所需信息的类。

听起来也有点模糊,但你怎么看?

【问题讨论】:

  • 所以你希望 AbstractCommand 的子类不能设置状态值,但是其他类可以做到?
  • 是的,类似的。当然,效果相同的更合适的解决方案也可以。
  • 您希望state 变量在您的所有命令中“共享”吗?在我看来,您只想在扩展类之间共享状态(即,一个用于 Command1 的所有实例,一个用于 Command2 的所有实例,等等)。
  • 是的,所有命令(扩展类)都需要读取权限。它始终是同一个变量。
  • 我明白了,但是在您的抽象类中将 state 声明为 static 将导致 所有 扩展类共享 相同状态。因此,如果Command1 的实例与Command2 的实例具有相同的状态。只要确保我理解你想要什么。

标签: java visibility encapsulation software-design


【解决方案1】:

如果我理解正确,您正在寻找 protected 关键字。

在 java 中,此关键字允许子类和包字段访问,但不公开该字段。这允许您在不牺牲该领域的公共保护的情况下寻找您正在寻找的公共只读行为。唯一可以直接访问受保护字段的类将是同一包中的任何内容或直接子类(可能位于不同的包中)。

来源:http://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html

【讨论】:

  • 不,不是。这将使它可以通过用户使用的子类访问......并且用户应该可以访问它。
  • 所以你有一个私有变量和一个可选的私有 setter,但有一个受保护的 getter。子类可以读取该值。只有类本身(父类)可以更改它。
  • 私有设置器没有多大帮助,因为该字段必须从外部设置。问题是:不是所有人。
  • 在这种复杂程度下,我不确定访问修饰符是否能提供他想要的控制级别。
【解决方案2】:

您可以将AbstractCommand 与“控制类”放在同一个包中,并将特定实现放在另一个包中。然后你可以提供一个包私有的 setter 和 protected getter。这将允许控制类设置值,而实现只能访问 getter。

但是,这会弄乱您的包结构。如果您不希望这种情况发生 - 尝试使用工厂。您可以构建以下包结构:

 command
     impl
         CommandImpl1 //extends AbstractCommand
         CommandImpl2 //extends AbstractCommand
     AbstractCommand
     CommandFactory

这个想法是工厂用于创建 AbstractCommand 的实例。因此,您将在任何其他包中将参数传递给 Factory,它会选择您需要的实现并返回一个新对象。在这种情况下,您可以使用前面的想法来授予对 getter 和 setter 的适当访问权限。但是,您可以在此处永久设置该字段。

如果您需要多次修改它,您可以创建一个评估器。这是与您的 AbstractCommand 在同一个包中的 CommandAccessor 类,它应该提供如下静态方法:

public static void setState(State newState, AbstractCommand command);

没有什么可以阻止您在实现类中使用它,但是您可以设置一个不应该使用它的非正式规则。

【讨论】:

  • 是的,谢谢,这是可能的......但这也会混淆包结构,因为从逻辑上讲它们并不真正属于一起......
  • 这是您描述的一个奇怪的用例。通常你希望给子类更多的访问权限而不是不相关的类,而不是更少。
  • @user905686 那是因为你要做的事情需要相当多的脑力锻炼。在超类中拥有一个必须从子类只读但对所述类的其他客户端读写的字段是非常不寻常的(至少对我而言)。我对您的项目一无所知,但在我看来,您的设计似乎是不恰当的。
  • 工厂(或建造者)模式听起来不错。但我不希望工厂选择一个实现——我有不同的命令,用户应该能够创建他喜欢的任何命令的实例。
  • 您可以使用不同的方法创建不同的实例。或者,您可以使用命令类型创建一个枚举并将其提供给工厂。如果你正在实现一个解析器枚举通常在解析代码中做得很好
【解决方案3】:

我只能提供模糊的解决方案。

一些解决方案:

随便

private static final State state = Controller.initState();

或者使用控制反转,依赖注入,@Inject。这也将允许单元测试。网络中肯定有开源 DI 容器(Spring,还是 Pico 容器还在?)。或者从某个 DI 容器中请求 bean。

如果两者都为时过早,请进行惰性评估(部分静态初始化已经是惰性的)。有时会看到一个内部类:

private static class Singleton {
    private static final State state = Controller.initState();
}

可能使用 getInstance。

我的选择:

不知何故没有静态,而是单例的吸气剂。一个 bean 框架与控制器一起工作。


单例而不是静态。

在之前的eclipse 3富客户端中大量使用的Statics(静态函数),比如

IPreferenceStore store = IDEWorkbenchPlugin.getDefault().getPreferenceStore();
boolean autoPrint = store.getBoolean(AUTO_PRINT);

现在也可以通过 OSGi 容器和注解进行依赖注入:

@Inject @Preference(AUTO_PRINT)
boolean autoPrint;

来自:Eclipse 4,M. Teufel 和 J. Helming 的 Rich Clients

除了更短之外,类之间的耦合更少,单元测试更容易编写,因为我们可以随意填写autoPrint,而不需要干预填充类。

如果对添加这样一个容器的开销犹豫不决,最简单的方法是使用一个全局应用程序上下文来替代几种静态方法,您可以在其中查找 java 对象、POJO bean。也许由 XML 文件支持:

State state = ApplicationContext.lookup(State.class, "state");

<bean name="state" class="org.anic.State" value="sleepy" depends="firstThis"/>
<bean name="firstThis .../>

请注意,不再需要静态状态。

Spring 框架有这样一种 XML 方法。

优点是集中初始化,可以考虑顺序和不同的工厂/创建方法。

(抱歉,回答混乱。)

【讨论】:

  • 您能否更详细地解释一下单例方法?有什么区别/诀窍?
【解决方案4】:

将它作为抽象类的构造函数传入

public abstract class AbstractCommand {
    private static State state;
    protected AbstractCommand(State state){
        this.state = state;
    }        

    public State getState(){
        return state;
    }
}

在你的扩展类中......

 public class Command1 extends AbstractCommand{
       public Command1(){
             super([some state]);
       }
 }

扩展类可以在初始化期间设置一次state,但此后具有只读访问权限。

【讨论】:

  • 但是子类必须询问对象的“控制类”(并且您不确定它是否已经初始化)并且每次构造命令时都会再次设置该字段。 ..
【解决方案5】:

所以我看到您希望 Magus 提到的行为为“所以您希望 AbstractCommand 的子类不能设置状态值,但其他类可以做到?”

这是我的建议:

  1. 使用一些规则创建接口。您想在所有子类中应用

  2. 现在让AbstractCommand 实现该接口,它还应该包含state 变量,通过这样做,您可以在较低级别维护一组规则

  3. 在第 1 步中定义的接口的第二段中,让您不想访问 AbstractCommand 类变量的其他类

通过这样做,您可以维护您的包结构。希望这会有所帮助。

【讨论】:

  • 我不确定我是否理解正确,究竟是什么阻止了子类设置值?我在哪里可以设置值?我真的不明白第 3 点。
  • 参考我添加的最新评论。由于帖子限制,我无法在此处添加它。
【解决方案6】:

这是我正在尝试的:

创建接口为:

public interface RuleInterface { //Define rules here void method1(); }

现在在你的 AbstractCommand 类中实现它

public abstract class AbstractCommand implements RuleInterface{ private static String state; }

还有其他类,这个类可以修改statevaribale

public class SubClassAbstractCommand extends AbstractCommand{ @Override public void method1() {
} }

为接口再创建一条腿:

public class AnotherLeg implements RuleInterface{ @Override public void method1() { } }

现在 AnotherLeg 类无法访问 state 变量,但您仍然可以通过接口 RuleInterface 强制执行规则

【讨论】:

  • state 是私有的,那么SubClassAbstractCommand 怎么修改呢?再说一次,子类(命令)将需要访问,但只读...
【解决方案7】:
public abstract class AbstractCommand {
    private static State state;
    static {
        state = Controller.getState();
    }
    protected AbstractCommand(){
    }        
    public State getState(){
        return state;
    }
}

【讨论】:

  • 欢迎来到本站。您是否介意通过添加一些文本来扩展它以解释它如何解决问题并将其与前面的答案区分开来?
猜你喜欢
  • 2017-10-08
  • 1970-01-01
  • 1970-01-01
  • 2011-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-28
  • 1970-01-01
相关资源
最近更新 更多