我正在使用具有多个项目和插件的客户端Java应用程序。它们的设计非常脆弱,因为在这些分区之间共享任何信息都涉及一些God类,这些类实例化并注册了实现特定接口的类。这些接口未经检查就激增,并且通常包含没有客户端需要的合同方法。
为了使所有事物脱钩,我提出了一些交换所对象,它们处理“设置”和“事件”。任何类都可以请求设置,并通过枚举指定设置的类型和期望的值的类型。任何类都可以注册一个设置源,该实现源实现一个接口,该接口定义了如何获取该设置(例如,通过检查某个类中的某个变量来确定网络是否可用),但是更常见的是,我可以将某些变量与原始类完全分开,并且它浮动为永恒作为一个匿名的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;
}
});
事件以类似的方式被从C#大量借用的EventArg对象注册。
//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
}
}
});
这些设计存在一些问题,我的客户要求我考虑一些具有更高的编译时安全性的东西。例如,事件中存在三种错误机会。事件侦听器应该不可能获得未注册的事件类型(错误1),但我至少应该检查一下。不保证EventArg类型将是正确的类型,也不保证在ObjectEventArg中传递的对象将符合期望(错误2)。无法保证传递的引用不为空(错误1)。所有这些都极大地取决于代码的良好行为,而这些行为在编译时无法保证。为此,有人可以尝试为时间创建设置,该设置接受字符串而不是字符串,谁说这是正确的?
我该怎么做才能改进此设计,避免某些未知情况并提供编译时保证?
-------------编辑---------------根据@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
}
}
}
维护类型安全正是Visitor Pattern设计的目的。
访客模式将对对象执行的操作与对象本身分开。在这种情况下,操作是处理程序,而对象是事件。此模式基于double dispatch原理工作,其中方法调用的行为因调用该方法的对象和提供给该方法的参数而异。
对于处理程序和事件,我们可以创建以下内容:
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)
。