Я понимаю концепцию объекта, и, как программист на Java, я чувствую, что парадигма ОО приходит на практике довольно естественно.
Однако недавно я подумал:
Подождите секунду, каковы на самом деле практические преимущества использования объекта по сравнению со статическим классом (с правильной практикой инкапсуляции и ОО)?
Я мог бы подумать о двух преимуществах использования объекта (оба являются значительными и мощными):
Полиморфизм: позволяет динамически и гибко менять функциональность во время выполнения. Также позволяет легко добавлять новые функциональные возможности и детали в систему. Например, если есть
Car
класс, предназначенный для работы сEngine
объектами, и вы хотите добавить новый Engine в систему, которую может использовать Car, вы можете создать новыйEngine
подкласс и просто передать объект этого класса вCar
объект без необходимости изменить что-нибудь оCar
. И вы можете решить сделать это во время выполнения.Возможность «передавать функциональность»: вы можете динамически передавать объект по системе.
Но есть ли еще преимущества для объектов перед статическими классами?
Часто, когда я добавляю новые «детали» в систему, я делаю это, создавая новый класс и создавая для него объекты.
Но недавно, когда я остановился и подумал об этом, я понял, что статический класс будет делать то же самое, что и объект, во многих местах, где я обычно использую объект.
Например, я работаю над добавлением механизма сохранения / загрузки файла в мое приложение.
С объектом вызывающая строка кода будет выглядеть так: Thing thing = fileLoader.load(file);
Со статическим классом это будет выглядеть так: Thing thing = FileLoader.load(file);
Какая разница?
Довольно часто я просто не могу придумать причину создания объекта, когда обычный старый статический класс будет действовать точно так же. Но в ОО-системах статические классы довольно редки. Так что я должен что-то упустить.
Есть ли еще какие-то преимущества для других объектов из двух, которые я перечислил? Пожалуйста, объясни.
РЕДАКТИРОВАТЬ: уточнить. Я нахожу объекты очень полезными при обмене функциональностью или передаче данных. Например, я написал приложение, которое составляет мелодии. MelodyGenerator
было несколько подклассов, которые по-разному создают мелодии, и объекты этих классов были взаимозаменяемыми (шаблон Стратегии).
Мелодии тоже были объектами, потому что их полезно передавать. Так были аккорды и весы.
Но как насчет «статических» частей системы, которые не будут распространяться? Например, механизм сохранения файла. Почему я должен реализовывать это в объекте, а не в статическом классе?
источник
Thing
?FileLoader
на тот, который читает из сокета? Или макет для тестирования? Или тот, который открывает ZIP-файл?System.Math
в .NET - пример чего-то, что имеет гораздо больший смысл как статический класс: вам никогда не понадобится менять его или высмеивать его, и ни одна из операций не может быть логически сделана частью экземпляра. Я действительно не думаю, что ваш «спасительный» пример соответствует этому счету.Ответы:
LOL, вы звучите как команда, над которой я работал;)
Java (и, вероятно, C #), безусловно, поддерживает этот стиль программирования. И я работаю с людьми, чей первый инстинкт: «Я могу сделать это статическим методом!» Но есть некоторые тонкие затраты, которые со временем вас настигнут.
1) Java - это объектно-ориентированный язык. И это сводит с ума функциональных парней, но на самом деле это выдерживает довольно хорошо. Идея ОО заключается в объединении функциональности с состоянием, чтобы иметь небольшие блоки данных и функциональные возможности, которые сохраняют свою семантику, скрывая состояние и выставляя только те функции, которые имеют смысл в этом контексте.
Переходя к классу с использованием только статических методов, вы нарушаете часть «состояния» уравнения. Но государству еще где-то жить. Итак, со временем я увидел, что класс со всеми статическими методами начинает иметь все более и более сложные списки параметров, потому что состояние перемещается из класса в вызовы функций.
После того, как вы создадите класс со всеми статическими методами, запустите и просто посмотрите, сколько из этих методов имеют один общий параметр. Это подсказка, что этот параметр должен быть либо содержащим класс для этих функций, либо этот параметр должен быть атрибутом экземпляра.
2) Правила ОО довольно хорошо поняты. Через некоторое время вы можете посмотреть на дизайн класса и посмотреть, соответствует ли он критериям, подобным SOLID. И после большого количества практических юнит-тестов вы развиваете понимание того, что делает класс «правильным» и «связным». Но не существует хороших правил для класса со всеми статическими методами, и нет реальной причины, по которой вы не должны просто связывать все там. Класс открыт в вашем редакторе, так что, черт возьми? Просто добавьте туда свой новый метод. Через некоторое время ваше приложение превращается в ряд конкурирующих «Объектов Бога», каждый из которых пытается доминировать в мире. Опять же, рефакторинг их в более мелкие единицы очень субъективен, и трудно сказать, правильно ли вы это сделали.
3) Интерфейсы являются одной из самых мощных функций Java. Наследование классов оказалось проблематичным, но программирование с интерфейсами остается одним из самых мощных трюков языка. (то же самое на C #). Все статические классы не могут быть помещены в эту модель.
4) Это хлопает дверью по важным ОО методам, которыми вы не можете воспользоваться. Таким образом, вы можете годами работать только с молотком в своем наборе инструментов, даже не осознавая, насколько проще было бы, если бы у вас тоже была отвертка.
4.5) Он создает самые сложные, самые неразрушимые зависимости во время компиляции. Так, например, если у вас есть,
FileSystem.saveFile()
то нет способа изменить это, за исключением фальсификации вашей JVM во время выполнения. Это означает, что каждый класс, который ссылается на ваш класс статической функции, имеет жесткую зависимость во время компиляции от этой конкретной реализации, что делает расширение практически невозможным и значительно усложняет тестирование. Вы можете тестировать статический класс изолированно, но становится очень трудно тестировать классы, которые ссылаются на этот класс изолированно.5) Вы сводите своих коллег с ума. Большинство профессионалов, с которыми я работаю, серьезно относятся к своему коду и обращают внимание, по крайней мере, на некоторый уровень принципов проектирования. Если отложить в сторону основные намерения языка, они вытянут свои волосы, потому что они будут постоянно рефакторинг кода.
Когда я нахожусь на языке, я всегда стараюсь использовать язык хорошо. Так, например, когда я нахожусь в Java, я использую хороший дизайн ОО, потому что тогда я действительно использую язык для того, что он делает. Когда я нахожусь в Python, я смешиваю функции уровня модуля со случайными классами - я мог писать только классы на Python, но тогда я думаю, что я не буду использовать язык для того, в чем он хорош.
Еще одна тактика - плохо использовать язык, и они жалуются на все проблемы, которые его вызывают. Но это относится практически ко всем технологиям.
Ключевой особенностью Java является управление сложностью в небольших тестируемых единицах, которые объединяются, чтобы их было легко понять. Java подчеркивает четкие определения интерфейса независимо от реализации, что является огромным преимуществом. Вот почему он (и другие подобные ОО-языки) так широко используется. При всей многословности и ритуализме, когда я заканчиваю большое Java-приложение, я всегда чувствую, что идеи более четко разделены в коде, чем мои проекты на более динамичном языке.
Хотя это сложно. Я видел, как люди получают «полностью статическую» ошибку, и отчасти сложно от них избавиться. Но я видел, что они испытывают большое облегчение, когда преодолевают это.
источник
Ты спросил:
Перед этим вопросом вы перечислили полиморфизм и обменивались двумя преимуществами использования объектов. Я хочу сказать, что таковы особенности парадигмы ОО. Инкапсуляция - еще одна особенность ОО-парадигмы.
Тем не менее, это не преимущества. Преимущества:
Вы сказали:
Я думаю, что у вас есть верная точка зрения там. По своей сути, программирование - это не что иное, как трансформация данных и создание побочных эффектов на основе данных. Иногда преобразование данных требует вспомогательных данных. В других случаях это не так.
Когда вы имеете дело с первой категорией преобразований, вспомогательные данные должны быть либо переданы в качестве входных данных, либо где-то сохранены. Объект - лучший подход, чем статические классы для таких преобразований. Объект может хранить вспомогательные данные и использовать их в нужное время.
Для второй категории преобразований статический класс так же хорош, как Object, если не лучше. Математические функции являются классическими примерами этой категории. Большинство стандартных функций библиотеки C тоже попадают в эту категорию.
Ты спросил:
Если этого
FileLoader
не делает, когда - либо , нужно хранить любые данные, я бы со вторым подходом. Если есть вероятность, что для выполнения операции потребуются вспомогательные данные, первый подход - более безопасная ставка.Ты спросил:
Я перечислил преимущества (преимущества) использования парадигмы ОО. Я надеюсь, что они говорят сами за себя. Если нет, я буду рад уточнить.
Ты спросил:
Это пример, когда статический класс просто не подойдет. Есть много способов сохранить данные вашего приложения в файл:
Единственный способ предоставить такие опции - это создать интерфейс и создать объекты, которые реализуют интерфейс.
В заключении
Используйте правильный подход для решения проблемы. Лучше не быть религиозным в отношении одного подхода против другого.
источник
Melody
,Chord
,Improvisations
,SoundSample
и другими), то это будет экономичнее упростить реализацию через абстракции.Иногда это зависит от языка и контекста. Например,
PHP
сценарии, используемые для обслуживания запросов, всегда имеют один запрос на обслуживание и один ответ для генерации в течение всего времени жизни сценария, поэтому статические методы для обработки запроса и генерации ответа могут быть подходящими. Но на сервере, на котором написаноNode.js
, может быть много разных запросов и ответов одновременно. Итак, первый вопрос - вы уверены, что статический класс действительно соответствует одноэлементному объекту?Во-вторых, даже если у вас есть синглтоны, использование объектов позволяет вам использовать преимущества полиморфизма, используя такие методы, как Dependency_injection и Factory_method_pattern . Это часто используется в различных шаблонах Inversion_of_control и полезно для создания фиктивных объектов для тестирования, ведения журнала и т. Д.
Вы упомянули преимущества выше, поэтому вот одно из них: наследование. Многие языки не имеют возможности переопределять статические методы так же, как методы экземпляров могут быть переопределены. Вообще, гораздо сложнее наследовать и переопределять статические методы.
источник
В дополнение к посту Роба Y
Пока ваши функциональные возможности
load(File file)
могут быть четко отделены от всех других функций, которые вы используете, можно использовать статический метод / класс. Вы экстернализуете состояние (что не так уж плохо), и вы также не получаете избыточности, поскольку можете, например, частично применить или каррировать свою функцию, чтобы вам не пришлось повторяться. (это на самом деле то же самое или похоже на использованиеfactory
шаблона)Однако, как только две из этих функций начинают широко использоваться, вы захотите как-то сгруппировать их. Представьте, что у вас есть не только
load
функция, но иhasValidSyntax
функция. Что ты бы сделал?Видите две ссылки
myfile
здесь? Вы начинаете повторяться, потому что ваше внешнее состояние должно передаваться для каждого вызова. Роб Y описал, как вы усваиваете состояние (здесьfile
), чтобы вы могли сделать это следующим образом:источник
hasValidSyntax()
иload()
не разрешено повторно использовать состояние (результат частично проанализированного файла).Основная проблема при использовании статических классов заключается в том, что они заставляют вас скрывать ваши зависимости и заставляют зависеть от реализаций. В следующей подписи конструктора, каковы ваши зависимости:
Ну, судя по подписи, человеку просто нужно имя, верно? Ну, нет, если реализация:
Так что человек не просто создан. На самом деле мы извлекаем данные из базы данных, которая дает нам путь к файлу, который затем должен быть проанализирован, а затем должны быть выявлены реальные данные, которые мы хотим. Этот пример явно зашкаливает, но это доказывает суть. Но подождите, может быть намного больше! Я пишу следующий тест:
Это должно пройти, правда? Я имею в виду, мы заключили в капсулу все эти другие вещи, верно? Ну, на самом деле это взрывается с исключением. Почему? О да, скрытые зависимости, о которых мы не знали, имеют глобальное состояние. Эти
DBConnection
потребности инициализируются и связно. ЭтиFileLoader
потребности инициализируется сFileFormat
объектом (например ,XMLFileFormat
илиCSVFileFormat
). Никогда не слышал о тех? Ну, в этом все дело. Ваш код (и ваш компилятор) не могут сказать вам, что вам нужны эти вещи, потому что статические вызовы скрывают эти зависимости. Я сказал тест? Я имел в виду, что новый младший разработчик только что выпустил нечто подобное в вашем последнем выпуске. Ведь скомпилированный = работает, верно?Кроме того, допустим, что вы работаете в системе, в которой не запущен экземпляр MySQL. Или, скажем, вы работаете в системе Windows, но ваш
DBConnection
класс выходит на ваш сервер MySQL только на компьютере с Linux (с путями Linux). Или, скажем, вы находитесь в системе, где путь, возвращаемыйDBConnection
не для чтения / записи. Это означает, что попытка запустить или протестировать эту систему в любом из этих условий завершится неудачей не из-за ошибки кода, а из-за ошибки проектирования, которая ограничивает гибкость кода и связывает вас с реализацией.Теперь, скажем, мы хотим регистрировать каждый вызов базы данных для одного конкретного
Person
экземпляра, который проходит по определенному, неприятному пути. Мы могли бы войти в системуDBConnection
, но это будет регистрировать все , внося много беспорядка и затрудняя разграничение конкретного пути кода, который мы хотим отследить. Однако, если мы используем внедрение зависимостей вDBConnection
экземпляр, мы могли бы просто реализовать интерфейс в декораторе (или расширить класс, поскольку у нас есть обе опции, доступные для объекта). Со статическим классом мы не можем внедрить зависимость, мы не можем реализовать интерфейс, мы не можем обернуть его в декоратор, и мы не можем расширить класс. Мы можем вызвать это только напрямую, где-то глубоко в нашем коде. Таким образом, мы вынуждены иметь скрытую зависимость от реализации.Это всегда плохо? Не обязательно, но может быть лучше изменить свою точку зрения и сказать: «Есть ли веская причина, чтобы это не было примером?» а не "Есть ли веская причина, по которой это должно быть примером?" Если вы действительно можете сказать, что ваш код будет таким же непоколебимым (и не сохраняющим состояния), как
Math.abs()
и в его реализации, и в том, как он будет использоваться, то вы можете рассмотреть вопрос о том, чтобы сделать его статичным. Однако наличие экземпляров над статическими классами дает вам мир гибкости, который не всегда легко восстановить после факта. Это также может дать вам больше ясности в истинной природе зависимостей вашего кода.источник
new
создать кучу объектов в конструкторе (что обычно не рекомендуется), но я также могу внедрить их. Со статическими классами нет способа внедрить их (в любом случае, это не так просто в Java, и это было бы совсем другое обсуждение для языков, которые это допускают), так что выбора нет. Вот почему я подчеркиваю идею того, что меня заставляют так сильно скрывать зависимости в своем ответе.Class
, тогда мы удостоверимся, что это правильный класс), или вы вводите утку (мы уверены, что она отвечает на правильный метод). Если вы хотите сделать последнее, то вам следует пересмотреть свой выбор языка. Если вы хотите сделать первое, экземпляры прекрасно работают и встроены.Просто мои два цента.
На ваш вопрос:
Вы можете спросить себя , если механизм той части системы , которая играет звуки, или часть системы , которая сохраняет данные в файл будет изменяться . Если ваш ответ «да», это означает, что вы должны абстрагировать их, используя
abstract class
/interface
. Вы можете спросить, как я могу знать будущее? Определенно, мы не можем. Таким образом, еслиjava.lang.Math
объект не имеет состояния, вы можете использовать «статический класс», например , использовать объектно-ориентированный подход.источник