Лучший способ спроектировать мультитиповый объект - PullRequest
3 голосов
/ 10 марта 2009

Допустим, у меня есть объект данных, но этот объект может содержать один из нескольких типов данных.

class Foo
{
    int intFoo;
    double doubleFoo;
    string stringFoo;
}

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

public int GetIntFoo();
public double GetDoubleFoo();
public string GetStringFoo();

Или я мог бы создать несколько свойств

public int IntFoo { get; set; }
public double DoubleFoo { get; set; }
public string StringFoo { get; set; }

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

Один из вариантов - использовать Generics.

class Foo<T>
{
    public T TheFoo { get; set; }
}

Однако, это не создает Foo, оно создает Foo . У каждого свой тип, поэтому я не могу использовать их как один и тот же тип.

Я мог бы извлечь Foo из FooBase, затем обработать все из них как FooBase, но тогда я снова столкнулся с проблемой доступа к данным.

Другой вариант Generics - использовать что-то вроде этого:

class Foo
{
    string stringRepresentationOfFoo;
    public T GetFoo<T>() { return /* code to convert string to type */ }
}

Конечно, проблема в том, что можно передать любой тип T, и, честно говоря, он немного занят.

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

В идеале, я хочу относиться ко всем Foo одинаково, но я хочу безопасность типов, чтобы, если нет StringFoo, я даже не мог скомпилировать ссылку на StringFoo.

Foo foo = new Foo("Foo");
string sFoo = foo.Value;  // succeeds.
&nbsp;
Foo foo = new Foo(0);
int iFoo = foo.Value; // succeeds
string sFoo = foo.Value; // compile error

Возможно, это даже невозможно ... и мне придется пойти на некоторые компромиссы, но, возможно, я что-то упустил.

Есть идеи?

EDIT:

Хорошо, как указывает Даниэль, проверка времени компиляции типа времени выполнения нецелесообразна.

Какой мой лучший вариант для того, что я хочу сделать здесь? А именно, относиться ко всем Foo одинаково, но все же иметь относительно нормальный механизм доступа?

EDIT2:

Я не хочу преобразовывать значение в разные типы. Я хочу вернуть правильный тип для значения. То есть, если это двойное число, я не хочу возвращать int.

Ответы [ 7 ]

4 голосов
/ 10 марта 2009

Как насчет передачи переменной в качестве параметра для get? Как это:

int i = foo.get(i);

Тогда в вашем классе у вас будет что-то вроде:

public int get(int p) {
    if(this.type != INTEGER) throw new RuntimeException("Data type mismatch");
    return this.intVal;
}

public float get(float p) {
    if(this.type != FLOAT) throw new RuntimeException("Data type mismatch");
    return this.floatVal;
}

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

3 голосов
/ 10 марта 2009

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

Что бы вы хотели от этого:

Foo bar = (new Random()).Next(2) == 0 ? new Foo("bar") : new Foo(1);
int baz = bar.Value;

Это ошибка компилятора?

Я думаю, что «относиться к ним одинаково» (по крайней мере, так, как вы это описали) и «ошибка времени компиляции» будут взаимоисключающими.

В любом случае, я думаю, что «лучшим способом» будет компромисс между дженериками и наследованием. Вы можете определить Foo<T>, который является подклассом Foo; тогда вы можете иметь коллекции Foo.

abstract public class Foo
{
  // Common implementation

  abstract public object ObjectValue { get; }
}

public class Foo<T> : Foo
{
   public Foo(T initialValue)
   {
      Value = initialValue;
   }

   public T Value { get; set; }

   public object ObjectValue
   { 
      get { return Value; }
   }
}
1 голос
/ 10 марта 2009

Многие системы используют вспомогательные методы для возврата альтернативных типов так же, как базовый объект .net frameworks имеет метод ToString ()

Выберите лучший базовый тип для каждого вашего объекта и укажите методы для других случаев

, например

class Foo{
    public Int32 Value { get; set; }
    public Byte ToByte() { return Convert.ToByte(Value); }
    public Double ToDouble() { return (Double)Value; }
    public new String ToString() { return Value.ToString("#,###"); }
}
0 голосов
/ 10 марта 2009

Извините, я просто не покупаю ваше помещение. Если все данные имеют одинаковое назначение, то все они должны иметь одинаковый тип. Рассмотрим класс, который предназначен для поддержания текущей температуры, возвращаемой одной из нескольких веб-служб. Все службы возвращают температуру в градусах Цельсия. Но каждый возвращается как int, один возвращается как double, а один возвращает его как строку.

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

В общем, если у вас есть несколько представлений об одной вещи, то это все-таки одно, а не несколько вещей. Преобразуйте несколько представлений в одно.

0 голосов
/ 10 марта 2009

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

Тем не менее, если я правильно читаю, вы хотели бы сохранить какое-то значение в контейнере, передать контейнер клиенту и безопасно получить значение.

При таком подходе вы можете использовать свое предложение, т.е.

namespace Project1 {
public class Class1 {
    static int Main(string[] args) {
        Foo a = new Foo();
        a.SetValue(4);
        Console.WriteLine(a.GetValue<int>());

        Foo b = new Foo();
        a.SetValue("String");
        Console.WriteLine(a.GetValue<string>());

        Console.ReadLine();

        return 0;
    }
}


class Foo {
    private object value; // watch out for boxing here!
    public void SetValue(object value) {
        this.value = value;
    }

    public T GetValue<T>() {
        object val = this.value;
        if (val == null) { return default(T); } // or throw if you prefer
        try {
            return (T)val;
        }
        catch (Exception) {
            return default(T);
            // cast failed, return default(T) or throw
        }
    }
}
}

Однако, в таком случае, почему бы просто не передать данные как объект и привести их самостоятельно?

В зависимости от ваших потребностей, вы также можете попробовать «PHP в C #»:

namespace Project1 {
public class Class1 {
    static int Main(string[] args) {
        MyInt a = 1;
        MyInt b = "2";

        Console.WriteLine(a + b); // writes 3

        Console.ReadLine();

        return 0;
    }
}


class MyInt {
    private int value;

    public static implicit operator int(MyInt container) {
        return container.value;
    }

    public static implicit operator MyInt(int value) {
        MyInt myInt = new MyInt();
        myInt.value = value;
        return myInt ;
    }

    public static implicit operator MyInt(string stringedInt) {
        MyInt myInt = new MyInt();
        myInt.value = int.Parse(stringedInt);
        return myInt;
    }
}
}
0 голосов
/ 10 марта 2009

Почему бы просто не использовать string, double и int?


После информации о коллекции: как насчет использования object? Вы должны будете проверить типы и так далее в любом случае. И чтобы помочь вам в этом, вы можете использовать операторы is и as. И метод Enumerable.Cast , или даже лучше, метод Enumerable.OfType .

0 голосов
/ 10 марта 2009

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

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

Я также видел реализации, в которых класс Value использовался для хранения результатов вычислений, результатов, которые могли бы представлять либо строку, double, int или boolean. Но проблема заключалась в том, что клиентский код должен был проверить свойство Value.Type для перечисления {String, Integer, Double, Boolean}, а затем либо преобразовать свойство Value.Value (которое было открыто внешним способом классом Value как тип объекта. ) или используйте определенные методы получения ValueString, ValueDouble, ValueInt, ValueBoolean.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...