Значительное количество времени я не могу придумать причину иметь объект вместо статического класса. Есть ли у объектов больше преимуществ, чем я думаю? [закрыто]


9

Я понимаю концепцию объекта, и, как программист на Java, я чувствую, что парадигма ОО приходит на практике довольно естественно.

Однако недавно я подумал:

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

Я мог бы подумать о двух преимуществах использования объекта (оба являются значительными и мощными):

  1. Полиморфизм: позволяет динамически и гибко менять функциональность во время выполнения. Также позволяет легко добавлять новые функциональные возможности и детали в систему. Например, если есть Carкласс, предназначенный для работы с Engineобъектами, и вы хотите добавить новый Engine в систему, которую может использовать Car, вы можете создать новый Engineподкласс и просто передать объект этого класса в Carобъект без необходимости изменить что-нибудь о Car. И вы можете решить сделать это во время выполнения.

  2. Возможность «передавать функциональность»: вы можете динамически передавать объект по системе.

Но есть ли еще преимущества для объектов перед статическими классами?

Часто, когда я добавляю новые «детали» в систему, я делаю это, создавая новый класс и создавая для него объекты.

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

Например, я работаю над добавлением механизма сохранения / загрузки файла в мое приложение.

С объектом вызывающая строка кода будет выглядеть так: Thing thing = fileLoader.load(file);

Со статическим классом это будет выглядеть так: Thing thing = FileLoader.load(file);

Какая разница?

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

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

РЕДАКТИРОВАТЬ: уточнить. Я нахожу объекты очень полезными при обмене функциональностью или передаче данных. Например, я написал приложение, которое составляет мелодии. MelodyGeneratorбыло несколько подклассов, которые по-разному создают мелодии, и объекты этих классов были взаимозаменяемыми (шаблон Стратегии).

Мелодии тоже были объектами, потому что их полезно передавать. Так были аккорды и весы.

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


Таким образом, вместо объекта с полями и, возможно, методами, без объектов, без записей, просто со множеством скалярных значений, передаваемых и возвращаемых статическими методами? Изменить: ваш новый пример предлагает иначе: объекты, просто без методов экземпляра? Иначе что Thing?

Что происходит, когда вам нужно поменять свой FileLoaderна тот, который читает из сокета? Или макет для тестирования? Или тот, который открывает ZIP-файл?
Бенджамин Ходжсон


1
@delnan Хорошо, я понял вопрос, ответ на который поможет мне понять: почему вы реализуете «статическую» часть системы как объект? Как пример в вопросе: механизм сохранения файла. Что я получу от реализации этого в объекте?
Авив Кон

1
По умолчанию следует использовать нестатический объект. Используйте статические классы, только если вы чувствуете, что это действительно выражает ваши намерения лучше. System.Mathв .NET - пример чего-то, что имеет гораздо больший смысл как статический класс: вам никогда не понадобится менять его или высмеивать его, и ни одна из операций не может быть логически сделана частью экземпляра. Я действительно не думаю, что ваш «спасительный» пример соответствует этому счету.
Бенджамин Ходжсон

Ответы:


14

LOL, вы звучите как команда, над которой я работал;)

Java (и, вероятно, C #), безусловно, поддерживает этот стиль программирования. И я работаю с людьми, чей первый инстинкт: «Я могу сделать это статическим методом!» Но есть некоторые тонкие затраты, которые со временем вас настигнут.

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

Переходя к классу с использованием только статических методов, вы нарушаете часть «состояния» уравнения. Но государству еще где-то жить. Итак, со временем я увидел, что класс со всеми статическими методами начинает иметь все более и более сложные списки параметров, потому что состояние перемещается из класса в вызовы функций.

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

2) Правила ОО довольно хорошо поняты. Через некоторое время вы можете посмотреть на дизайн класса и посмотреть, соответствует ли он критериям, подобным SOLID. И после большого количества практических юнит-тестов вы развиваете понимание того, что делает класс «правильным» и «связным». Но не существует хороших правил для класса со всеми статическими методами, и нет реальной причины, по которой вы не должны просто связывать все там. Класс открыт в вашем редакторе, так что, черт возьми? Просто добавьте туда свой новый метод. Через некоторое время ваше приложение превращается в ряд конкурирующих «Объектов Бога», каждый из которых пытается доминировать в мире. Опять же, рефакторинг их в более мелкие единицы очень субъективен, и трудно сказать, правильно ли вы это сделали.

