Являются ли классы только с одним (публичным) методом проблемой?

51

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

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

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

  • VideoCompressor (с compressметодом, который принимает входное видео типа RawVideoи возвращает выходное видео типа CompressedVideo).
  • VideoSplitter (с splitметодом, который принимает входное видео типа RawVideoи возвращает вектор из 2 выходных видео, каждого типа RawVideo).
  • VideoIndexer (с indexметодом, который принимает входное видео типа RawVideoи возвращает индекс видео типа VideoIndex).

Я считаю себя инстанцированием каждого класса только совершать звонки , как VideoCompressor.compress(...), VideoSplitter.split(...), VideoIndexer.index(...).

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

Это на самом деле проблема?

yjwong
источник
14
Это зависит от языка. В многопарадигмальном языке, таком как C ++ или Python, эти классы не имеют бизнеса: их «методы» должны быть свободными функциями.
3
@delnan: даже в C ++ вы обычно используете классы для создания модулей, даже если вам не нужны полные «возможности OO». Действительно, существует альтернатива использования пространств имен вместо того, чтобы группировать «свободные функции» вместе с модулем, но я не вижу в этом никаких преимуществ.
Док Браун
5
@DocBrown: C ++ имеет пространства имен для этого. Фактически в современном C ++ вы часто используете свободные функции для методов, потому что они могут (статически) быть перегружены для всех аргументов, в то время как методы могут быть перегружены только для инвоканта.
Ян Худек
2
@DocBrown: это очень суть вопроса. Модули, использующие пространства имен, имеющие только один «внешний» метод, прекрасно подходят, когда функция достаточно сложна. Потому что пространства имен не претендуют на то, чтобы представлять что-либо, и не притворяются объектно-ориентированными. Классы делают, но классы как это - действительно просто пространства имен. Конечно, в Java у вас не может быть функции, так что это результат. Позор на Яве.
Ян Худек
2
Связанные (почти дубликаты): programmers.stackexchange.com/q/175070/33843
Хайнци,

Ответы:

87

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

Док Браун
источник
6
Это правильный ответ, и я проголосовал за него, но вы могли бы улучшить его, объяснив некоторые преимущества. Я думаю, что инстинкт ОП говорит ему, что проблема в чем-то другом ... то есть, что VideoCompressor действительно интерфейс. Mp4VideoCompressor является классом и должен быть взаимозаменяемым с другим VideoCompressor без копирования кода для VideoSplitter в новый класс.
pdr
1
Извините, но вы не правы. Класс с одним методом должен быть просто автономной функцией.
Майлз Рут
3
@MilesRout: ваш комментарий показывает, что вы неправильно поняли вопрос - ОП написал о классе с одним открытым методом, а не с одним методом (и он действительно имел в виду класс с несколькими закрытыми методами, как он сказал нам здесь в одном комментарии).
Док Браун
2
«Класс с одним методом должен быть просто автономной функцией» . Грубое предположение о том, что это за метод. У меня есть класс с одним открытым методом. За этим стоят несколько методов в этом классе плюс 5 других классов, которые абсолютно не имеют представления о клиенте. Превосходное применение SRP даст чистый, простой интерфейс многоуровневой структуры классов, причем эта простота проявляется вплоть до самого верха.
радаробоб
2
@ Энди: вопрос был «это проблема», а не «соответствует ли это определению ОО». Более того, в этом вопросе нет абсолютно никаких признаков того, что OP означает классы без состояния.
Док Браун
26

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

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

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

