Как лучше организовать файлы классов и интерфейсов?

17

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


У меня есть два класса ModelOneи ModelTwo, эти классы выполняют похожий тип функциональности, но не связаны друг с другом. Тем не менее, у меня есть третий класс, CommonFuncкоторый содержит некоторые общедоступные функциональные возможности, которые реализованы в обоих ModelOneи ModelTwoбыли учтены в соответствии с DRY. Две модели создаются внутри ModelMainкласса (который сам создается на более высоком уровне и т. Д., Но я останавливаюсь на этом уровне).

Контейнер IoC, который я использую, - это Microsoft Unity . Я не претендую на то, чтобы быть экспертом в этом, но я понимаю, что вы регистрируете кортеж интерфейса и класса в контейнере, а когда вам нужен конкретный класс, вы спрашиваете у контейнера IoC, какой объект соответствует определенному интерфейсу. Это подразумевает, что для каждого объекта, который я хочу создать из Unity, должен быть соответствующий интерфейс. Поскольку каждый из моих классов выполняет разные (и не перекрывающиеся) функциональные возможности, это означает, что между интерфейсом и классом 1 существует соотношение 1: 1 . Однако это не значит, что я пишу интерфейс для каждого класса, который я пишу.

Таким образом, код мудрый я в конечном итоге с 2 :

public interface ICommonFunc 
{ 
}

public interface IModelOne 
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelTwo
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelMain 
{ 
  IModelOne One { get; } 
  IModelTwo Two { get; } 
  ..
}

public class CommonFunc : ICommonFunc { .. }

public class ModelOne : IModelOne { .. }

public class ModelTwo : IModelTwo { .. }

public class ModelMain : IModelMain { .. }

Вопрос в том, как организовать мое решение. Должен ли я держать класс и интерфейс вместе? Или я должен держать классы и интерфейсы вместе? НАПРИМЕР:

Вариант 1 - организованный по имени класса

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-Main
          |   |
          |   |-IModelMain.cs
          |   |-ModelMain.cs
          |
          |-One
          |   |
          |   |-IModelOne.cs
          |   |-ModelOne.cs
          |
          |-Two
              |
              |-IModelTwo.cs
              |-ModelTwo.cs
              |

Вариант 2 - Организован по функциональности (в основном)

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-IModelMain.cs
          |-IModelOne.cs
          |-IModelTwo.cs
          |-ModelMain.cs
          |-ModelOne.cs
          |-ModelTwo.cs
          |

Вариант 3 - Разделение интерфейса и реализация

MySolution
  |
  |-MyProject
      |
      |-Interfaces
      |   |
      |   |-Models
      |   |   |
      |       |-Common
      |       |   |-ICommonFunc.cs
      |       |
      |       |-IModelMain.cs
      |       |-IModelOne.cs
      |       |-IModelTwo.cs
      |
      |-Classes
          | 
          |-Models
          |   |
              |-Common
              |   |-CommonFunc.cs
              |
              |-ModelMain.cs
              |-ModelOne.cs
              |-ModelTwo.cs
              |

Вариант 4. Дальнейший пример функциональности

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Components
          |   |
          |   |-Common
          |   |   |
          |   |   |-CommonFunc.cs
          |   |   |-ICommonFunc.cs
          |   |   
          |   |-IModelOne.cs
          |   |-IModelTwo.cs
          |   |-ModelOne.cs
          |   |-ModelTwo.cs
          |
          |-IModelMain.cs
          |-ModelMain.cs
          |

Мне не нравится вариант 1 из-за имени класса в пути. Но так как я склоняюсь к соотношению 1: 1 из-за моего выбора / использования IoC (и это может быть спорным), у этого есть преимущества в видении отношений между файлами.

Вариант 2 привлекателен для меня, но теперь я замутил воды между ModelMainи суб-моделями.

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

Вариант 4. Я взял вариант 2 и настроил его, чтобы отделить компоненты от родительской модели.

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


1. Фрэнк отметил, что соотношение 1: 1 возвращает нас к дням .h и .cpp файлов C ++. Я знаю, откуда он. Мое понимание Единства, похоже, ставит меня в этот угол, но я также не уверен даже, как выйти из него, если вы также следуете пословице Program to an interface Но это обсуждение для другого дня.

2. Я опустил детали каждого конструктора объектов. Здесь контейнер IoC вводит объекты по мере необходимости.

Питер М
источник
1
Тьфу. Пахнет, что у вас есть соотношение интерфейс / класс 1: 1. Какой контейнер IoC вы используете? Ninject предоставляет механизмы, которые не требуют соотношения 1: 1.
RubberDuck
1
@RubberDuck FWIW Я использую Unity. Я не претендую на то, чтобы быть экспертом в этом, но если мои занятия хорошо спроектированы с одной обязанностью, как я могу получить соотношение почти 1: 1?
Питер М
Вам нужен IBase или база может быть абстрактной? Зачем IDerivedOne, когда он уже реализует IBase? Вы должны зависеть от IBase, а не от производного. Я не знаю о Unity, но другие контейнеры IoC позволяют вам делать «контекстно-зависимые» инъекции. В основном, когда Client1нужно IBase, это обеспечивает Derived1. Когда Client2необходимо IBase, IoC предоставляет Derived2.
RubberDuck
1
Что ж, если база абстрактна, то нет причин interfaceдля этого. Это interfaceдействительно просто абстрактный класс со всеми виртуальными членами.
RubberDuck
1
RubberDuck правильно. Лишние интерфейсы просто раздражают.
Фрэнк Хилман

Ответы:

4

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

Я сомневаюсь, что вы бы предпочли каталог под названием «Базовые классы»; Большинство разработчиков не хотели бы этого, ни каталог под названием «Интерфейсы». В c # каталоги также являются пространствами имен по умолчанию, что вдвойне запутывает.

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

Фрэнк Хилман
источник
Я знаком с предоставленной вами ссылкой, но я не уверен, как она связана с организацией файлов. Хотя VS по умолчанию использует пути к начальному пространству имен, я знаю, что это произвольный выбор. Также я обновил свой пример кода и возможности верстки.
Питер М
@PeterM Я не уверен, какую IDE вы используете, но Visual Studio использует имена каталогов для автоматической генерации объявлений пространства имен, например, при добавлении класса. Это означает, что, хотя у вас могут быть различия между именами каталогов и именами пространств имен, работать таким образом более болезненно. Так что большинство людей этого не делают.
Фрэнк Хайлеман
Я использую VS. И да, я знаю боль. Это возвращает воспоминания о Visual Source Safe макете файла.
Питер М
1
@PeterM Что касается соотношения 1: 1 к классу. Некоторые инструменты требуют этого. Я рассматриваю это как недостаток инструмента - .net имеет достаточно возможностей отражения, чтобы не нуждаться в таких ограничениях в любом таком инструменте.
Фрэнк Хилман
1

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

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

Бернхард Хиллер
источник
Я пересмотрел пример кода и добавил еще несколько опций
Peter M
1

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

Они кодеры, которые будут использовать ваши классы? Тогда лучше всего отделить интерфейсы от кода.

Они сопровождают ваш код? Тогда лучше хранить интерфейсы и классы вместе.

Сядьте и создайте несколько вариантов использования. Тогда лучшая организация может предстать перед вами.

shawnhcorey
источник
-2

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

Лариса Савчекоо
источник