Что-то не так с моим классом Factory?

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


person Smooth Operator    schedule 13.01.2011    source источник
comment
Почему вы не обрабатываете свои исключения в строках, которые их создают в createPiece?   -  person justkt    schedule 13.01.2011
comment
Гораздо лучше избегать отражения.   -  person Tom Hawtin - tackline    schedule 13.01.2011
comment
Спасибо всем! Я немного изменил свой код и на этот раз не использовал отражение.   -  person Smooth Operator    schedule 13.01.2011


Ответы (6)


Вы пытаетесь рефлективно создать объект 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, это не проверено, просто показано, как это сделать лучше.

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

person Buhake Sindi    schedule 13.01.2011
comment
Ваши предложения требуют прохождения класса. Как вызывающий код должен получить объект Class в первую очередь? - person Dave Costa; 13.01.2011
comment
Спасибо! Извините, я не очень привык к обозначениям :). Что значит - person Smooth Operator; 13.01.2011
comment
@ Дэйв Коста, правда .... хорошо замечено, я прочитал его спецификацию и жестко закодировал оттуда. Я обновлю этот пост. - person Buhake Sindi; 13.01.2011
comment
Спасибо, я заметил, что кажется проще :). Пытался сделать какой-то причудливый код, который не слишком хорошо получился: P - person Smooth Operator; 13.01.2011

И @SuppressWarnings, и throws Throwable бьют тревогу. См. Effective Java от Bloch.

person Raedwald    schedule 13.01.2011
comment
Я слышал об этом, вроде хорошо. Просто нужно сначала лучше изучить основы :). Но разве это не допустимый метод для создания объектов? Почему исключения? - person Smooth Operator; 13.01.2011

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

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

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

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

try{ 
    "your code which throws exception" 
} catch(ThrownExceptionType e){ 
   throw new RuntimrException(e);
}

Помните, что вам все еще нужно обработать исключение. Это не значит, что все будет хорошо.

person fmucar    schedule 13.01.2011
comment
Правильно, спасибо! Как мне обернуть исключения? :) Я действительно не понимаю, почему исключения вообще существуют, разве не должно работать такое создание объектов? Я имею в виду, что я пишу в блоке try-catch? - person Smooth Operator; 13.01.2011
comment
@Alex См. выше, отредактировал сообщение. Исключение составляют исключительные случаи. Компьютер не может решить, что делать автоматически, если он не может найти класс pieceType Class.forName(pieceType) для загрузки. Вы также должны сказать ему, что делать в исключительных случаях. - person fmucar; 13.01.2011
comment
@Alex: Исключения хороши (только один пример) для проблем, которые вы не можете предвидеть, например, если файл, к которому вы пытаетесь получить доступ, не существует или что-то подобное. Это хороший способ остановить поток выполнения и не пытаться сделать что-то невозможное, это ошибочно, поэтому выполнение продолжается в блоке try/catch. - person Atticus; 13.01.2011

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

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

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

ИЗМЕНИТЬ:

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

person Atticus    schedule 13.01.2011
comment
Спасибо! Фабрика находится внутри класса Piece, в котором у меня также есть метод, использующий Фабрику. Является ли класс Piece оберткой? Итак, что-нибудь другое, чем использование отражения, вероятно, является лучшей альтернативой? - person Smooth Operator; 13.01.2011
comment
@Alex: да, я имел в виду ваш класс Piece как обертку. Я бы рассматривал размышления только в том случае, если это действительно важно. Я обновил свой ответ ссылкой, которая объясняет, когда использовать отражение. - person Atticus; 13.01.2011

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

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

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

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

person Dave Costa    schedule 13.01.2011

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