Можем ли мы жить без конструкторов?

25

Допустим, по какой-то причине все объекты созданы таким образом: $ obj = CLASS :: getInstance (). Затем мы внедряем зависимости с помощью сеттеров и выполняем начальную инициализацию с помощью $ obj-> initInstance (); Есть ли реальные проблемы или ситуации, которые нельзя решить, если мы вообще не будем использовать конструкторы?

Причиной создания объекта таким образом является то, что мы можем заменить класс внутри getInstance () в соответствии с некоторыми правилами.

Я работаю в PHP, если это имеет значение

Аксель Фоли
источник
какой язык программирования?
комнат
2
PHP (почему это важно?)
Аксель Фоли
8
Похоже, что то, что вы хотите сделать, может быть достигнуто с помощью шаблона Factory.
СуперМ
1
Как примечание: упомянутый фабричный шаблон @superM - это один из способов реализации Inversion of Control (Dependency Injection).
Иоахим Зауэр
Разве это не то, что делает JavaScript с объектами?
Thijser

Ответы:

44

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

Конструкторы - отличное место для инициализации и проверки переданных параметров. Если вы больше не можете использовать их для этого, то инициализация, обработка состояний (или простое конструирование запрещенных объектов «сломанных» объектов) становится намного сложнее и частично невозможным.

Например, если каждый Fooобъект нуждается в a, Frobnicatorон может проверить в своем конструкторе, если Frobnicatorон не равен NULL. Если вы уберете конструктор, его будет сложнее проверить. Вы проверяете в каждой точке, где это будет использоваться? В init()методе (эффективно экстернализуя метод конструктора)? Никогда не проверяй и надейся на лучшее?

Хотя вы, вероятно, все еще могли бы реализовать все (в конце концов, вы все еще пытаетесь завершить), некоторые вещи сделать будет намного сложнее.

Лично я бы посоветовал изучить инъекцию зависимости / инверсию контроля . Эти методы также позволяют переключать конкретные классы реализации, но они не мешают написанию / использованию конструкторов.

Йоахим Зауэр
источник
Что вы имели в виду под «или явно отрицая конструктор« разбитых »объектов»?
Компьютерщик
2
@ Geek: конструктор может проверить свой аргумент и решить, приведут ли они к рабочему объекту (например, если ваш объект нуждается в a, HttpClientтогда он проверяет, является ли этот параметр ненулевым). И если эти ограничения не выполняются, то может возникнуть исключение. Это не совсем возможно с подходом конструкции и набора значений.
Иоахим Зауэр
1
Я думаю, что OP только что описал экстернализацию конструктора внутри init(), что вполне возможно, хотя это просто налагает больше бремени обслуживания.
Питер
26

2 преимущества для конструкторов:

Конструкторы допускают атомарные этапы построения объекта.

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

Если для создания экземпляра Fooтребуется установить 3 свойства, конструктор может взять ссылку на все 3, проверить их и выдать исключение, если они недействительны.

Инкапсуляция

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

Например, каждому Fooэкземпляру требуется либо экземпляр Barсвойства, barлибо экземпляр BarFinderсвойства barFinder. Он может использовать любой из них. Вы можете создать конструктор для каждого допустимого набора параметров и таким образом применять соглашения.

Логика и семантика для объектов живут внутри самого объекта. Это хорошая инкапсуляция.

Brandon
источник
15

Да, вы можете жить без конструкторов.

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

Но нет, вам не нужны ваши собственные конструкторы. Конечно, вам не нужны «классы» и объекты.

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

GrandmasterB
источник
Абсолютно. Можно даже жить без классов и предметов, для начала.
JensG
5
Приветствую могучего ассемблера!
Давор Адрало
9

Преимущество использования конструкторов состоит в том, что они упрощают гарантию того, что у вас никогда не будет недопустимого объекта.

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

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

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

Philipp
источник
3

$ obj = CLASS :: getInstance (). Затем мы внедряем зависимости с помощью сеттеров и выполняем начальную инициализацию с помощью $ obj-> initInstance ();

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

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

А внутри конструктора вы можете обеспечить согласованность следующим образом:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args затем можно использовать для установки закрытых членов по мере необходимости.

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

Izkata
источник
2

Я на самом деле думал о похожих вещах.

Я задал вопрос: «Что делает конструктор, и возможно ли сделать это по-другому?» Я пришел к таким выводам:

  • Это обеспечивает инициализацию некоторых свойств. Принимая их в качестве параметров и устанавливая их. Но это может быть легко осуществлено компилятором. Просто аннотируя поля или свойства как «обязательные», компилятор проверяет во время создания экземпляра, правильно ли все установлено. Вызов для создания экземпляра, вероятно, будет таким же, просто не будет никакого метода конструктора.

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

  • Некоторая более сложная логика построения. Современные шаблоны не рекомендуют делать это в конструкторе, но предлагают использовать специализированные фабричные методы или классы. Так что использование конструкторов в этом случае минимально.

Итак, чтобы ответить на ваш вопрос: да, я считаю, что это возможно. Но это потребовало бы больших изменений в дизайне языка.

И я только заметил, что мой ответ довольно ОТ.

Euphoric
источник
2

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

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

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Как ранее рекомендовал Йоахим Зауэр, вместо того, чтобы использовать Factoryдизайн-паттер, прочтите о Dependency Injection. Я бы порекомендовал почитать Dependency Injection в .NET Марка Симанна .

SOReader
источник
1

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

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

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

marcocs
источник
1

Чтобы сбалансировать некоторые другие ответы, утверждая:

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

а также

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

Такие заявления иногда предполагают, что:

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

Но это не совсем так. Большинство языков не имеют правила против передачи конструктора this( selfили любого языка, который его называет) внешнему коду. Такой конструктор полностью подчиняется правилу, указанному выше, и все же он рискует подвергнуть полу-построенные объекты внешнему коду. Это незначительный момент, но легко упускается из виду.

Дэниел Уорвикер
источник
0

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

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

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

Омега
источник