В одном случае вам может понадобиться класс с одним открытым методом, потому что он должен реализовывать интерфейс с одним открытым методом. Будь то для наблюдателя или внедрения зависимости или чего-то еще. Здесь это снова зависит от языка. В языках с функторами первого класса (C ++ ( std::functionили параметр шаблона), C ​​# (делегат), Python, Perl, Ruby ( proc), Lisp, Haskell, ...) эти шаблоны используют типы функций и не нуждаются в классах. В Java нет (пока не будет версии 8) функциональных типов, поэтому вы используете интерфейсы с одним методом и соответствующие классы с одним методом.

Конечно, я не защищаю написание одной огромной функции. Он должен иметь частные подпрограммы, но они могут быть частными для файла реализации (статическое или анонимное пространство имен на уровне файлов в C ++) или в частном вспомогательном классе, который создается только внутри публичной функции ( для хранения данных или нет? ).

Ян Худек
источник
12
«Зачем притворяться, что это класс, когда это не так». Наличие объекта вместо свободной функции позволяет состояние и подтип. Это может оказаться полезным в мире, где меняются требования. Рано или поздно возникает необходимость поиграть с настройками сжатия видео или предоставить альтернативные алгоритмы сжатия. До этого слово «класс» просто говорит нам, что эта функция относится к легко расширяемому и взаимозаменяемому программному модулю с четкой ответственностью. Разве это не то, к чему действительно стремится ОО?
Приходите с
1
Что ж, если у него есть только один публичный метод (и подразумевает отсутствие защищенных), он не может быть расширен. Конечно, упаковка параметров сжатия может иметь смысл, и в этом случае она становится функциональным объектом (некоторые языки поддерживают их отдельно, а некоторые нет).
Ян Худек
3
Это фундаментальная связь между объектами и функциями: функция изоморфна объекту с одним методом и без полей. Замыкание изоморфно объекту с одним методом и некоторыми полями. Функция выбора, возвращающая одну из нескольких функций, которые закрываются по одному и тому же набору переменных, изоморфна объекту. (Фактически, именно так объекты кодируются в JavaScript, за исключением того, что вы используете словарную структуру данных вместо функции селектора.)
Йорг W Mittag
4
«Он больше не является объектно-ориентированным. Поскольку эти классы ничего не представляют, они просто сосуды для функций». - Это не верно. Я бы сказал, что этот подход более объектно-ориентированный. ОО не означает, что он представляет собой объект реального мира. Большое количество программирования имеет дело с абстрактными понятиями, но означает ли это, что вы не можете применить ОО-подход для его решения? Точно нет. Это больше о модульности и абстракции. Каждый объект несет единоличную ответственность. Это позволяет легко оборачиваться вокруг программы и вносить изменения. Недостаточно места для перечисления многочисленных преимуществ ООП.
Despertar
2
@Phoshi: Да, я понимаю. Я никогда не утверждал, что функциональный подход не сработает. Однако это явно не тема. Видео компрессор или видео трансмогрификатор или что-то еще все еще является вполне подходящим кандидатом на объект.
Приходите с
13

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

Представьте, что у вас есть класс с именем, VideoExporterкоторый, в конце концов, сможет сжимать видео. Чистым способом было бы иметь интерфейс:

interface IVideoCompressor
{
    Stream compress(Video video);
}

который будет реализован так:

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

и используется так:

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}

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

Арсений Мурзенко
источник
2
Ваш ответ не различает публичные и частные методы. Это может быть понято как рекомендация помещать весь код класса в один метод, но я думаю, это не то, что вы имели в виду?
Док Браун
2
@DocBrown: частные методы не имеют отношения к этому вопросу. Они могут быть помещены во внутренний вспомогательный класс или как угодно.
Ян Худек
2
@JanHudec: в настоящее время текст «плохой альтернативой будет иметь VideoExporter, который имеет множество методов», но это должно быть «плохой альтернативой будет иметь VideoExporter, который имеет множество открытых методов».
Док Браун
@DocBrown: согласен здесь.
Ян Худек
2
@DocBrown: спасибо за ваши замечания. Я отредактировал свой ответ. Первоначально я думал, что предполагается, что вопрос (и, таким образом, мой ответ) касается только публичных методов. Похоже, это не так очевидно.
Арсений Мурзенко
6

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

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

Doval
источник
1
Начиная с Java 8, у вас есть лямбды, так что вы в значительной степени можете передавать функции.
Сильвиу Бурча
Справедливо, но это не будет официально выпущено немного дольше, и некоторые рабочие места не спешат переходить на более новые версии.
Довал
Дата выхода в марте. Кроме того, сборки EAP были очень популярны для JDK 8 :)
Сильвиу Бурча
Хотя лямбды Java 8 - это просто сокращенный способ определения объектов с помощью только одного открытого метода, кроме небольшого сохранения стандартного кода, здесь они вообще не имеют значения.
Жюль
5

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

Это хорошо известный шаблон дизайна, который называется « Стратегический шаблон» .

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

Например, в этом случае вы можете иметь, interface VideoCompressorа затем иметь несколько альтернативных реализаций, например class H264Compressor implements VideoCompressorи class XVidCompressor implements VideoCompressor. Из OP не ясно, что задействован какой-либо интерфейс, даже если его нет, может быть, просто первоначальный автор оставил дверь открытой, чтобы реализовать шаблон стратегии, если это необходимо. Что само по себе тоже хороший дизайн.

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

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

Эмили Л.
источник
1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

То, что у вас есть, является модульным, и это хорошо, но если бы вы сгруппировали эти обязанности в IVideoProcessor, это, вероятно, имело бы больше смысла с точки зрения DDD.

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

CodeART
источник
Поскольку причина необходимости изменения каждой из этих функций несколько иная, я бы сказал, что их объединение, как это, нарушает SRP.
Жюль
0

Это проблема - вы работаете с функциональной стороны дизайна, а не с данными. На самом деле у вас есть 3 автономные функции, которые были OO-ified.

Например, у вас есть класс VideoCompressor. Почему вы работаете с классом, предназначенным для сжатия видео - почему у вас нет класса Video с методами для сжатия (видео) данных, которые содержит каждый объект этого типа?

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

РЕДАКТИРОВАТЬ:

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

string mystring("Hello"); 
mystring.concat("World");

но ОП хочет, чтобы это работало так:

string mystring();
string result = mystring.concat("Hello", "World");

