Дизайн класса С# без использования внутреннего или статического?

У меня есть куча данных, которые я хочу создать в классе, и для каждой переменной я хочу, чтобы также был определен определенный набор методов. IE:

[TypeA] VarA
[TypeB] VarB
[TypeC] VarC

FA1() which is a function of VarA and VarB
FA2() which is a function of VarA and VarC

FB1() which is a function of VarB and VarA
FB2() which is a function of VarB and VarC
...

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

Я придумал 3 возможных метода организации моего кода, и я не слишком доволен каждым из них, и я ищу или советую, какой метод лучше (или даже если я пропустил совершенно другой метод реализации):

<сильный>1. Частичный класс

partial class Base
{
}

partial class Base 
{
  [TypeA] VarA;

  FA1 { .. };  // function of VarA and VarB
  FA2 { .. };  // function of VarA and VarC
}


partial class Base 
{
  [TypeB] VarB;

  FB1 { .. };  // function of VarB and VarA
  FB2 { .. };  // function of VarB and VarC
}

Плюсы:

  1. Простой
  2. Доступ к переменным возможен только из класса Base.
  3. Если есть две переменные одного типа, то функции для каждой переменной могут по-разному реализовывать свою собственную функцию.

Минусы:

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

Обратите внимание, что Минусы могут быть решены каким-то генератором кода (может быть, пора изучить T4 ??)


<сильный>2. Внутренний класс

class Base 
{
  internal [ClassA] ObjA = new [ClassA]();
  internal [ClassB] ObjB = new [ClassB]();
}

class [BaseClassA]
{
  public [TypeA] VarA;

  public virtual F1 { .. };
  public virtual F2 { .. };
}

class [ClassA] : [BassClassA]
{
  public override F1 { .. };  // function of VarA and ObjB.VarB
  public override F2 { .. };  // function of VarA and ObjC.VarC
}
...

Плюсы:

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

Минусы:

  1. Использование Internal означает, что данные видны везде в сборке.

<сильный>3. Статические данные

abstract class Data
{
   static [TypeA] VarA;
   static [TypeB] VarB;
   ...
}

abstract class [BaseClassA] : Data
{
  public virtual F1 { .. };
  public virtual F2 { .. };
}

class [ClassA] : [BassClassA]
{
  public override F1 { .. };  // function of VarA and VarB
  public override F2 { .. };  // function of VarA and VarC
}

class Base 
{
 [ClassA] ObjA = new [ClassA]();
 [ClassB] ObjB = new [ClassB]();
}

Плюсы:

  1. Система гарантирует, что все подпрограммы созданы
  2. Данные не разлетаются по всей сборке
  3. Внутри каждой функции вы можете напрямую ссылаться на другие переменные в соответствии с решением «частичный класс».

Минусы:

  1. Использование static пахнет так, будто я заново изобрел глобальные данные.

Я хочу каким-то образом выбрать лучшие моменты каждого метода:

  1. Прямой способ доступа к переменным методов «Частичный класс» и «Статический»
  2. Локальные данные метода "Частичный класс"
  3. Автоматическое принудительное выполнение функций методов "Внутренний" и "Статический".

И я хочу избежать:

  1. Отсутствие принудительной генерации функции в «Частичном классе»
  2. Глобальный доступ к данным в методе «Внутренний»
  3. Переизобретение глобальных данных в «Статическом» методе

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

[TypeA] VarA : IFunctions;
[TypeB] VarB : IFunctions;

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

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


person Peter M    schedule 20.09.2009    source источник
comment
вл. мозг болит. как формируются данные.   -  person    schedule 21.09.2009


Ответы (3)


Вы представляете четыре примера кода: «простую» версию, чтобы вы могли объяснить проблему, а затем 3 «лучших» решения для устранения проблемы. Единственной версией, которая говорила сама за себя, была простая версия. Итак, я думаю о бедном разработчике, который должен прийти на поддержку в следующем году (это может быть ты, забывший, что ты сделал).

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

Если вы не знакомы с ним, Microsoft FxCop (также встроенный в некоторые разновидности Visual Studio) сканирует скомпилированные сборки и сопоставляет сотни правил фреймворка с вашим кодом, от правильного написания и регистра переменных до неиспользуемых локальных переменных.

