在我的一个项目中,我有两个“数据传输对象”RecordType1和RecordType2,它们继承自RecordType的抽象类。
我希望两个RecordType对象在“process”方法中由同一个RecordProcessor类处理。我的第一个想法是创建一个通用的流程方法,该方法委托给两个特定的流程方法,如下所示:
public RecordType process(RecordType record){
if (record instanceof RecordType1)
return process((RecordType1) record);
else if (record instanceof RecordType2)
return process((RecordType2) record);
throw new IllegalArgumentException(record);
}
public RecordType1 process(RecordType1 record){
// Specific processing for Record Type 1
}
public RecordType2 process(RecordType2 record){
// Specific processing for Record Type 2
}
我读过Scott Meyers在 Effective C ++ 中写下以下内容:
“任何时候你发现你自己编写的形式代码'如果对象属于T1类型,那么就做一些事情,但是如果它是T2类型,那么别做其他事情,'自己拍吧。”
如果他是正确的,显然我应该拍打自己。我真的没有看到这是多么糟糕的设计(除非当然有人将RecordType子类化并添加到RecordType3而不向处理它的通用“Process”方法添加另一行,从而创建一个NPE),以及我能想到的替代方案涉及将特定处理逻辑首当其冲地放在RecordType类本身中,这对我来说真的没有多大意义,因为理论上我可以对这些记录执行许多不同类型的处理。
有人可以解释为什么这可能被视为糟糕的设计并提供某种替代方案,仍然负责将这些记录处理到“处理”类?
更新
return null
更改为throw new IllegalArgumentException(record);
答案 0 :(得分:26)
Visitor模式通常用于此类情况。虽然代码有点复杂,但是在添加新的RecordType
子类后,必须在任何地方实现逻辑,否则将无法编译。只要instanceof
遍布整个地方,就很容易错过一两个地方。
示例:
public abstract class RecordType {
public abstract <T> T accept(RecordTypeVisitor<T> visitor);
}
public interface RecordTypeVisitor<T> {
T visitOne(RecordType1 recordType);
T visitTwo(RecordType2 recordType);
}
public class RecordType1 extends RecordType {
public <T> T accept(RecordTypeVisitor<T> visitor) {
return visitor.visitOne(this);
}
}
public class RecordType2 extends RecordType {
public <T> T accept(RecordTypeVisitor<T> visitor) {
return visitor.visitTwo(this);
}
}
用法(注意泛型返回类型):
String result = record.accept(new RecordTypeVisitor<String>() {
String visitOne(RecordType1 recordType) {
//processing of RecordType1
return "Jeden";
}
String visitTwo(RecordType2 recordType) {
//processing of RecordType2
return "Dwa";
}
});
我也建议抛出异常:
throw new IllegalArgumentException(record);
而不是在找不到任何类型时返回null
。
答案 1 :(得分:3)
我的建议:
public RecordType process(RecordType record){
return record.process();
}
public class RecordType
{
public RecordType process()
{
return null;
}
}
public class RecordType1 extends RecordType
{
@Override
public RecordType process()
{
...
}
}
public class RecordType2 extends RecordType
{
@Override
public RecordType process()
{
...
}
}
如果您需要执行的代码与模型不应该知道的内容(如UI)相关联,那么您将需要使用一种双重调度或访问者模式。
答案 2 :(得分:0)
另一种可能的方法是使process()(或者可能称之为“doSubclassProcess()”,如果它澄清事物)abstract(在RecordType中),并在子类中具有实际的实现。 e.g。
class RecordType {
protected abstract RecordType doSubclassProcess(RecordType rt);
public process(RecordType rt) {
// you can do any prelim or common processing here
// ...
// now do subclass specific stuff...
return doSubclassProcess(rt);
}
}
class RecordType1 extends RecordType {
protected RecordType1 doSubclassProcess(RecordType RT) {
// need a cast, but you are pretty sure it is safe here
RecordType1 rt1 = (RecordType1) rt;
// now do what you want to rt
return rt1;
}
}
注意一些错别字 - 想想我已经解决了所有问题。
答案 3 :(得分:0)
设计是达到目的的手段,而不知道你的目标或约束,没有人能够判断你的设计在特定情况下是否良好,或者如何改进。
但是,在面向对象的设计中,将方法实现保留在单独的类中的标准方法是visitor pattern。
仍为每种类型提供单独的实现。 PS:在代码审核中,我会标记return null
,因为它可能会传播错误而不是报告错误。考虑:
RecordType processed = process(new RecordType3());
// many hours later, in a different part of the program
processed.getX(); // "Why is this null sometimes??"
换句话说,所谓的无法访问的代码路径应该抛出异常,而不是导致未定义的行为。
答案 4 :(得分:0)
如果您的示例中有错误的设计,则在适用时不使用访客模式。
另一个是效率。与其他技术相比,instanceof
相当慢,例如使用相等性与class
对象进行比较。
使用访问者模式时,通常一个有效且优雅的解决方案是使用Map
在支持class
和访客实例之间进行映射。带有if ... else
支票的大型instanceof
区块将非常无效。
答案 5 :(得分:0)
违反SOLID的开闭原则