Использование отражения, как вы, не идеально. Как только вы переименуете класс Piece, и клиенты передадут жестко закодированные полные имена классов, ваше приложение сломается. Предложение Elite Gent позволяет избежать этой проблемы, но все же требует, чтобы клиенты знали конкретный класс, который именно то, что пытается скрыть фабричный шаблон. На мой взгляд, лучшим подходом было бы либо использовать enum для представления типов Piece и позволить клиенту передавать это в качестве аргумента вашему фабричному методу, либо создавать отдельные фабричные методы для каждого типа (с шестью типами штук, которые возможны).
Так как я все равно откладываю, вот пример подхода enum:
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 также будет верхнего уровня.
Я думаю, что это лучший способ для вашего примера, и он позволяет избежать проблемы обработки исключений.
Возвращаясь к этому, есть несколько подходов, которые вы можете рассмотреть (в зависимости от вашего подхода к рефлексии):
Throwing Throwable, как и вы, - плохая практика, поскольку он объединяет все ошибки и делает обработку ошибок очень трудоемкой и непрозрачной для клиентов. Не делайте этого, если у вас нет других вариантов.
Объявите 'throws' в вашем методе для всех проверенных исключений. Чтобы решить, является ли это допустимым, подумайте, должен ли клиент знать и понимать тип исключения, которое вы генерируете. В вашем примере, должен ли клиент, который хочет создать Rook, знать и понимать исключения InstantiationException, IllegalAccessException и ClassNotFoundException, созданные вашим кодом отражения? Вероятно, не в этом случае.
Оберните их в исключение времени выполнения, которое не должно быть перехвачено клиентами. Это не всегда хорошая идея. Тот факт, что код, который вы вызываете, генерирует проверенные исключения, обычно имеет причину. В вашем примере вы делали рефлексию, и это может пойти не так во многих отношениях (API объявляет LinkageError, ExceptionInInitializerError, ClassNotFoundException, IllegalAccessException, InstantiationException и SecurityException). Упаковка отмеченных исключений в исключение времени выполнения не устраняет эту проблему. Я считаю это «запахом кода». Если ошибка означает неисправимый системный сбой, то это может быть правильным выбором, но в большинстве случаев вы бы хотели обрабатывать такие сбои более изящно.
Создайте пользовательское проверенное исключение для полной подсистемы. См., Например, Spring org.springframework.dao.DataAccessException, который используется, чтобы обернуть все специфические для реализации исключения доступа к данным. Это означает, что клиенты должны будут отлавливать только один тип исключений и могут выяснить подробности, если это необходимо В вашем случае вы можете создать проверенную исключительную ситуацию PieceCreationException и использовать ее для переноса всех ошибок отражения. Это допустимый шаблон обработки исключений, но я думаю, что он может быть слишком сложным для вашей PieceFactory.
Вернуть ноль. Вы можете перехватить все исключения в вашем коде и просто вернуть null, если они возникнут. Просто убедитесь, что ваш JavaDoc четко указывает на это. Недостатком этого подхода является то, что клиенты могут везде проверять наличие нулей.
Возвращает определенный тип ошибки. Это вызывающий (очень объектно-ориентированный) подход, который я видел в API ядра Java некоторое время назад (черт, не помню где). Для этого вы должны создать дополнительный тип пьесы:
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");
}
}
И возвращать экземпляр этого типа при возникновении ошибки во время создания. Преимущество состоит в том, что это больше самодокументируется, чем возвращает простое нулевое значение, но клиентам, конечно, придется проверять это, используя что-то вроде instanceof
. Если этого не произойдет, то они столкнутся с UnsupportedOperationException, генерируемым при вызове методов Piece. Немного тяжелый, но очень явный. Я не уверен, что зайду так далеко, но это все еще интересная идея.