2013-04-26 6 views
1

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

Есть несколько вариантов здесь:

Глядя на BCL, я вижу, что Stream класса имеет такие свойства, как CanRead и CanSeek, которые могут быть использованы для проверки, если функция поддерживается и бросает NotSupportedException, если я то, что не поддерживается.

public interface IHandler 
{ 
    bool SupportsA { get; } 
    bool SupportsB { get; } 

    void A(); 
    void B(); 
} 

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

Я тоже очень люблю ценностей побитовое Enum, и я думаю, что другой путь был бы:

[Flags] 
public enum HandlerFeature 
{ 
    None = 0 
    A = 1, 
    B = 2, 
    C = 4 
} 

public interface IHandler 
{ 
    HandlerFeature Features { get; } 

    void A(); 
    void B(); 
    void C(); 
} 

И метод расширения, как это может быть написано:
bool Supports<T>(this T handler, HandlerFeature feature) where T : IHandler

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

public interface IHandler 
{ 
    [HandlerRequires(Feature.None)] 
    HandlerFeature Features { get; } 

    [HandlerRequires(Feature.A)] 
    void A(); 

    [HandlerRequires(Feature.B)] 
    void B(); 

    [HandlerRequires(Feature.A | Feature.B)] 
    void AB(); 

    [HandlerRequires(Feature.C)] 
    void C(); 
} 

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

Есть ли передовая практика для этого?
Или другой подход, который вы рекомендуете?
Или что-то не так с теми, что я указал?

+1

http://en.wikipedia.org/wiki/Interface_segregation_principle – Ani

ответ

1

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

В любом случае, я бы посоветовал вам не создавать такой «толстый» интерфейс. Я бы посоветовал вам прочитать о «Принципе разделения сегрегации», который является частью принципов объектно-ориентированного дизайна SOLID (Google, вы найдете несколько статей). В нем говорится, что клиенты не должны зависеть от методов, которые они не используют. Следствием этого является то, что вы используете несколько небольших интерфейсов вместо одного, который объединяет несколько аспектов. Теперь я не знаю контекста, в котором вы работаете, но если ваш интерфейс объединяет несколько сквозных проблем, таких как, например, кроме основных возможностей бизнеса, я бы настоятельно рекомендовал выкинуть протоколирование интерфейса и вместо этого использовать шаблон Decorator (также, Google, если вы этого не знаете :-)).

Надеюсь, это поможет.

+1

+1 и да, это также связано с комментарием от Ани.Вы правы, и я полностью согласен, но, по моему сценарию, это действительно не *, что толстый * :) И об атрибуте я никогда не собирался заставить его использовать, я надеялся избежать использования его только в контракт, как-то. И спасибо за те концепции, которые вы упомянули. –

+0

В тех случаях, когда код, который получает что-то одного типа интерфейса, часто хотел бы использовать функцию другого, если он присутствует, может быть выгодным, чтобы прежний интерфейс включал методы, связанные с этой функцией. Например, если 'IEnumerable ' включает свойства, указывающие, что-то ограничено, быстро счетно или нет, а также метод 'Count()', который может, если не что иное, либо вызвать 'Enumerable .CountByEnumeration (this) '(если он ограничен) или бросать' NotSupportedException' (если он не ограничен) будет более чистым, чем при попытке применить к 'ICollection'. – supercat

+0

@supercat: Я не согласен. Если я хочу, чтобы перечисляемый имел конечное количество элементов, я бы используйте интерфейсы IReadOnlyCollection или IReadOnlyList вместо IEnumerable . В противном случае я нарушил бы принцип замены Лискова. Mark Seemann написал интересную запись в блоге об этом: http://blog.ploeh.dk/2013/07/20/linq- versus-the-lsp/ – feO2x