面向对象设计 - 何时需要共同的基类[缺陷设计?]

3

我有一个简化的设计,如下所示(基本上是一堆处理程序来处理两种不同类型的请求:EventRequest和SpeechRequest)。以下是伪代码:

class SpeechRequest {sessionId: String; slot: String}
class EventRequest {sessionId: String; event: String}

class SpeechRequestHandler; 
class EventRequestHandler;

class SpeechRequestHandler[A/B/C] extends SpeechRequestHandler {
    - handle(request: SpeechRequest) {      
        doSt(request.slot)
    }
}

class EventRequestHandler[A/B/C] extends EventRequestHandler {
    - handle(request: EventRequest) {       
        doSt(request.event)     
    }   
}

有两种不同的调度程序用于查找适当的处理程序以处理每种类型的请求并将它们转发到处理程序进行处理:

class SpeechDispatcher {
    - handle(request: SpeechRequest) {
        handler: SpeechRequestHandler = findHandlerToHandle(request);
        handler.handle(request);
    }
}

class EventDispatcher {
    - handle(request: EventRequest) {
        handler: EventRequestHandler = findHandlerToHandle(request);
        handler.handle(request);
    }
}

现在,我想重构并创建一个基础/通用类。自然而然地,我想到了这个:
class Request {sessionId: String}
class SpeechRequest extends Request {slot: String}
class EventRequest extends Request {event: String}

class RequestHandler {
    - canHandleRequest(Request): bool
    - handle(Request)
}

class SpeechRequestHandler extends RequestHandler {
    - canHandleRequest(request: Request): bool = request instanceof SpeechRequest
}

class EventRequestHandler extends RequestHandler {
    - canHandleRequest(request: Request): bool = request instanceof EventRequest
}

class SpeechRequestHandler[A/B/C] extends SpeechRequestHandler {
    - handle(Request: request) {
        //need to cast to specific type to extract a certain fields to do some operation
        //!! I feel something is not right because of that
        speechRequest:SpeechRequest = (SpeechRequest)request;
        doSt(speechRequest.slot)

        //other operation can work with base Request object; so it's OK
    }
}

class EventRequestHandler[A/B/C] extends EventRequestHandler {
    - handle(Request: request) {
        eventRequest:EventRequest = (EventRequest)request;
        doSt(eventRequest.event)

        //other operation can work with base Request object; so it's OK
    }   
}

对于所有SpeechRequestHandler[A/B/C]:handle函数,我现在需要将Request对象强制转换为(SpeechRequest)对象,即speechRequest:SpeechRequest = (SpeechRequest)request;。
我觉得我的设计存在缺陷。如果每个SpeechRequestHandler都需要将对象转换为(SpeechRequest),以便我可以使用这些信息,那么在这种情况下重构基类是否没有意义?
请问您能否提供更好的方法或设计模式来清洁地处理这个问题。
谢谢。

您可能需要标记正确的语言。 - Tom
@Tom:我的原始代码是用Java编写的,但上面的代码(混合了一些Scala风格)使得在这里表示代码更短。 - auxdx
你能否使用在RequestHandler上定义的泛型来约束实现中预期事件的类型? - MadProgrammer
@MadProgramer:不,我不想使用泛型,因为我的处理程序是由Spring Bean初始化的,而且它与泛型一起使用效果不太好。此外,它会增加额外的复杂性。我只是想知道将类强制转换为特定类型是否正常? - auxdx
2个回答

0

如果您想保持类型安全,可以使用双重分派来完成此类操作。请参见https://en.wikipedia.org/wiki/Double_dispatch

看起来您需要类似于这样的东西:

interface HandlerSet{
    SpeechHandler getSpeechHandler(Request request);
    EventHandler getEventHandler(Request request);
}

abstract Request {
    ... common stuff ...
    abstract handleWith(HandlerSet handlers);
}
class SpeechRequest extends Request {
    sessionId: String; slot: String
    handleWith(HandlerSet handlers) {
        return handlers.getSpeechHandler(this).handle(this);
    }
}
class EventRequest extends Request {
    sessionId: String; event: String
    handleWith(HandlerSet handlers) {
        return handlers.getEventHandler(this).handle(this);
    }
}

0
主要问题在于您引入了一种可以与每种类型的请求一起调用的方法,但是请求的具体处理程序必须知道具体类型。
类似这样的东西。
 public void handleRequest(Request request){
      // Find the correct handler
      for(RequestHandler requestHandler : requestHandlers){
           if(requestHandler.canHandle(request)){
               // Opps, the handler needs the concrete type
               requestHandler.handle(request);
           }
      }
 }

在运行时,唯一具有足够信息以知道哪个处理程序可以处理它的对象是Request。因此,您必须要求请求选择一个特定的目标方法来调用。

这被称为访问者模式

public interface Request {
    public void accept(RequestVisitor requestVisitor);
}

public interface RequestVisitor {
    public void visit(SpeechRequest speechRequest);
    public void visit(EventRequest eventRequest);
}

现在,Request的实现可以决定调用RequestVisitor的哪个回调函数:
public class SpeechRequest implements Request {
    public void accept(RequestVisitor requestVisitor) {
        requestVisitor.visit(this);
    }
}

public class EventRequest implements Request {
    public void accept(RequestVisitor requestVisitor) {
        requestVisitor.visit(this);
    }
}

具体处理程序可以使用具体请求对象。

public class SpeechRequestHandler {
    public void handle(SpeechRequest request) {
        System.out.println(request.getClass().getSimpleName());
    }
}

public class EventRequestHandler {
    public void handle(EventRequest request) {
        System.out.println(request.getClass().getSimpleName());
    }
}

从客户端的角度来看,访问者可能会像这样使用:

SpeechRequestHandler speechRequestHandler = new SpeechRequestHandler();
EventRequestHandler eventRequestHandler = new EventRequestHandler();

RequestVisitor requestVisitor = new RequestVisitor() {

    @Override
    public void visit(EventRequest eventRequest) {
        eventRequestHandler.handle(eventRequest);
    }

    @Override
    public void visit(SpeechRequest speechRequest) {
        speechRequestHandler.handle(speechRequest);
    }
};

Request request1 = new SpeechRequest();
Request request2 = new EventRequest();

request1.accept(requestVisitor);
request2.accept(requestVisitor);

但是使用访问者模式通常(但不总是)表明您尝试使界面过于抽象。这就是为什么您会丢失后来必须重新创建的类型信息。也许您会找到更好的设计。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接