3) Интерфейсы являются одной из самых мощных функций Java. Наследование классов оказалось проблематичным, но программирование с интерфейсами остается одним из самых мощных трюков языка. (то же самое на C #). Все статические классы не могут быть помещены в эту модель.

4) Это хлопает дверью по важным ОО методам, которыми вы не можете воспользоваться. Таким образом, вы можете годами работать только с молотком в своем наборе инструментов, даже не осознавая, насколько проще было бы, если бы у вас тоже была отвертка.

4.5) Он создает самые сложные, самые неразрушимые зависимости во время компиляции. Так, например, если у вас есть, FileSystem.saveFile()то нет способа изменить это, за исключением фальсификации вашей JVM во время выполнения. Это означает, что каждый класс, который ссылается на ваш класс статической функции, имеет жесткую зависимость во время компиляции от этой конкретной реализации, что делает расширение практически невозможным и значительно усложняет тестирование. Вы можете тестировать статический класс изолированно, но становится очень трудно тестировать классы, которые ссылаются на этот класс изолированно.

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

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

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

Ключевой особенностью Java является управление сложностью в небольших тестируемых единицах, которые объединяются, чтобы их было легко понять. Java подчеркивает четкие определения интерфейса независимо от реализации, что является огромным преимуществом. Вот почему он (и другие подобные ОО-языки) так широко используется. При всей многословности и ритуализме, когда я заканчиваю большое Java-приложение, я всегда чувствую, что идеи более четко разделены в коде, чем мои проекты на более динамичном языке.

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


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

Я имею в виду: иногда понятно, почему я хочу объекты. Я воспользуюсь примером из правки моего вопроса: у меня было приложение, которое составляет мелодии. Существовали разные весы, которые я мог подключить к MelodyGenerators, чтобы генерировать разные мелодии. А мелодии были объектами, поэтому я мог положить их в стек и вернуться к воспроизведению старых и т. Д. В подобных случаях мне понятно, почему я должен использовать объекты. Я не понимаю, почему я должен использовать объекты для представления «статических» частей системы: например, механизм сохранения файла. Почему это не должен быть статический класс?
Авив Кон

Класс со статическими методами должен выводить свое состояние. Что иногда является очень важной техникой факторинга. Но большую часть времени вы хотите инкапсулировать состояние. Конкретный пример: несколько лет назад сотрудник написал "сборщик дат" на Javascript. И это был кошмар. Клисомеры постоянно жаловались на это. Поэтому я реорганизовал его в объекты «календаря», и неожиданно я создал несколько экземпляров, поставил их рядом, перепрыгивал между месяцами и годами и т. Д. Это было настолько богато, что нам фактически пришлось отключить функции. Инстанциация дает вам такую ​​шкалу.
Роб

3
«Идея ОО заключается в объединении функциональности с состоянием, чтобы иметь небольшие блоки данных и функциональные возможности, которые сохраняют свою семантику, скрывая состояние и выставляя только те функции, которые имеют смысл в этом контексте». Это в корне неправильно. OO не подразумевает изменяемое состояние, и абстракция не является исключительной для OO. Есть два способа достижения абстракции, и объекты - только один из них (другой является абстрактными типами данных).
Довал

1
@ Довал Я не понимаю, почему каждое обсуждение ОО обязательно должно превращаться в обсуждение функционального программирования.
Роб

6

Ты спросил:

Но есть ли еще преимущества для объектов перед статическими классами?

Перед этим вопросом вы перечислили полиморфизм и обменивались двумя преимуществами использования объектов. Я хочу сказать, что таковы особенности парадигмы ОО. Инкапсуляция - еще одна особенность ОО-парадигмы.

Тем не менее, это не преимущества. Преимущества:

  1. Лучшие абстракции
  2. Лучшая ремонтопригодность
  3. Лучшая тестируемость

Вы сказали:

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

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

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

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

Ты спросил:

С объектом вызывающая строка кода будет выглядеть так: Thing thing = fileLoader.load(file);

Со статическим классом это будет выглядеть так: Thing thing = FileLoader.load(file);

