Считается ли плохим дизайном выполнение длительных операций в конструкторе?

Я реализую класс для сравнения деревьев каталогов (на C#). Сначала я реализовал собственно сравнение в конструкторе класса. Нравится:

DirectoryComparer c = new DirectoryComparer("C:\\Dir1", "C:\\Dir2");

Но делать возможную длительную операцию в конструкторе не кажется «правильным». Альтернативный способ - сделать конструктор закрытым и добавить статический метод, подобный этому:

DirectoryComparer c = DirectoryComparer.Compare("C:\\Dir1", "C:\\Dir2");

Что вы думаете? Вы ожидаете, что конструктор будет «быстрым»? Второй пример лучше или просто усложняет использование класса?

КСТАТИ:

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

Редактировать:

Просто чтобы немного прояснить мой пример. Меня не только интересует, отличаются ли каталоги, меня также интересует, чем они отличаются (какие файлы). Так что простого возвращаемого значения int будет недостаточно. Ответ cdragon76.myopenid.com на самом деле довольно близок к тому, что я хочу (+1 к вам).

Если вы не отметите ответ, возможно, это должна быть вики сообщества?

Peter Lillevold 13.03.2009 10:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
1
807
14

Ответы 14

Я предпочитаю второй.

Я ожидаю, что конструктор создаст экземпляр класса. Метод compare делает то, для чего он предназначен.

Вы никогда не должны делать ничего, что может привести к сбою в конструкторе. Вы не хотите создавать недопустимые объекты. Хотя вы можете реализовать состояние «зомби», в котором объект мало что делает, гораздо лучше выполнять любую сложную логику отдельными методами.

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

Jon Skeet 06.11.2008 23:06

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

James 07.11.2008 17:38

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

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

Я думаю, что интерфейс может быть тем, что вам нужно. Я бы создал класс для представления каталога и реализовал бы интерфейс DirectoryComparer. Этот интерфейс будет включать метод сравнения. Если в C# уже есть интерфейс Comparable, вы также можете просто реализовать его.

В коде ваш вызов будет таким:

D1 = new Directory("C:\");
..
D1.compare(D2);

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

Я бы подумал, что комбинация из двух является «правильным» выбором, поскольку я ожидал бы, что метод Compare вернет результат сравнения, а не сам компаратор.

DirectoryComparer c = new DirectoryComparer();

int equality = c.Compare("C:\\Dir1", "C:\\Dir2");

... и, как упоминает Дана, в .Net есть интерфейс IComparer, который отражает этот шаблон.

Метод IComparer.Compare возвращает int, поскольку классы IComparer используются в основном с сортировкой. Однако общая картина соответствует проблеме вопроса в том, что:

  1. Конструктор инициализирует экземпляр с (необязательно) параметрами «настройки»
  2. Метод Compare принимает два параметра "данных", сравнивает их и возвращает "результат".

Теперь результатом может быть int, bool, набор различий. Все, что соответствует потребностям.

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

  • Сравните еще раз - что, если каталоги изменились?
  • Измените файлы, которые вы сравниваете, обновив участников.

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

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

Поэтому я предпочитаю:

DirectoryComparer = new DirectoryComparer(&Dir1,&Dir2);

DirectoryComparer-> Сравнить ();

Или же

DirectoryComparer = new DirectoryComparer();

DirectoryComparer-> Сравнить (& Dir1, & Dir2);

Я согласен, и у вас все еще может быть одна строка, указав new DirectoryComparer (a, b) .Compare ()

Don Kirkby 06.11.2008 23:29

@Don: Бывший коллега назвал это «попыткой сделать слишком много сексуальных вещей в одной строке». Если новый не работает по какой-либо причине, вы только что вызвали исключение, которое трудно отследить.

tloach 07.11.2008 17:33

Если вы работаете с C#, вы можете использовать методы расширения для создания метода сравнения двух каталогов, которые вы присоедините к сборке в DirectoryClass, чтобы он выглядел примерно так:

Directory dir1 = new Directory("C:\.....");
Directory dir2 = new Directory("D:\.....");

DirectoryCompare c = dir1.CompareTo(dir2);

Это было бы намного более понятной реализацией. Подробнее о методах расширения здесь.

Если операция может занять неизвестное количество времени, вы, возможно, захотите экспортировать эту операцию в другой поток (чтобы ваш основной поток не блокировался и мог делать другие вещи, например, показывать вращающийся индикатор хода выполнения). Другие приложения могут не захотеть этого делать, они могут захотеть все в одном потоке (например, те, у которых нет пользовательского интерфейса). Перенести создание объекта в отдельный поток, ИМХО, немного неудобно. Я бы предпочел создать объект (быстро) в моем текущем потоке, а затем просто позволить его методу работать в другом потоке, и как только метод завершит работу, другой поток может умереть, и я могу получить результат этого метода в моем текущий поток, используя другой метод объекта перед сбросом объекта, так как я счастлив, как только узнаю результат (или сохраняю копию, если результат включает в себя больше деталей, мне, возможно, придется использовать по одному).

Я согласен с общим мнением не выполнять длительные операции внутри конструкторов.

Кроме того, говоря о дизайне, я бы подумал об изменении вашего второго примера, чтобы метод DirectoryComparer.Compare возвращал что-то иное, чем объект DirectoryComparer. (Возможно, новый класс под названием DirectoryDifferences или DirectoryComparisonResult.) Объект типа DirectoryComparer звучит как объект, который вы бы использовали для сравнения каталогов, в отличие от объекта, который представляет различия между парой каталогов.

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

Например:

DirectoryComparer comparer = new DirectoryComparer(
    DirectoryComparerOptions.IgnoreDirectoryAttributes
);
DirectoryComparerResult result = comparer.Compare("C:\\Dir1", "C:\\Dir2");

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

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

Ни в том, ни в другом случае конструктор не выполняет никакой работы, кроме инициализации экземпляра. В случае двух вышеупомянутых алгоритмов либо управляется клиентом, как, например, Iterator, либо потоком прослушивания событий.

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

Концептуально конструктор - это функция, отображающая представление объекта на объект, который он представляет.

По определению выше Integer.valueOf (1) на самом деле больше конструктор, чем new Integer (1), потому что Integer.valueOf (1) == Integer.valueOf (1). , В любом случае эта концепция также означает, что все аргументы коснструктора и только аргумент конструктора должны определять равное поведение объекта.

Обязательно сделаю второе.

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

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

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

Конструктор - это то, что следует использовать для инициализации объекта, метод должен использоваться для «чего-то», т.е. иметь функцию.

Я думаю, что конструктор не только может потратить столько времени, сколько необходимо для создания действительного объекта, но и конструктор должен это сделать. Откладывать создание объекта очень плохо, так как вы получаете потенциально недопустимые объекты. Итак, вам придется проверять объект каждый раз, прежде чем прикоснуться к нему (именно так это делается в MFC, у вас везде есть методы bool IsValid()).

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

Что делает класс DirectoryComparer? Что это за ответственность? С моей точки зрения (которая является точкой зрения программиста на C++) похоже, что вам было бы лучше использовать бесплатную функцию, но я не думаю, что у вас могут быть бесплатные функции на C#, не так ли? Думаю, вы соберете файлы, которые разные в объекте DirectoryComparer. Если это так, вам лучше создать что-то вроде массива файлов или эквивалентного класса с соответствующим именем.

Другие вопросы по теме