Хотя я лично согласен с большинством правил, которые Microsoft включила в FxCop, я думаю, что настоящая прелесть этого заключается в возможности создавать свои собственные правила. Я создал правила, которые я добавляю в FxCop, которые обеспечивают соблюдение принципов CRUD, например, если у вас есть метод CreateXxx, у вас должен быть метод DeleteXxx и т. д. Таким образом, если вы определите класс, соответствующий желаемому шаблону, вы можете получить список всех переменных {A, B, C}, а затем гарантировать, что FuncAB(A, B) существует и FuncAC(A, C) существует и т. д.

Тогда даже младший разработчик будет пойман FxCop в следующий раз, когда он реализует IBiVariableFunction и забудет функцию в паре.

Привет, Адриан

person Adrian    schedule 22.09.2009
comment
Спасибо за ответ. Я никогда не думал об использовании FxCop предложенным способом, но мой вопрос больше о том, чтобы не создавать кучу кода вручную, чем о том, чтобы прийти постфактум и заставить FxCop сказать мне, что вы этого не сделали. Что касается сложности вопроса. Простая версия и пример №1 идентичны — они отличаются только использованием разделяемых классов для распределения кода по отдельным файлам. Два других примера используют/злоупотребляют простым наследованием для достижения желаемого результата - я не считаю, что это требует подробного объяснения. - person Peter M; 23.09.2009
comment
Интересно, я только что пересмотрел ваши образцы и нарушил одно из своих первоначальных предположений. Изначально я думал, что функции транзитивны по своим аргументам. То есть FuncAB(...) и FuncBA(...) были бы одинаковыми (например, сложение вместо конкатенации), теперь я вижу, что это не так. Итак, не задумываясь над ответом, поможет ли общий интерфейс? Что-то вроде IFunc‹T, U›, для которого требуется Func(T, U) и Func(U, T)? Вероятно, становится довольно уродливым с большим количеством объявленных интерфейсов... - person Adrian; 23.09.2009
comment
Я не уверен, что мне подойдет универсальный интерфейс, но пока я игнорирую проблему. - person Peter M; 25.09.2009

Ваш вопрос в значительной степени лишен какого-либо реального контекста и его трудно понять. Вы дали три «ответа» без четкого вопроса (imo).

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

interface IAStuff {
    TypeA AProp { get; }
    void DoSomethingToA();
}

interface IBStuff {
    TypeB BProp { get; }
    void DoSomethingToB();
}

public class Foo : IAStuff, IBStuff {
    TypeA AProp { get; private set; }
    TypeB BProp { get; private set; }

    void DoSomethingToA() { ... }
    void DoSomethingToB() { ... }
}

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

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

-Ойсин

person x0n    schedule 20.09.2009
comment
Спасибо за ответ, хотя я считаю, что у меня есть четкий вопрос, но я не собираюсь утверждать, что он обязательно ясно представлен :-) Но что касается вашего ответа, я вижу, откуда вы исходите, однако, как написано, я не не думаю, что он хорошо масштабируется, поскольку мне приходится определять интерфейс для каждой переменной (а не только для каждого типа переменной) и, следовательно, по-прежнему вручную поддерживать имена всех методов - то, от чего я надеюсь избавиться. И я не вижу общего интерфейса, помогающего, поскольку он абстрагирует типы, но не имена методов. - person Peter M; 21.09.2009
comment
Если вы замените переменную классом, возможно, подойдет универсальный интерфейс или базовый класс. Один старый мудрый кодер как-то сказал мне: если не можешь заставить это работать, добавь еще классов ;-) - person x0n; 21.09.2009
comment
чтобы уточнить, я имею в виду фактор вне поля. - person x0n; 21.09.2009
comment
Я думаю, что добавление классов приведет меня к решениям № 2 или № 3, которые я предложил. Я не уверен, но думаю, что генератор кода может быть лучшим решением в этом случае. - person Peter M; 21.09.2009

Не могли бы вы использовать предложение 2, но с защищенным вместо внутреннего?

person ICR    schedule 21.09.2009
comment
Моя первоначальная реакция - нет. Защищенный разрешает доступ к членам из классов, производных от базового класса. Однако в этом случае я использую композицию и не получаю ClassA и ClassB от Base. В результате и ClassA, и ClassB должны компилироваться независимо от Base, и я не понимаю, как это сделать без Internal - person Peter M; 21.09.2009