Какая разница?

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

Ты спросил:

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

Я перечислил преимущества (преимущества) использования парадигмы ОО. Я надеюсь, что они говорят сами за себя. Если нет, я буду рад уточнить.

Ты спросил:

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

Это пример, когда статический класс просто не подойдет. Есть много способов сохранить данные вашего приложения в файл:

  1. Сохраните его в файле CSV.
  2. Сохраните его в файле XML.
  3. Сохраните его в файле JSON.
  4. Сохраните его как двоичный файл с прямым сбросом данных.
  5. Сохраните это непосредственно к некоторой таблице в базе данных.

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

В заключении

Используйте правильный подход для решения проблемы. Лучше не быть религиозным в отношении одного подхода против другого.


Кроме того , если много различных типов объектов (классы) должны быть сохранены (например Melody, Chord, Improvisations, SoundSampleи другими), то это будет экономичнее упростить реализацию через абстракции.
Rwong

5

Иногда это зависит от языка и контекста. Например, PHPсценарии, используемые для обслуживания запросов, всегда имеют один запрос на обслуживание и один ответ для генерации в течение всего времени жизни сценария, поэтому статические методы для обработки запроса и генерации ответа могут быть подходящими. Но на сервере, на котором написано Node.js, может быть много разных запросов и ответов одновременно. Итак, первый вопрос - вы уверены, что статический класс действительно соответствует одноэлементному объекту?

Во-вторых, даже если у вас есть синглтоны, использование объектов позволяет вам использовать преимущества полиморфизма, используя такие методы, как Dependency_injection и Factory_method_pattern . Это часто используется в различных шаблонах Inversion_of_control и полезно для создания фиктивных объектов для тестирования, ведения журнала и т. Д.

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


Спасибо за ответы. По вашему мнению: при создании «статической» части системы, то, что не будет «передаваться», например, часть системы, которая воспроизводит звуки, или часть системы, которая сохраняет данные в файл: я должен реализовать это в объекте или в статическом классе?
Авив Кон

3

В дополнение к посту Роба Y


Пока ваши функциональные возможности load(File file)могут быть четко отделены от всех других функций, которые вы используете, можно использовать статический метод / класс. Вы экстернализуете состояние (что не так уж плохо), и вы также не получаете избыточности, поскольку можете, например, частично применить или каррировать свою функцию, чтобы вам не пришлось повторяться. (это на самом деле то же самое или похоже на использование factoryшаблона)

Однако, как только две из этих функций начинают широко использоваться, вы захотите как-то сгруппировать их. Представьте, что у вас есть не только loadфункция, но и hasValidSyntaxфункция. Что ты бы сделал?

     if (FileLoader.hasValidSyntax(myfile))
          Thing thing = FileLoader.load(myfile);
     else
          println "oh noes!"

Видите две ссылки myfileздесь? Вы начинаете повторяться, потому что ваше внешнее состояние должно передаваться для каждого вызова. Роб Y описал, как вы усваиваете состояние (здесь file), чтобы вы могли сделать это следующим образом:

     FileLoader myfileLoader = new FileLoader(myfile)
     if (myfileLoader.hasValidSyntax())
          Thing thing = myfileLoader.load();
     else
          println "oh noes!"

Необходимость сдать один и тот же объект дважды - это поверхностный симптом; реальная проблема, на которую это может указывать, заключается в том, что некоторая работа выполняется с избыточностью - файл, возможно, придется открывать дважды, анализировать дважды и закрывать дважды, если hasValidSyntax()и load()не разрешено повторно использовать состояние (результат частично проанализированного файла).
Rwong

2

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

public Person(String name)

Ну, судя по подписи, человеку просто нужно имя, верно? Ну, нет, если реализация:

public Person(String name) {
    ResultSet rs = DBConnection.getPersonFilePathByName(name);
    File f = FileLoader.load(rs.getPath());
    PersonData.setDataFile(f);
    this.name = name;
    this.age = PersonData.getAge();
}

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

public void testPersonConstructor() {
    Person p = new Person("Milhouse van Houten");
    assertEqual(10, p.age);
}

