Лучший способ спроектировать многотипный объект

StackOverflow https://stackoverflow.com/questions/629189

  •  08-07-2019
  •  | 
  •  

Вопрос

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

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; }

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

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

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

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

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

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

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

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

Есть какие-нибудь идеи?

Редактировать:

Итак, как указывает Дэниел, проверка типа среды выполнения во время компиляции непрактична.

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

РЕДАКТИРОВАТЬ 2:

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

Это было полезно?

Решение

Как насчет передачи переменной в качестве параметра в 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 проверить, какой тип вы хотите.Если он может предоставить вам этот тип, он это делает, иначе он выдает исключение во время выполнения.

Другие советы

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

Что бы вы хотели, чтобы это делало:

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; }
   }
}

Многие системы используют вспомогательные методы для возврата альтернативных типов точно так же, как базовый объект .net framework имеет метод 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("#,###"); }
}

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

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

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

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


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

На самом деле, какова цель этого класса?Самой большой проблемой, по-видимому, является дизайн, нарушающий, по крайней мере, 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;
    }
}
}

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top