0

Фабрика в Java: как создавать конкретные объекты с различными параметрами конструктора

12

Я пытаюсь реализовать паттерн "Фабрика" в Java. У меня есть класс Shape, от которого наследуются Circle и Triangle. Проблема в том, что конструктор Shape принимает только 2 параметра, в то время как Circle принимает 3 параметра, и Triangle тоже (я не буду показывать код для Triangle, так как он идентичен циклу).

Чтобы лучше продемонстрировать:

private interface ShapeFactory {
    public Shape create(int x, int y);
}

private class CircleFactory implements ShapeFactory {
    public Shape create(float radius, int x, int y) { // ошибка
        return new Circle(radius, x, y);
    }
}

Есть идеи, как обойти эту проблему? Я не должен получать ввод от пользователя внутри фабрики (он должен поступать извне).

Спасибо!

5 ответ(ов)

0

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

0

Ваши реализации должны принимать одинаковое количество аргументов, у вас есть три варианта:

  1. Позвольте фабрике хранить дополнительные аргументы, чтобы вам нужно было предоставлять только центр, к примеру.
  2. Позвольте фабрике принимать все аргументы, даже если некоторые из фабрик могут игнорировать часть из них.
  3. Используйте аргумент переменной длины, например, 'double...'. Однако проблема в том, что вызывающая сторона должна знать, что именно требуется фабрике, что, по моему мнению, подрывает идею фабрики.
0

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

public interface ShapeArguments {
}

public class CircleArguments implements ShapeArguments {
    public double radius;
    public double x;
    public double y;

    public CircleArguments(double radius, double x, double y) {
        this.radius = radius;
        this.x = x;
        this.y = y;
    }
}

private interface ShapeFactory {
    Shape create(ShapeArguments args);
}

private class CircleFactory implements ShapeFactory {
    public Shape create(ShapeArguments args) {
        CircleArguments circleArgs = (CircleArguments) args;
        return new Circle(circleArgs.radius, circleArgs.x, circleArgs.y);
    }
}

Если между аргументами фигур есть общие параметры, вы можете использовать наследование, чтобы упростить управление ими.

0

Имея интерфейс Shape, обычно это является плохим дизайном, потому что он очень ограничен. Для описания различных форм требуется разная информация. Хорошим примером этого является метод изменения размера. Для круга нужно изменить радиус, а для прямоугольника – изменить обе стороны, что означает передачу двух параметров вместо одного.

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

private interface ShapeFactory {
    public Shape create(float x, float y, float width, float height);
}

private class CircleFactory implements ShapeFactory {
    public Shape create(float x, float y, float width, float height) {
        float radius = Math.min(width, height);
        return new Circle(radius, x, y);
    }
}

Еще одна идея заключается в том, что вы можете использовать фабрику другим способом (хотя предыдущий вариант тоже может быть хорош, в зависимости от ваших нужд):

private interface ShapeFactory {
    public Shape create(float x, float y, float width, float height, boolean isCircle);
}

private class MyShapeFactory implements ShapeFactory {
    public Shape create(float x, float y, float width, float height, boolean isCircle) {
        if (isCircle)
            return new Circle(Math.min(width, height), x, y);
        else
            return new Rectangle(width, height, x, y);
    }
}

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

Действительно важно, хочет ли верхний уровень кода знать, какой именно реализацией Shape он получает в ответ. В некоторых случаях может произойти так, что вам нужно будет переработать это в общий дескриптор. Например, у вас не обязательно есть метод Shape.scale(width, height), и если он существует, вы не сможете изменить размер своего круга или прямоугольника, потому что, как и в случае с конструкторами, масштабирование там различное. Но если все, что вам нужно, – это вызвать что-то вроде Shape.draw(canvas), думаю, вы на правильном пути.

Я нашел похожий вопрос с аналогичным ответом, возможно, это тоже поможет вам: https://softwareengineering.stackexchange.com/a/389507/65755

0

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

Согласно книге (Шаблоны проектирования):

Еще одна вариация данного паттерна позволяет фабричному методу создавать несколько типов продуктов. Фабричный метод принимает параметр, который определяет, какой именно объект нужно создать. Все объекты, создаваемые фабричным методом, будут реализовывать интерфейс Product. В примере с документами приложение может поддерживать разные виды документов. Вы передаете методу CreateDocument дополнительный параметр, чтобы указать, какой тип документа нужно создать.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь