Сколько конструкторов должно быть в классе? - PullRequest
11 голосов
/ 29 января 2009

В настоящее время я модифицирую класс, который имеет 9 различных конструкторов. В целом, я считаю, что этот класс очень плохо спроектирован ... поэтому мне интересно, если это плохой дизайн для класса с таким количеством конструкторов.

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

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

Итак, мой вопрос, как мне исправить эту проблему? Пытаясь уменьшить количество конструкторов? Чтобы все существующие конструкторы принимали параметр ISomeManager?

Конечно, классу не нужно 9 конструкторов! ... и в довершение всего в этом файле 6000 строк кода!

Вот цензурированное представление конструкторов, о которых я говорю выше:

public MyManager()
    : this(new SomeManager()){} //this one I added

public MyManager(ISomeManager someManager) //this one I added
{
    this.someManager = someManager;
}

public MyManager(int id)
    : this(GetSomeClass(id)) {}

public MyManager(SomeClass someClass)
    : this(someClass, DateTime.Now){}

public MyManager(SomeClass someClass, DateTime someDate)
{
    if (someClass != null)
       myHelper = new MyHelper(someOtherClass, someDate, "some param");
}

public MyManager(SomeOtherClass someOtherClass)
    : this(someOtherClass, DateTime.Now){}

public MyManager(SomeOtherClass someOtherClass, DateTime someDate)
{
    myHelper = new MyHelper(someOtherClass, someDate, "some param");
}

public MyManager(YetAnotherClass yetAnotherClass)
    : this(yetAnotherClass, DateTime.Now){}

public MyManager(YetAnotherClass yetAnotherClass, DateTime someDate)
{
    myHelper = new MyHelper(yetAnotherClass, someDate, "some param");
}

Обновление:

Спасибо всем за ваши ответы ... они были превосходны!

Просто подумал, что дам обновленную информацию о том, что я в итоге сделал.

Чтобы решить проблему исключений нулевых ссылок, я изменил дополнительные конструкторы для получения ISomeManager.

В настоящий момент мои руки связаны, когда речь заходит о возможности реорганизовать этот конкретный класс, поэтому я буду помечать его как один в моем списке классов todo, чтобы изменить его, когда у меня будет свободное время. На данный момент я просто рад, что смог реорганизовать класс SomeManager ... он был таким же огромным и ужасным, как этот класс MyManager.

Когда я подойду к редизайну MyManager, я буду искать способ разделить функциональность на два или три разных класса ... или сколько потребуется, чтобы обеспечить выполнение SRP.

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

Ответы [ 16 ]

21 голосов
/ 29 января 2009

Класс должен делать одно и только одно. Если у него так много конструкторов, это, кажется, признак того, что он делает слишком много вещей.

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

15 голосов
/ 29 января 2009

Как можно меньше,
Столько, сколько нужно.

7 голосов
/ 29 января 2009

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

3 голосов
/ 06 февраля 2014

Ответ: 1 (в отношении инъекций).

Вот замечательная статья на эту тему: Анти-шаблон внедрения зависимостей: несколько конструкторов

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

Так что иметь необязательные параметры конструктора или перегруженные конструкторы не имеет смысла для меня. Ваш единственный публичный конструктор должен определять набор зависимостей вашего класса. Это контракт, который предлагает ваш класс, который гласит: «Если вы дадите мне IDigitalCamera, ISomethingWorthPhotographing и IBananaForScale, я дам вам самый лучший проклятый IPhotographWithScale, который вы можете себе представить. Любая из этих вещей, ты сам по себе ".

Вот статья Марка Симанна, в которой рассматриваются некоторые тонкие причины наличия канонического конструктора: Укажите вашу цель зависимости

3 голосов
/ 29 января 2009

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

Думайте о наборе конструкторов в классе как о математической функции, отображающей M наборов (где каждый набор является списком аргументов одного конструктора) в N экземпляров данного класса. Теперь, скажем, класс Bar может принимать Foo в одном из своих конструкторов, а класс Foo принимает Baz в качестве аргумента конструктора, как показано ниже:

    Foo --> Bar
    Baz --> Foo

У нас есть опция добавления еще одного конструктора в Bar, такая что:

    Foo --> Bar
    Baz --> Bar
    Baz --> Foo

Это может быть удобно для пользователей класса Bar, но, поскольку у нас уже есть путь от Baz к Bar (через Foo), нам не нужен этот дополнительный конструктор. Следовательно, именно здесь находится призыв к суду.

Но если мы неожиданно добавим новый класс с именем Qux, и нам понадобится создать из него экземпляр Bar: нам нужно добавить конструктор где-нибудь . Так что это может быть:

    Foo --> Bar
    Baz --> Bar
    Qux --> Bar
    Baz --> Foo

OR

    Foo --> Bar
    Baz --> Bar
    Baz --> Foo
    Qux --> Foo

У последнего будет более равномерное распределение конструкторов между классами, но будет ли оно лучшим решением, во многом зависит от того, как они будут использоваться.

2 голосов
/ 29 января 2009

Ваша проблема не в количестве конструкторов. Наличие 9 конструкторов - это больше, чем обычно, но я не думаю, что это обязательно неправильно. Это, конечно, не источник вашей проблемы. Реальная проблема заключается в том, что первоначальный дизайн был все статические методы. Это действительно особый случай, когда классы слишком тесно связаны. Классы, которые сейчас терпят неудачу, связаны с идеей, что функции являются статическими. С этим классом вы мало что можете сделать. Если вы хотите сделать этот класс нестатичным, вам придется отменить все те соединения, которые были записаны в код другими. Измените класс так, чтобы он был нестатичным, а затем обновите все вызывающие программы, чтобы сначала создать экземпляр класса (или получить его из одного объекта). Один из способов найти всех вызывающих - это сделать функции приватными и позволить компилятору сообщить вам.

При 6000 строках класс не очень сплоченный. Вероятно, он пытается сделать слишком много. В идеальном мире вы бы реорганизовали класс (и тех, кто его вызывает) в несколько меньших классов.

2 голосов
/ 29 января 2009

Не только в этом классе вы должны беспокоиться о рефакторинге. Это все остальные классы. И это, вероятно, только одна нить в запутанной системе, которая является вашей кодовой базой. У меня есть сочувствие ... Я в одной лодке. Босс хочет, чтобы все тестировалось модулем, не хочет переписывать код, чтобы мы могли тестировать модули. В конечном итоге делать некрасивые хаки, чтобы заставить это работать. Вам нужно будет переписать все, что использует статический класс, чтобы больше не использовать его, и, возможно, передать его намного больше ... или вы можете обернуть его в статический прокси-сервер, который обращается к одиночному. Таким образом, вы, по крайней мере, издеваетесь над синглтоном и тестируете таким образом.

1 голос
/ 29 января 2009

Я ограничиваю свой класс только одним реальным конструктором. Я определяю real конструктор как тот, который имеет тело. Затем у меня есть другие конструкторы, которые просто делегируют реальным в зависимости от их параметров. По сути, я заковываю свои конструкторы.

Глядя на ваш класс, есть четыре конструктора, которые имеют тело:

public MyManager(ISomeManager someManager) //this one I added
{
    this.someManager = someManager;
}

public MyManager(SomeClass someClass, DateTime someDate)
{
    if (someClass != null)
       myHelper = new MyHelper(someOtherClass, someDate, "some param");
}

public MyManager(SomeOtherClass someOtherClass, DateTime someDate)
{
    myHelper = new MyHelper(someOtherClass, someDate, "some param");
}

public MyManager(YetAnotherClass yetAnotherClass, DateTime someDate)
{
    myHelper = new MyHelper(yetAnotherClass, someDate, "some param");
}

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

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

Если вас заинтересовал этот подход, попробуйте найти копию книги Refactoring to Patterns и перейдите на страницу Конструкторы цепочек .

1 голос
/ 29 января 2009

Достаточно, чтобы выполнить свою задачу, но помните принцип единой ответственности, который гласит, что класс должен нести только одну ответственность. Имея это в виду, вероятно, очень мало случаев, когда имеет смысл иметь 9 конструкторов.

0 голосов
/ 22 февраля 2015

Конструктор должен иметь только те аргументы, которые являются обязательными для создания экземпляра этого класса. Все остальные переменные экземпляра должны иметь соответствующие методы getter и setter. Это сделает ваш код гибким, если вы планируете добавлять новые переменные экземпляра в будущем.

Фактически следуя принципу ОО -

  1. Для каждого класса дизайна стремятся к низкому сцеплению и высокой когезии
  2. Классы должны быть открыты для расширения, но закрыты для модификации.

    у вас должен быть такой дизайн -

    импорт статического org.apache.commons.lang3.Validate. *; сотрудник публичного класса { личное строковое имя; частный сотрудник () {}

    публичная строка getName () { вернуть имя; }

    открытый статический класс EmployeeBuilder { Частный выпускной Сотрудник Сотрудник;

    public EmployeeBuilder()
    {
        employee = new Employee();
    }
    
    public EmployeeBuilder setName(String name)
    {
        employee.name = name;
        return this;
    }
    
    public Employee build()
    {
        validateFields();
        return employee;
    }
    
    private void validateFields()
    {
        notNull(employee.name, "Employee Name cannot be Empty");
    }
    

    } }

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