像你这样使用反射并不理想。一旦你重命名了 Piece 类并且客户端传递了硬编码的完全限定类名,你的应用程序就会崩溃。Elite Gent 的建议避免了这个问题,但仍然要求客户知道具体的类,这正是工厂模式试图隐藏的内容。在我看来,更好的方法是使用枚举来表示 Piece 类型并让客户端将其作为参数传递给您的工厂方法,或者为每种类型创建单独的工厂方法(可行的 6 种类型)。
由于无论如何我都在拖延,这里是枚举方法的一个例子:
import java.util.ArrayList;
import java.util.List;
class PieceFactoryExample {
static enum PieceFactory {
ROOK {
Piece create() {
return new Rook();
}
};
abstract Piece create();
}
static class Field {
char line;
int row;
public Field(char line, int row) {
this.line = line;
this.row = row;
}
public String toString() {
return "" + line + row;
}
}
static interface Piece {
void moveTo(Field field);
List<Field> possibleMoves();
}
static abstract class AbstractPiece implements Piece {
Field field;
public void moveTo(Field field) {
this.field = field;
}
}
static class Rook extends AbstractPiece {
public List<Field> possibleMoves() {
List<Field> moves = new ArrayList<Field>();
for (char line = 'a'; line <= 'h'; line++) {
if (line != field.line) {
moves.add(new Field(line, field.row));
}
}
for (char row = 1; row <= 8; row++) {
if (row != field.row) {
moves.add(new Field(field.line, row));
}
}
return moves;
}
}
public static void main(String[] args) {
Piece pawn = PieceFactory.ROOK.create();
Field field = new Field('c', 3);
pawn.moveTo(field);
List<Field> moves = pawn.possibleMoves();
System.out.println("Possible moves from " + field);
for (Field move : moves) {
System.out.println(move);
}
}
}
我在这里将所有内容都设为静态以保持示例自包含。通常 Field、Piece、AbstractPiece 和 Rook 将是顶级模型类,而 PieceFactory 也将是顶级模型类。
我认为这是您的示例的更好方法,并且避免了异常处理问题。
回到这一点,您可以考虑几种方法(基于您的反射方法):
像你一样抛出 Throwable 是不好的做法,因为它将所有错误集中在一起,并且使错误处理对客户来说非常麻烦和不透明。除非您没有其他选择,否则不要这样做。
在你的方法上声明所有检查异常的“抛出”。要确定这是否有效,请考虑客户端是否应该知道并理解您抛出的异常类型。在您的示例中,想要创建 Rook 的客户端是否应该知道并理解您的反射代码抛出的 InstantiationException、IllegalAccessException 和 ClassNotFoundException?在这种情况下可能不是。
Wrap them in a runtime exception which needs not be caught by clients. This is not always a good idea. The fact that code you are calling is throwing checked exceptions usually has a reason. In your example you were doing reflection, and this can go wrong in many ways (the API declares LinkageError, ExceptionInInitializerError, ClassNotFoundException, IllegalAccessException, InstantiationException and SecurityException). Wrapping the checked exceptions in a runtime exception does not make that problem go away. I consider doing this a 'code smell'. If the error means an unrecoverable system failure, then it might be a valid choice, but in most cases you would want to handle such failures more gracefully.
Throw a custom checked exception for a complete subsystem. See for example Spring's org.springframework.dao.DataAccessException which is used to wrap all implementation specific data access exceptions. This means clients will have to catch just one exception type and can figure out the details if they need to. In your case you could create a checked PieceCreationException and use that to wrap all the reflection errors. This is a valid exception handling pattern, but I think it might be a little to heavy-handed for your PieceFactory.
Return null. You could catch all the exceptions within your code and simply return null if they occur. Just make sure your JavaDoc clearly indicates this. A drawback of this approach is that clients might have to check for nulls everywhere.
Return a specific error type. This is a geeky (very object-oriented) approach I saw in the Java core API somewhere a while back (darn, can't remember where). For this you would create an extra Piece type:
class NullPiece implements Piece {
public void moveTo(Field field) {
throw new UnsupportedOperationException("The Piece could not be created");
}
public List<Field> possibleMoves() {
throw new UnsupportedOperationException("The Piece could not be created");
}
}
And return an instance of this type when an error occurs during creation. The advantage is that it is more self-documenting than returning a simple null-value but clients would of course have to check for this using something like instanceof
. If they don't, then they will encounter the UnsupportedOperationException thrown when the Piece methods are called. A bit heavy, but very explicit. I'm not sure I would go this far, but it's still an interesting idea.