Как разрешить возражения безопасности типов для глобальных настроек / реестра событий? - PullRequest
0 голосов
/ 29 января 2020

Я работаю над клиентским приложением Java с несколькими проектами и плагинами. У них очень хрупкий дизайн, потому что обмен любой информацией между этими подразделениями включает в себя некоторые классы богов, создающие и регистрирующие классы, которые реализуют определенные c интерфейсы. Интерфейсы распространяются неконтролируемыми и часто содержат методы контракта, которые не требуются ни одному клиенту.

В попытке отделить все, я предложил пару объектов центра обмена информацией, которые обрабатывают «настройки» и «события». Любой класс может запросить настройку, указав тип настройки из перечисления и тип ожидаемого значения. Любой класс может зарегистрировать источник параметра, который реализует интерфейс, определяющий, как получить этот параметр (например, доступна ли сеть путем проверки некоторой переменной в каком-либо классе), но чаще я могу полностью отделить некоторую переменную от исходного класса, и она всплывает для вечность как анонимный SettingSource, который просто принимает и публикует какое-то значение настройки.

//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;
  }
});

События регистрируются аналогичным образом с объектом EventArg, заимствованным из 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
    }
  }
});
* 1008. * Есть некоторые проблемы с этими проектами, и мои клиенты просят меня рассмотреть кое-что, что имеет большую безопасность во время компиляции. Например, есть три возможности для ошибок в событиях. Для слушателя события должно быть невозможно получить тип события, для которого он не зарегистрирован (ошибка 1), но я должен по крайней мере проверить. Нет гарантии, что тип EventArg будет правильного типа или что объект, переданный в ObjectEventArg, будет соответствовать ожиданиям (ошибка 2). Нет гарантии, что переданная ссылка не является нулевой (ошибка 1). Все это так сильно зависит от хорошего поведения кода, которое не может быть гарантировано во время компиляции. Впрочем, кто-то может попытаться создать настройки для времени, которое принимает строку вместо long, и кто скажет, что правильно?

Что я могу сделать, чтобы улучшить этот дизайн, избежать некоторых неизвестных и предоставить гарантию на время компиляции?

------------- РЕДАКТИРОВАТЬ --------------- На основании ответа @Jason Albano я мог бы переписать мое решение больше так.

//Settings now return a defined type; It could still be null or the wrong type was registered
//if I go all the way with his solution I won't need to cast, but I will have one register/get method for each type.
NetworkIDSetting netID = (NetworkIDSetting) Setting.GetSetting(Setting.Type.NetworkID);

//I can still create a self-defined setting
class NetworkIDSetting() {
  private long myID=-1;

  public long getValue() {
    return myID;
  }

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

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

//i'm less certain how to create a setting that is bound more tightly to another class.
abstract class ScreenWidthSetting {
  public abstract int getWidth();

  //set width is not allowed in this case

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

//then instantiate in the required class
Setting.RegisterLongSource(new ScreenWidthSetting() {
  @Override
  public int getWidth() {
    this.getScreen().getWidth();
  }
});

//definition of an event class
class MessageReceivedEvent {
  public Message myMessage;
  public MessageReceivedEvent(Message m) {
    myMessage = m;
  }

  @Override
  public Event.Type getEventType() {
    return Event.Type.MessageReceived;
  }
}

//code from an event generator
Events.Event(new MessageReceivedEvent(messageInstance));

//I can still create an anonymous event listener with many more guarantees
//again, if I go the whole way I will have a multiplicity of very similar methods
Events.RegisterListener(Event.Type.MessageReceived, new Event.MessageListener() {
  @Override
  public void HandleEvent(MessageEvent e) {
     Message m = e.myMessage;
     if(m!=null)
     {
       this.handleMessage(m);
     }
     else
     {
       //error 3 is still unavoidable
     }
  }
} 

1 Ответ

0 голосов
/ 30 января 2020

Поддержание безопасности типов - это именно то, для чего предназначен Шаблон посетителя .

Шаблон посетителя отделяет операции, выполняемые над объектами, от самих объектов. В этом случае операции являются обработчиками, а объекты - событиями. Этот шаблон работает по принципу двойной отправки , где поведение вызова метода зависит от объекта, для которого вызывается метод, и от аргументов, предоставленных методу.

В этом случае Об обработчиках и событиях мы можем создать следующее:

public interface Handler {
    void handle(EventA a);
    void handle(EventB b);
}

public interface Event {
    void accept(Handler handler);
}

public class EventA implements Event {

    @Override
    public void accept(Handler handler) {
        handler.handle(this);
    }
}

public class EventB implements Event {

    @Override
    public void accept(Handler handler) {
        handler.handle(this);
    }
}

Может показаться утомительным продолжать реализовывать метод accept в каждом конкретном классе Event с тем же телом handler.handle(this), но это ключ к шаблону. Ссылка this поддерживает тип класса, выполняющего вызов. Так, для класса EventA, когда вызывается accept, вызов handler.handle(this) приведет к вызову Handler#handle(EventA a). Аналогично, вызов accept для объекта EventB приведет к вызову Handler#handle(EventB b).

...