2013-03-11 2 views
2

У меня есть интерфейс, который выглядит следующим образом:должен реализовать ЯВНО интерфейс A или интерфейс B (ровно один)

public interface IOpportunity 
{ 
    string Name { get; } 
    string Description { get; } 
    ILocation Location { get; } 
} 

public interface ILocation : IHierarchicalEntity 
{ 
    int OpptyCount { get; } 
} 

public interface IHierarchicalEntity 
{ 
    string SID { get; } 
    string Name { get; } 
} 

Однако, я хочу объект ILocation также реализовать один из этих интерфейсов:

public interface IHierarchicalEntityWithParentNames : IHierarchicalEntity 
{ 
    /// <summary> 
    /// Returns the lowest level that this heirarchy goes (0 for a flat hierarchy, 1 for a two-level etc.) 
    /// </summary> 
    int LeafLevel { get; } 

    /// <summary> 
    /// Returns the name of the Segment for the given level (0 for a root node, n for leaf node, where n = LeafLevel) 
    /// </summary> 
    /// <param name="level"></param> 
    /// <returns></returns> 
    string GetNameForLevel(int level); 
} 


public interface IHierarchicalEntityWithParentIds : IHierarchicalEntity 
{ 
    IHierarchicalEntityWithParentIds ParentEntity { get; } 
    string ParentSID { get; } 
} 

в связи с характером кода я пишу, я не могу объединить эти интерфейсы в один интерфейс, который имеет какое-то GetParent метода

в коде, потребляет эти интерфейсы, у меня есть два класса: один, который расходует объект ILocation, если он является IHierarchicalEntityWithParentNames, а другой, если он IHierarchicalEntityWithParentIds

Как я могу выложить интерфейсы (возможно, мне нужно иметь некоторые абстрактные классы) для поддержки имея этот «один или другой» дизайн?

+0

Я отредактировал ваше название. Пожалуйста, смотрите: «Если вопросы включают« теги »в их названиях?] (Http://meta.stackexchange.com/questions/19190/), где консенсус« нет, они не должны ». –

ответ

0

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

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

1

Вы не можете. Вы либо явно реализуете интерфейс, либо нет. То, что вы описываете, эффективно «либо метод A , либо метод B будет существовать», но это не концепция, которая существует на C# (или любой другой язык, о котором я знаю!).

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

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

+0

+1 для подтверждения того, что я уже знал, но все-таки не отличного решения (как вы сами заявили) –

2

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

Я обратился к этому путем создания единого класса Coords, который явно реализует оба интерфейса ICoordsCanon и ICoordsUser. Фактические координаты лениво хранится и оценены с автоматизированным преобразованием, как это:

protected static IntMatrix2D MatrixUserToCanon; 
protected IntVector2D VectorCanon { 
    get { return ! isCanonNull ? _vectorCanon 
          : VectorUser * MatrixUserToCanon; } 
    set { _vectorCanon = value; isUserNull = true; } 
} IntVector2D _vectorCanon; 
bool isCanonNull; 

protected static IntMatrix2D MatrixCanonToUser; 
protected IntVector2D VectorUser { 
    get { return ! isUserNull ? _vectorUser 
          : VectorCanon * MatrixCanonToUser; } 
    set { _vectorUser = value; isCanonNull = true; } 
} IntVector2D _vectorUser; 
bool isUserNull; 

Конструктором COORDS является частным, с общественным статическими функциями NewUserCoords (...) и NewCanonCoords (...) определен.

Хотя реализация на самом деле не является ни ... или ..., она должна быть реализована так для приложения. Большинство применений приложений либо работают с объектами ICoordsCanon, либо с объектами ICoordsUser; два метода ICoordsCanon.User() и ICoordsUser.Canon() существуют для преобразования между ними по мере необходимости.

По многочисленным просьбам здесь приведены определения интерфейсов и их реализации.

public interface ICoordsUser { 
    int    X   { get; } 
    int    Y   { get; } 
    IntVector2D  Vector { get; set; } 
    ICoordsCanon Canon  { get; } 
    //ICoordsUser  Clone(); 
    string   ToString(); 
    int    Range(ICoordsUser coords); 
    IEnumerable<NeighbourCoords> GetNeighbours(Hexside hexsides); 
    } 

    public partial class Coords { 
    int   ICoordsUser.X   { get { return VectorUser.X; } } 
    int   ICoordsUser.Y   { get { return VectorUser.Y; } } 
    IntVector2D ICoordsUser.Vector  { get { return VectorUser; } 
              set { VectorUser=value; } } 
    ICoordsCanon ICoordsUser.Canon  { get { return this;   } } 
    //ICoordsUser ICoordsUser.Clone() { return NewUserCoords(VectorUser); } 
    string  ICoordsUser.ToString() { return VectorUser.ToString(); } 

    IEnumerable<NeighbourCoords> ICoordsUser.GetNeighbours(Hexside hexsides) { 
     return GetNeighbours(hexsides); 
    } 
    int ICoordsUser.Range(ICoordsUser coords) { return Range(coords.Canon); } 
    } 
} 

