Есть ли запах кода пустых интерфейсов?

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

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

Мне также сказали, что .NET Framework использует пустые интерфейсы для целей тегов.

Мой вопрос: является ли пустой интерфейс сильным признаком проблемы дизайна или широко используется?

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

EDIT: я написал более длинную часть об этой проблеме, подробно объяснил проблему и решение.

Ответ 1

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

Как сообщается @V4Vendetta, существует правило статического анализа, которое предназначено для этого: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

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

Это заявленная рекомендация MSDN:

Удалите интерфейс или добавьте к нему участников. Если пустой интерфейс используется для обозначения набора типов, замените интерфейс на пользовательский атрибут.

Это также отражает раздел Critique уже опубликованной ссылки wikipedia.

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

Ответ 2

Вы заявляете, что ваша функция "возвращает совершенно разные объекты на основе определенных случаев" - но насколько они отличаются? Может ли быть писателем потока, другим классом пользовательского интерфейса, другим объектом данных? Нет... Я в этом сомневаюсь!

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

Ответ 3

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

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

Ответ 4

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

РЕДАКТИРОВАТЬ: Учитывая ваше разъяснение, вы действительно должны использовать интерфейс маркера. "совершенно разные" совершенно разные, чем "одинаковые". Если бы они были совершенно разными (а не только, что у них нет общих членов), это будет запах кода.

Ответ 5

Как уже многие уже говорили, пустой интерфейс действительно используется как "интерфейс маркера".

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

//Every object in the domain has an identity-sourced Id field
public interface IDomainObject
{
   long Id{get;}
}

//No additional useful information other than this is an object from the user security DB
public interface ISecurityDomainObject:IDomainObject {}

//No additional useful information other than this is an object from the Northwind DB
public interface INorthwindDomainObject:IDomainObject {}


//No additional useful information other than this is an object from the Southwind DB
public interface ISouthwindDomainObject:IDomainObject {}

Затем ваши репозитории могут быть созданы для ISecurityDomainObject, INorthwindDomainObject и ISouthwindDomainObject, а затем у вас есть проверка времени компиляции, чтобы ваш код не пытался передать объект безопасности в базу данных Northwind (или любую другую перестановку). В таких ситуациях интерфейс предоставляет ценную информацию о характере класса, даже если он не предусматривает какого-либо контракта на выполнение.