Это должно пройти, правда? Я имею в виду, мы заключили в капсулу все эти другие вещи, верно? Ну, на самом деле это взрывается с исключением. Почему? О да, скрытые зависимости, о которых мы не знали, имеют глобальное состояние. Эти DBConnectionпотребности инициализируются и связно. Эти FileLoaderпотребности инициализируется с FileFormatобъектом (например , XMLFileFormatили CSVFileFormat). Никогда не слышал о тех? Ну, в этом все дело. Ваш код (и ваш компилятор) не могут сказать вам, что вам нужны эти вещи, потому что статические вызовы скрывают эти зависимости. Я сказал тест? Я имел в виду, что новый младший разработчик только что выпустил нечто подобное в вашем последнем выпуске. Ведь скомпилированный = работает, верно?

Кроме того, допустим, что вы работаете в системе, в которой не запущен экземпляр MySQL. Или, скажем, вы работаете в системе Windows, но ваш DBConnectionкласс выходит на ваш сервер MySQL только на компьютере с Linux (с путями Linux). Или, скажем, вы находитесь в системе, где путь, возвращаемый DBConnectionне для чтения / записи. Это означает, что попытка запустить или протестировать эту систему в любом из этих условий завершится неудачей не из-за ошибки кода, а из-за ошибки проектирования, которая ограничивает гибкость кода и связывает вас с реализацией.

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

Это всегда плохо? Не обязательно, но может быть лучше изменить свою точку зрения и сказать: «Есть ли веская причина, чтобы это не было примером?» а не "Есть ли веская причина, по которой это должно быть примером?" Если вы действительно можете сказать, что ваш код будет таким же непоколебимым (и не сохраняющим состояния), как Math.abs()и в его реализации, и в том, как он будет использоваться, то вы можете рассмотреть вопрос о том, чтобы сделать его статичным. Однако наличие экземпляров над статическими классами дает вам мир гибкости, который не всегда легко восстановить после факта. Это также может дать вам больше ясности в истинной природе зависимостей вашего кода.


Скрытые зависимости - это очень хороший момент. Я почти забыл свою идиому: «Качество кода следует оценивать по тому, как он используется, а не по тому, как он реализован».
Euphoric

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

@valenterry Да, мы также можем скрывать нестатические зависимости, но ключевой момент заключается в том, что скрытие нестатических зависимостей является выбором . Я мог бы newсоздать кучу объектов в конструкторе (что обычно не рекомендуется), но я также могу внедрить их. Со статическими классами нет способа внедрить их (в любом случае, это не так просто в Java, и это было бы совсем другое обсуждение для языков, которые это допускают), так что выбора нет. Вот почему я подчеркиваю идею того, что меня заставляют так сильно скрывать зависимости в своем ответе.
cbojar

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

Насколько мне известно, в Java вы не можете предоставить статический класс в качестве параметра, не пройдя весь процесс отражения. (Я пробовал все возможные способы. Если у вас есть способ, я бы хотел его увидеть.) И если вы решите это сделать, вы по сути либо подрываете встроенную систему типов (параметр является Class, тогда мы удостоверимся, что это правильный класс), или вы вводите утку (мы уверены, что она отвечает на правильный метод). Если вы хотите сделать последнее, то вам следует пересмотреть свой выбор языка. Если вы хотите сделать первое, экземпляры прекрасно работают и встроены.
cbojar

1

Просто мои два цента.

На ваш вопрос:

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

Вы можете спросить себя , если механизм той части системы , которая играет звуки, или часть системы , которая сохраняет данные в файл будет изменяться . Если ваш ответ «да», это означает, что вы должны абстрагировать их, используя abstract class/ interface. Вы можете спросить, как я могу знать будущее? Определенно, мы не можем. Таким образом, если java.lang.Mathобъект не имеет состояния, вы можете использовать «статический класс», например , использовать объектно-ориентированный подход.


Есть насмешливый совет: три удара и вы рефакторинг , что говорит о том, что можно удержаться до тех пор, пока действительно не понадобятся изменения. Под «изменением» я подразумеваю следующее: необходимо сохранить более одного типа объектов; или необходимость сохранения в более чем одном формате файла (тип хранения); или резкое увеличение сложности сохраненных данных. Конечно, если кто-то уверен, что изменения произойдут в ближайшее время, можно заранее предусмотреть более гибкий дизайн.
Rwong
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.