теперь есть места, где класс может использоваться для хранения набора связанных функций, но это не OO, это удобный способ использования функций OO языка, чтобы помочь лучше управлять вашей кодовой базой, но это никоим образом "ОО Дизайн". Объект в таких случаях является полностью искусственным, просто используется таким образом, потому что язык не предлагает ничего лучшего для решения такого рода проблем. например. В таких языках, как C #, вы бы использовали статический класс для обеспечения этой функциональности - он повторно использует механизм классов, но вам больше не нужно создавать экземпляр объекта просто для вызова его методов. Вы в конечном итоге с методами, как string.IsNullOrEmpty(mystring)я думаю, плохо по сравнению с mystring.isNullOrEmpty().

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

gbjbaanb
источник
9
-1 я совершенно не согласен. Проектирование OO для начинающих работает только с объектами данных, такими как a, Videoи имеет тенденцию перегружать такие классы функциональностью, которая часто заканчивается беспорядочным кодом с> 10K LOC на класс. Усовершенствованный дизайн ОО разбивает функциональность на более мелкие единицы, такие как VideoCompressor(и позволяет Videoклассу быть просто классом данных или фасадом для VideoCompressor).
Док Браун
5
@DocBrown: с этого момента он больше не является объектно-ориентированным дизайном, поскольку VideoCompressorне представляет объект . В этом нет ничего плохого, просто показан предел объектно-ориентированного дизайна.
Ян Худек
3
ах, но ОО начинающих, где одна функция превращается в класс, на самом деле вовсе не ОО, а только поощряет массовую развязку, которая заканчивается тысячами файлов классов, которые никто не может поддерживать. Я думаю, что лучше всего рассматривать класс как «обёртку данных», а не «обёртку функциональности», которая даст новичку лучшее понимание того, как думать о ОО-программах.
gbjbaanb
4
@DocBrown на самом деле точно подчеркнул мою озабоченность; У меня действительно есть Videoкласс, но методология сжатия ни в коем случае не тривиальна, поэтому я бы на самом деле разбил compressметод на несколько других частных методов. Это
одна
2
Я думаю , что это может быть нормально иметь Videoкласс, но он будет состоять из VideoCompressor, а VideoSplitter, и другие связанные классы, которые должны, в хорошей форме OO, быть высоко сплоченные индивидуальные занятия.
Эрик Кинг,
-1

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

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

Кстати, вы сказали

Я нахожу себя экземпляром каждого класса

эти классы, по-видимому, являются службами (и, следовательно, не имеют состояния). Ты думал о том, чтобы сделать их одиночками?

diegomtassis
источник
1
Синглтоны, как правило, антипаттерны. Смотрите Singletons: Решение проблем, о которых вы не знали с 1995 года .
Ян Худек
3
В остальном, хотя принцип разделения интерфейса является хорошим шаблоном, этот вопрос касается реализаций, а не интерфейсов, поэтому я не уверен, что он актуален.
Ян Худек
3
Я не собираюсь обсуждать, является ли синглтон образцом или антипаттерном. Я предложил возможное решение (у него даже есть имя) этой проблемы, он должен решить, подходит он или нет.
diegomtassis
Что касается релевантности интернет-провайдера или нет, то вопрос в следующем: «Являются ли классы только одним методом проблемой?», Противоположность этому - класс со многими методами, который становится проблемой, когда вы делаете клиент напрямую зависит от этого ... именно ксероксный пример Роберта Мартина, который он взял, чтобы сформулировать провайдера.
diegomtassis
-1

Да, есть проблема. Но не серьезный. Я имею в виду, что вы можете структурировать свой код следующим образом, и ничего плохого не произойдет, его можно поддерживать. Но есть некоторые слабые стороны такого рода структурирования. Например, подумайте, если представление вашего видео (в вашем случае оно сгруппировано в классе RawVideo) изменится, вам нужно будет обновить все ваши классы операций. Или учтите, что у вас может быть несколько представлений видео, которые различаются во время выполнения. Тогда вам нужно будет сопоставить представление с конкретным классом «операция». Также субъективно раздражает перетаскивание зависимости для каждой операции, которую вы хотите выполнить с чем-либо. и обновлять список зависимостей, передаваемых каждый раз, когда вы решаете, что операция больше не нужна или вам нужна новая операция.

Также это на самом деле является нарушением ПСП. Некоторые люди просто воспринимают SRP как руководство по разделению обязанностей (и далеко зашли, рассматривая каждую операцию как отдельную ответственность), но они забывают, что SRP также является руководством по группированию обязанностей. И в соответствии с обязанностями ПСП, которые меняются по одной и той же причине, следует группировать их так, чтобы в случае изменения они были ограничены как можно меньшим количеством классов / модулей. Что касается больших классов, то это не проблема наличия нескольких алгоритмов в одном классе, если эти алгоритмы связаны между собой (то есть делятся некоторыми знаниями, которые не должны быть известны за пределами этого класса). Проблема в больших классах, которые имеют алгоритмы, которые никак не связаны и меняются / меняются по разным причинам.

ddiukariev
источник