и

public interface ICoordsCanon { 
    int    X   { get; } 
    int    Y   { get; } 
    IntVector2D  Vector { get; set; } 
    ICoordsCustom Custom { get; } 
    ICoordsUser  User  { get; } 
    //ICoordsCanon Clone(); 
    string   ToString(); 
    int    Range(ICoordsCanon coords); 
    IEnumerable<NeighbourCoords> GetNeighbours(Hexside hexsides); 
    } 
    public partial class Coords { 
    int    ICoordsCanon.X   { get { return VectorCanon.X; } } 
    int    ICoordsCanon.Y   { get { return VectorCanon.Y; } } 
    IntVector2D  ICoordsCanon.Vector  { get { return VectorCanon; } 
               set { VectorCanon=value; } } 
    ICoordsUser  ICoordsCanon.User  { get { return this; } } 
    ICoordsCustom ICoordsCanon.Custom  { get { return this; } } 
    //ICoordsCanon ICoordsCanon.Clone() { return NewCanonCoords(this.VectorCanon); } 
    string   ICoordsCanon.ToString() { return VectorCanon.ToString(); } 

    IEnumerable<NeighbourCoords> ICoordsCanon.GetNeighbours(Hexside hexsides) { 
     return GetNeighbours(hexsides); 
    } 
    int ICoordsCanon.Range(ICoordsCanon coords) { return Range(coords); } 
    } 

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

+0

Мне нравится то направление, в котором вы сейчас находитесь - можете ли вы вставить свои определения интерфейса? –

+0

Я не думаю, что это сработает для меня, потому что я хочу, чтобы определения были разделены (они могут быть конвертируемыми, но они не обязательно должны быть). Кроме того, я хочу, чтобы разработчик обеспечил реализацию для ILocation, а не меня. –

+0

Это похоже на рефакторинг, с которым я сейчас частично справляюсь. Это было первоначально реализовано специально для hexGrids. Теперь я абстрагирую функциональность базового класса только в абстрактном классе Coords, с специальным кодом Hex-grid в подклассе HexCoords. Затем будет построено и протестировано новое воплощение IsometricCoords. Рефакторинг идет гладко, но не будет загружаться в течение нескольких дней. –

0

Вы можете использовать Code Contracts. Пост-состояние. Smth вот так

[ContractClassFor(typeof(IOpportunity))] 
public abstract class OpportunityContract : IOpportunity 
{ 

    public ILocation Location 
    { 
     get { Contract.Ensures(Contract.Result<ILocation>() is IHierarchicalEntityWithParentNames || Contract.Result<ILocation>() is IHierarchicalEntityWithParentIds); } 
    } 
} 
+0

Я не думаю, что это обеспечивает проверку времени компиляции - не так ли? –

Смежные вопросы