Initialize member of abstract class without subcla

2019-07-17 20:26发布

问题:

I have an abstract class:

public abstract class AbstractCommand {

    private static State state;
}

Intention

  • An object of class State is provided by some "controlling classes", providing data that is needed by each AbstractCommand subclass
  • Each subclass needs read access to it
  • The subclasses are not allowd to change the field

Current approach

The field state should be initialized by the "controlling classes" of the program so that subclasses (that define commands) can use it (read-only). The subclasses are defined internally and should be used as an interface for the user. This user should not have write access to state.

The problem

  • Adding a public setState() method in AbstractCommand would make it accessible to all subclasses and with that to the user
  • Making the field final would force the creating of the object to take place in the abstract class and the "controlling classes" would have to use this object, furthermore it would not be replacable

How do you handle something like this?

Another try

Because some answers suggested solutions using package visibility I wonder if this would do a good job:

Have a class in the same package that provides the required information by delegating a call from the "controlling classes" (from outside the package) to the abstract class.

Sounds a little fuzzy, too but what do you think?

回答1:

If I understand you correctly, you are looking for the protected keyword.

In java this keyword allows for subclass and package field access, but does not make the field public. This allows for the public read-only behavior you're looking for without sacrificing the public protection of the field. The only classes that can access a protected field directly will be anything in the same package or a direct subclass (which may be in a different package).

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



回答2:

You could put the AbstractCommand into the same package with the "controlling classes" and specific implementations to another package. Then you could provide a package-private setter and protected getter. This would allow the controlling classes set the value and implementations would only have access to the getter.

Howevery, this would mess your package structure. If you do not want this to happen - try to use a Factory. You culd build the following package structure:

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

The idea is that a Factory is used to create instances of an AbstractCommand. So you will pass the parameter to the Factory in any other package and it would select an implementation you need and return you a new object. In this case you could use the previous idea to grant proper access to the getters and setters. However here you would be able to set the field once and forever.

If you need to modify it many times, you could create an assessor. This is the CommandAccessor class in the same package as your AbstractCommand and it should provide the static methos like:

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

Nothing would prevent you from using it in the implementation classes, however you could just set an informal rule that it should no be used.



回答3:

I can only offer fuzzy solutions.

Some solutions first:

Either do

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

Or use inversion of controll, dependency injection, @Inject. That would allow unit tests too. There certainly are open source DI containers out there in the web (Spring, or is Pico container still around?). Or requesting beans from some DI container.

If both are too early, go for lazy evaluation (partly the initialisation of statics is already lazy). Sometimes one will see an inner class:

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

Possibly with a getInstance.

My choice:

Somehow no statics, but getters to singletons. A bean frame work with controllers.


Singletons instead of statics.

Statics (static functions) where abundantly used in the prior eclipse 3 rich client, like

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

Now alternatively with dependency injection by the OSGi container and annotations:

@Inject @Preference(AUTO_PRINT)
boolean autoPrint;

From: Eclipse 4, Rich Clients by M. Teufel and J. Helming

Besides being shorter, there is less coupling between classes, and unit tests can more easily be written, as we can fill in autoPrint like we like, and do not need to meddle with the filling class.

If one hesitates adding the overhead of such a container, the easiest way is to have alternatives to several statics is having one global application context, where you can lookup java objects, POJO beans. Maybe backed by an XML file:

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

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

Mind, there no longer is a need to have a static state.

The Spring framework has such an XML approach.

The advantage being a centralized initialisation, where sequence and different factory / creation methods are thinkable.

(Sorry for the messy answer.)



回答4:

Pass it in as the constructor of your abstract class

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

    public State getState(){
        return state;
    }
}

In your extending classes...

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

The extending class can set the state once during initialization, but has read-only access thereafter.



回答5:

So I see you want the behavior as mentioned by Magus as "So you want that subclasses of AbstractCommand can't set the state value, but an other class can do it ?"

Here is my suggestion:

  1. Create an Interface with some rules. That you want to apply in all your subclasses

  2. Now Let AbstractCommand implement that Interface and it should also contain state variable, by doing this you can maintain a set of rule at lower level

  3. In second leg of Interface define in step 1 have your other class that you want not to have access to AbstractCommand class variable

By doing this you can maintain your package structure. Hope this helps.



回答6:

Here is what I was trying:

Create Interface as:

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

Now implement this in your AbstractCommand class

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

Have other class, this class can modify state varibale

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

Create one more leg for Interface as:

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

Now AnotherLeg class can't access state variable but still you can enforce the rules via interface RuleInterface



回答7:

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