mercredi 29 janvier 2020

How to resolve type safety objections to global setting/event registry?

I'm working on a client’s Java application with multiple projects and plugins. They have a very brittle design because sharing any information across these division involves some god classes instantiating and registering classes that implement specific interfaces. The interfaces proliferate unchecked and often contain contract methods that no client requires.

In an effort to decouple everything, I have proposed a couple of clearinghouse objects that handle “settings” and “events”. Any class can request a setting, specifying the type of setting from an enumeration and the type of value expected. Any class can register a setting source that implements an interface defining how to get that setting (e.g. whether the network is available by checking some variable in some class), but more often I can completely separate some variable from the original class and it floats for eternity as an anonymous SettingSource that just accepts and publishes some setting value.

//code from caller requesting the setting value
long netID = Setting.GetLongSetting(Setting.Type.NetworkID);

//one of those self-defined settings
Setting.RegisterLongSource(Setting.Type.NetworkID, new LongSource() {
  private long myID=-1;

  @Override
  public long getValue() {
    return myID;
  }

  @Override
  public void setValue(long l) {
    myID = l;
  }

  @Override
  public Setting.Type getType() {
    return Setting.Type.NetworkID;
  }
} 

The events are registered in a similar way with an EventArg object borrowed heavily from C#.

//code from an event generator
Events.Event(Event.Type.MessageReceived, new ObjectEventArg(messageInstance));

//An anonymous event listener that expects a message object and passes it to a method for processing
Events.RegisterListener(Event.Type.MessageReceived, new Event.Listener() {
  @Override
  public void HandleEvent(Event.Type type, EventArg e) {
    if(type!=Event.Type.MessageReceived)
    {
       try
       {
         Message m = (Message) ((ObjectEventArgs)e).value;
         if(m!=null)
         {
           this.handleMessage(m);
         }
         else
         {
           //error 3
         }
       }
       catch
       {
         //error 2
       }
    }
    else
    {
      //error 1
    }
  }
} 

There are some problems with these designs and my clients are asking me to consider something that has more compile-time safety. For example there are three opportunities for errors in the events. It should be impossible for an event listener to get an event type for which it didn't register (error 1), but I should at least check. There is no guarantee that the EventArg type will be the right kind or that the object passed in the ObjectEventArg will match expectations (error 2). There is no guarantee that the passed reference isn't null (error 1). All of this leaves so very much up to the good behavior of the code that cannot be guaranteed at compile time. For that matter, someone could try to create settings for time that accept a string instead of a long and who to say which is right?

What could I do to improve this design, avoid some of the unknowns, and provide that compile-time guarantee?

Aucun commentaire:

Enregistrer un commentaire