Использование отражения, как и вы, не является идеальным. Как только вы переименуете класс 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, знать и понимать InstantationException, исключение IllegalAccessException и ClassNotFoundException, созданное вашим кодом отражения? Наверное, не в этом случае.
Оберните их в исключение времени выполнения, которое не должно быть уловлено клиентами. Это не всегда хорошая идея. Тот факт, что код, который вы вызываете, выбрасывает проверенные исключения, обычно имеет причину. В вашем примере вы делали отражение, и это может пойти не так во многих отношениях (API объявляет LinkageError, ExceptionInInitializerError, ClassNotFoundException, IllegalAccessException, InstantiationException и SecurityException). Приобретение проверенных исключений в исключении времени выполнения не приводит к тому, что проблема исчезнет. Я считаю, что это «запах кода». Если ошибка означает неустранимый системный сбой, тогда это может быть допустимый выбор, но в большинстве случаев вы хотели бы обработать такие сбои более изящно.
Выполните специальное проверочное исключение для полной подсистемы. См. Например, org.springframework.dao.DataAccessException Spring, который используется для обертывания всех исключений доступа к конкретным данным для реализации. Это означает, что клиентам придется поймать только один тип исключения и может выяснить детали, если это необходимо. В вашем случае вы можете создать проверенное исключение PieceCreationException и использовать его для обертывания всех ошибок отражения. Это допустимая схема обработки исключений, но я думаю, что это может быть немного тяжело для вашего PieceFactory.
Обратный null. Вы можете поймать все исключения в вашем коде и просто вернуть null, если они произойдут. Просто убедитесь, что ваш JavaDoc четко указывает это. Недостатком этого подхода является то, что клиентам, возможно, придется проверять нули везде.
Возврат определенного типа ошибки. Это geeky (очень объектно-ориентированный) подход, который я видел в 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. Немного тяжелый, но очень явный. Я не уверен, что я зашел так далеко, но это все еще интересная идея.
Почему вы не обрабатываете свои исключения на линиях, которые производят их в 'createPiece'? – justkt
Гораздо лучше избегать отражения. –
Спасибо всем! Я немного изменил свой код и не использовал рефлексию на этот раз. –