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