Доступ к статическому свойству дочернего объекта в родительском методе. Особенности проектирования - PullRequest
6 голосов
/ 16 мая 2011

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

Вот ситуация, которую я хочу обсудить с вами.

Я хочу реализовать некоторые типы данных, учитывающие единицу, такие как длина, масса, ток, ... Должно быть неявное приведение для создания экземпляров из заданной строки. Например, «1,5 м» должно соответствовать значению «150 см», или «20 дюймов» следует обрабатывать правильно.

Чтобы иметь возможность конвертировать между различными единицами, мне нужны количественные константы преобразования. Моя идея состояла в том, чтобы создать абстрактный базовый класс с некоторыми статическими методами перевода. Они должны использовать статически определенный словарь для своей работы. Итак, посмотрите на пример.

public class PhysicalQuantities
{
    protected static Dictionary<string, double> myConvertableUnits;

    public static double getConversionFactorToSI(String baseUnit_in)
    {
        return myConvertableUnits[baseUnit_in];
    }
}

public class Length : PhysicalQuantities
{
    protected static Dictionary<string, double> myConvertableUnits = new Dictionary<string, double>()
    {
        { "in", 0.0254 }, { "ft", 0.3048 }
    };
}

class Program
{
    static void Main(string[] args)
    {
        Length.getConversionFactorToSI("in");
    }
}

Я думаю, что это дает довольно интуитивное использование и делает код компактным, достаточно читабельным и расширяемым. Но, конечно же, я столкнулся с теми же проблемами, о которых упоминает post .

Теперь у меня вопрос: как я могу избежать этих проблем при разработке?

Ответы [ 3 ]

4 голосов
/ 16 мая 2011

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

Так что, если я не ошибаюсь:

  • потокобезопасен (все работает со словарем в статическом конструкторе)
  • синтаксис по-прежнему прост в использовании и читабелен SIConversion<Length>.GetFactor() (на 1 символ больше)
  • код, необходимый для реализации на очень классных производных классах register(string,double); (на самом деле короче, чем определение в вашем словаре)

    interface ISIConversionSubscriber
    {
        void Register(Action<string, double> regitration);
    }
    
    static class SIConversion<T> where T : ISIConversionSubscriber, new()
    {
    
        private static Dictionary<string, double> myConvertableUnits = new Dictionary<string, double>();
    
        static SIConversion() {
            T subscriber = new T();
            subscriber.Register(registrationAction);
        }
    
        public static double GetFactor(string baseUnit)
        {
            return myConvertableUnits[baseUnit];
        }
    
        private static void registrationAction(string baseUnit, double value)
        {
            myConvertableUnits.Add(baseUnit, value);
        }
    
    }
    
    abstract class PhysicalQuantities : ISIConversionSubscriber
    {
        public abstract void Register(Action<string, double> register);
    }
    
    class Length : PhysicalQuantities
    {
        public override void Register(Action<string, double> register)
        {
            // for each derived type register the type specific values in this override
            register("in", 1);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(SIConversion<Length>.GetFactor("in"));
        }
    }
    

Выход: 1

В случае, если вам интересно, почему я сделал PhysicalQuantities абстрактным: избегать его использования с SIConversion<PhysicalQuantities>.GetFactor(), поскольку у нас нет преобразования для базового класса. В любом случае вам, вероятно, не нужны экземпляры базового класса, такого как этот, - это не полное представление количества, поэтому оно, вероятно, будет содержать только повторно используемые методы.

Другим предложением было бы использовать Enum для baseUnit вместо строки. Поскольку все стремятся к безопасности типов и кричат ​​о магических нитях, это, вероятно, хороший путь, по которому нужно следовать:))

3 голосов
/ 16 мая 2011

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

static void Main()
{
    // Since I'm working with instances, I tend to pass the actual 
    // measured amount in as well...  However, you could leave this out (or pass 1)
    var len = Length.Create(42, "in"); 
    double conversionFactory = len.ConversionFactorToSI;
}

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

2 голосов
/ 16 мая 2011

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

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

...