Что-то не так с моим классом Фабрики? - PullRequest
0 голосов
/ 13 января 2011
class PieceFactory {     
     @SuppressWarnings("rawtypes")
     public Piece createPiece(String pieceType) throws Throwable{
        Class pieceClass = Class.forName(pieceType);
        Piece piece = (Piece) pieceClass.newInstance();

         return piece;       
     }
}

Я еще не все привык к обработке исключений, поэтому я просто их выкидываю, но везде, где я использую метод, использующий эту фабрику, он говорит мне, что я должен генерировать исключения как throwable.

Например, в одном из моих классов у меня есть метод, который создает множество объектов, используя метод, который использует фабрику. Я могу использовать метод в этом классе, просто выдав исключение, однако оно не будет работать, если я попытаюсь передать ссылку на этот класс в другой класс, а затем использовать метод оттуда. Тогда это заставляет меня попытаться поймать исключение.

Мне, вероятно, не нужна фабрика, но она показалась мне интересной, и я хотел бы попробовать использовать шаблоны. Я создал фабрику по той причине, что у меня есть 6 подклассов Piece, и я не хочу использовать метод для их создания, передавая тип подкласса, который я хочу, в качестве аргумента метода.

Ответы [ 6 ]

1 голос
/ 13 января 2011

Я рекомендую вам поработать над написанием обработчиков исключений. В противном случае ваш единственный вариант - загадать ваш код объявлениями throws, что раздражает и вызывает трудности, как вы уже видели.

В этом случае тело вашего метода может выдать два проверенных исключения: ClassNotFoundException из вызова forname() и InstantiationException из вызова newInstance().

Как лучше всего справиться с этим, зависит от вашего приложения и ваших предпочтений. Как следует из другого ответа, один из вариантов - просто перехватить их и выбросить непроверенные исключения. Это разумно, если вы ожидаете, что эти исключения никогда не должны возникать после завершения приложения. Это, вероятно, тот подход, который я бы использовал здесь, поскольку, если бы произошло одно из этих исключений, это, вероятно, указывало бы на ошибку конфигурации.

Но если есть основания полагать, что эти исключения могут разумно возникать при обычном использовании, вам следует подумать о том, как вы хотите их обработать. Например, если имя подкласса Piece было введено пользователем в графический интерфейс пользователя (я не знаю, скорее всего, просто для пояснения), то ClassNotFoundException становится намного более вероятным, поскольку имя может быть легко написано с ошибкой. В этой ситуации, возможно, имеет смысл позволить этому методу вызвать это исключение и потребовать, чтобы вызывающая сторона перехватила и обработала его (например, предоставив пользователю сообщение о том, что запрошенный класс не существует).

1 голос
/ 13 января 2011

Исключения нужно где-то ловить. Если я правильно понял, у вас есть один класс, обернутый вокруг этой фабрики, скажем FactoryWrapper. Где-то в вашем коде вы используете этот класс-обертку, и вы можете либо выбросить свое исключение, либо перехватить его, потому что метод, в котором вы его используете, вероятно, в какой-то момент окружен (вероятно, в базовом классе) блоком try / catch, в то время как другое место (в которое вы передаете вашу ссылку FactoryWrapper), вероятно, является последним средством (это не очень хорошая практика, но это может быть метод, который никогда не вызывается), где нужно перехватить исключение.

Это всего лишь возможное объяснение того, почему вы не можете бросить исключение в другом классе. Как уже упоминалось, попробуйте не использовать рефлексию, потому что это намного медленнее, чем другие альтернативы.

Вы можете использовать одну абстрактную фабрику, а затем фабрику для каждого типа. Подробнее здесь и здесь . Надеюсь, это поможет.

РЕДАКТИРОВАТЬ:

Здесь - интересная статья об отражении. Это даст вам представление, когда его использовать.

1 голос
/ 13 января 2011

Вы пытаетесь рефлексивно создать объект Piece.

Class.forName() выбрасывает ClassNotFoundException, в то время как Class.newInstance() выдает InstantiationException, IllegalAccessException (следовательно, вам нужно бросить Throwable.

Лучший способ создать объект с помощью типов классов, вероятно, сделать следующее:

class PieceFactory {     

    public Piece createPiece(String pieceType) throws Throwable{
        Piece piece = null;

        if ("SubPiece1".equals(pieceType)) {
            piece = new SubPiece1();
        } else if ("SubPiece2".equals(pieceType)) {
            piece = new SubPiece2();
        }

        return piece;       
    }
}

PS , он не проверен, просто показывает лучший способ сделать это.

Надеюсь, это поможет! :)

1 голос
/ 13 января 2011

Оба @SuppressWarnings и throws Throwable звонят в колокола тревоги. См. Эффективная Java от Bloch.

1 голос
/ 13 января 2011

Если вы не хотите везде объявлять операторы throw или блок try / catch.

Создайте класс и расширяйте класс RuntimeException или используйте сам RuntimeException или связанные * RuntimeException расширяющие классы.

Затем поместите блок try-catch в корень (методы фабричного класса) и оберните исключения, выброшенные в тип исключения Runtime (один из указанных выше, какой бы вы ни выбрали).

Однако это действительно зависит от того, что вам нужно.В какой-то момент вам все равно придется обрабатывать исключения, иначе ваше приложение будет генерировать исключения во время выполнения, и приложение не будет делать то, что ожидалось, если вы их не обрабатываете.Это не значит, что все будет хорошо работать.

0 голосов
/ 13 января 2011

Использование отражения, как вы, не идеально. Как только вы переименуете класс 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. Немного тяжелый, но очень явный. Я не уверен, что зайду так далеко, но это все еще интересная идея.

...