Tworzenie obiektu żądanego typu na podstawie typu innego obiektu

0

Mam problem. Tzn. miałem, rozwiązałem go, ale wydaje mi się to mało fajne. Dochodzę do wniosku, że albo mam skopany projekt aplikacji, albo powinienem użyć jakiegoś wzorca, albo nie wiem sam. ;P

Chodzi o to, że potrzebuję metody, która dla danego obiektu z Modelu zwróci mi obiekt GUI, wcześniej wywołując na nim metodę.
Mój model wygląda tak:

abstract class BaseData
{
    public string Name { get; set; }
}

class StringData : BaseData
{
    public string MyString { get; set; }
}

class IntData : BaseData
{
    public int MyInt { get; set; }
}

interface IGui<T> where T : BaseData
{
    T MyData { get; }
    void Initialize(T item);
}

Natomiast kod klas GUI tak:

class StringGui : BaseGui, IGui<StringData>
{
    private StringData data;

    #region IGui<StringData> Members

    public StringData MyData
    {
        get { return this.data; }
    }

    public void Initialize(StringData item)
    {
        base.Initialize(item);
        this.data = item;
    }

    #endregion

    public override string ToString()
    {
        return string.Format("Name: {0} MyString: {1}", this.Name, this.MyData.MyString);
    }
}

class IntGui : BaseGui, IGui<IntData>
{
    private IntData data;

    #region IGui<IntData> Members

    public IntData MyData
    {
        get { return this.data; }
    }

    public void Initialize(IntData item)
    {
        base.Initialize(item);
        this.data = item;
    }

    #endregion

    public override string ToString()
    {
        return string.Format("Name: {0} MyInt: {1}", this.Name, this.MyData.MyInt);
    }
}

class BaseGui
{
    public string Name { get; private set; }

    protected void Initialize(BaseData key)
    {
        this.Name = key.Name;
    }
}

No i teraz moja metoda - najprostsze rozwiązanie, to if...else:

static BaseGui Create(BaseData key)
{
    Type kt = key.GetType();

    if (kt == typeof(StringData))
    {
        var val = new StringGui();
        val.Initialize(key as StringData);
        return val;
    }
    else if (kt == typeof(IntData))
    {
        var val = new IntGui();
        val.Initialize(key as IntData);
        return val;
    }

    return null;
}

Skuteczne, ale długie i nie DRY.

Wpadłem zatem na pomysł użycia słownika par typ-typ:

static BaseGui Create2(BaseData key)
{
    Dictionary<Type, Type> dict = new Dictionary<Type, Type>()
    {
        {typeof(StringData), typeof(StringGui)},
        {typeof(IntData), typeof(IntGui)},
    };

    Type keyType = key.GetType();

    if (dict.ContainsKey(keyType))
    {
        var guiType = dict[keyType];
        var val = (BaseGui)Activator.CreateInstance(guiType);
        guiType.GetMethod("Initialize").Invoke(val, new object[] { key });
        return val;
    }

    return null;
}

Oczywiście słownik powinien być poza metodą, ale nie o to chodzi teraz.
Nazwę metody "Initialize" mogę wydobyć przez refleksję, nie muszę jej hardcodować. Ale tak czy siak, wydaje mi się to trochę przekombinowane.
Ma ktoś jakiś pomysł/opinię?

0

Przenieść tworzenie Gui do Data?

abstract class BaseData
{
    public string Name { get; set; }

    public abstract BaseGui CreateGui();
}

class StringData : BaseData
{
    public string MyString { get; set; }

    public override BaseGui CreateGui()
    {
        StringGui stringGui = new StringGui();
        stringGui.Initialize(this);

        return stringGui;
    }
}

class IntData : BaseData
{
    public int MyInt { get; set; }

    public override BaseGui CreateGui()
    {
        IntGui intGui = new IntGui();
        intGui.Initialize(this);

        return intGui;
    }
}

I metoda Create:

static BaseGui Create(BaseData key)
{
    return key.CreateGui();
}
0

Dzięki. :) Ale chyba zapomniałem o tym napisać: klasy Data nie mają dostępu do klas GUI - to inne warstwy, oddzielne biblioteki.

0

To może coś takiego, co w C++ nazywa się wirtualny konstruktor i słownik z prototypami?

class StringGui : BaseGui, IGui<StringData>
{
    private StringData data;

    #region IGui<StringData> Members

    public StringData MyData
    {
        get { return this.data; }
    }

    public void Initialize(StringData item)
    {
        base.Initialize(item);
        this.data = item;
    }

    #endregion

    public override string ToString()
    {
        return string.Format("Name: {0} MyString: {1}", this.Name, this.MyData.MyString);
    }

    public override BaseGui Create(BaseData data)
    {
        StringGui gui = new StringGui();

        gui.Initialize(data as StringData);

        return gui;
    }
}

class IntGui : BaseGui, IGui<IntData>
{
    private IntData data;

    #region IGui<IntData> Members

    public IntData MyData
    {
        get { return this.data; }
    }

    public void Initialize(IntData item)
    {
        base.Initialize(item);
        this.data = item;
    }

    #endregion

    public override string ToString()
    {
        return string.Format("Name: {0} MyInt: {1}", this.Name, this.MyData.MyInt);
    }

    public override BaseGui Create(BaseData data)
    {
        IntGui gui = new IntGui();

        gui.Initialize(data as IntData);

        return gui;
    }
}

abstract class BaseGui
{
    public string Name { get; private set; }

    public abstract BaseGui Create(BaseData key);

    protected void Initialize(BaseData key)
    {
        this.Name = key.Name;
    }
}

static Dictionary<Type, BaseGui> guis = new Dictionary<Type, BaseGui>()
{
    { typeof(StringData), new StringGui() },
    { typeof(IntData), new IntGui() }
};

static BaseGui Create(BaseData key)
{
    Type dataType = key.GetType();

    if (!guis.ContainsKey(dataType)) throw new Exception("nie ma odpowiadajacego gui");

    return guis[key.GetType()].Create(key);
}
0

No, wygląda ciekawie. Pomijając fakt, że BaseGui nie może być abstrakcyjne, ale to akurat nie problem.
Tylko, że w takim układzie nie potrzebuję już generycznej metody Initialize, wystarczy mi Create. I to jest właśnie chyba niefajne, że do niej przekazuję obiekt typu bazowego - istnieje "ryzyko", że ktoś może próbować zrobić StringGui z IntData. Generyczna Initialize miała temu zapobiec. Sam już nie wiem co lepsze. :/

0
    
class Creator
    {
        private readonly IDictionary<Type, Type> _map = null;

        public Creator(IDictionary<Type,Type> map)
        {
            this._map = map;
        }

        public BaseGui Create<T>(T data) where T:BaseData
        {
            IGui<T> gui = InnerCreate<T>();
                gui.Initialize(data);

            return gui as BaseGui;
        }

        private IGui<T> InnerCreate<T>() where T:BaseData
        {
            return Activator.CreateInstance(this._map[typeof(T)]) as IGui<T>;
        }
    } 

Tylko jeszcze o to jest brzydkie "return gui as BaseGui;"

Mi osobiście słownik się podoba, tylko nie z typami a z fabrykami na przykład. Oddelegujemy to brzydkie Activator.CreateInstance

0

Metoda fajna, ale ja chyba nie bardzo kumam jak tego mam użyć... Żeby znać parametr T dla każdego elementu w liście, musiałbym go najpierw sprawdzić, a więc if...else, którego chciałem się pozbyć.

0

Dlaczego? C# sam wprowadza parametr T zależnie od tego co podamy
gdy mamy
IntData data;
to wywoując metodę w taki sposób Create(data); automatycznie zostanie wprowadzone za nas Create<IntData>(data);

0

T będzie typem IntData, gdy faktycznie przekażemy tam jawnie obiekt tego typu. Gdy do metody przekazane zostanie IntData, ale w postaci klasy bazowej BaseData (a w taki sposób trzymane będą te obiekty w liście), T będzie typu BaseData.

0

Mam pytanie:

Czy metody MyInt, MyString, MyWhateverTypeItIs muszą się inaczej nazywać? Bo gdyby nie, to wszystko da się załatwić za pomocą typów generycznych..

A zatem - czy muszą się inaczej nazywać ?

0

No jeżeli będzie pod postacią BaseData, to oczywiście że tak, wybieranie metod nie zachodzi at runtime.

0

@dark_astray - no więc właśnie, mam kolekcję BaseData, potrzebuję z niej zrobić konkretne BaseGui.

Deti napisał(a)

Mam pytanie:

Czy metody MyInt, MyString, MyWhateverTypeItIs muszą się inaczej nazywać? Bo gdyby nie, to wszystko da się załatwić za pomocą typów generycznych..

A zatem - czy muszą się inaczej nazywać ?

Tzn. które metody bo nie łapię chyba?

0

Chodzi mu zapewne o to aby zrobić BaseData generycznym i aby zamiast MyInt MyString dać generyczne MyData przykładowo.

0

Aha... Ten przykład jest uproszczony - w rzeczywistości te klasy mają kilka-kilkanaście właściwości różnych typów, więc ten pomysł nie przejdzie.

0

Oj ja myśle że nie ma co tutaj kombinować tylko zrobić to na słowniku.
Tutaj pobawiłem się w stworzenie GuiProvidera

Stworzyłem interfejs fabryki, i GuiProvidera ktory dostarczał będzie nam GUI

    interface IFactory<T, P>
    {
        T Create(P paremter);
    }
    interface IGuiProvider
    {
        void Register<T>(IFactory<IGui<T>, T> factory) where T : BaseData;
        BaseGui GetGui(BaseData data);
    }

Implementacja przykładowej fabryki

    class StringGuiFactory : IFactory<IGui<StringData>,StringData>
    {
        #region IFactory<IGui<StringData>> Members

        public IGui<StringData> Create(StringData parameter)
        {
            return new StringGui().Initialize(parameter); //method chaining jakby co
        }

        #endregion
    }
 

Implementacja GuiProvidera

 
    class GuiProvider : IGuiProvider
    {
        private readonly IDictionary<Type, dynamic> _map = new Dictionary<Type, dynamic>();

        #region IGuiProvider Members

        public void Register<T>(IFactory<IGui<T>, T> factory) where T : BaseData
        {
            this._map[typeof(T)] = factory;
        }

        public BaseGui GetGui(BaseData data)
        {
            return this._map[data.GetType()].Create(data as dynamic);
        }

        #endregion
    }

Gdybyśmy zrezygnowali z fabryki generycznej odeszły by nam te dynamic-y

0

Zapewne użycie fabryk jest najbardziej obiektowe. Ale powoduje powstanie dużo dodatkowego kodu, a mi chodziło o to by było go jak najmniej. Po dopisaniu nowej klasy modelu, nowej klasy GUI, musiałbym też pisać nową fabrykę... Słabo jak dla mnie. A skoro już mam używać jednego dynamica, to mogę użyć ich dwóch:

public static BaseGui Create(BaseData key)
{
    Dictionary<Type, Type> dict = new Dictionary<Type, Type>()
    {
        {typeof(StringData), typeof(StringGui)},
        {typeof(IntData), typeof(IntGui)},
    };

    Type keyType = key.GetType();

    if (dict.ContainsKey(keyType))
    {
        dynamic val = Activator.CreateInstance(dict[keyType]);
        val.Initialize(key as dynamic);
        return val;
    }

    return null;
}

Może to i słabe, ale chyba najlepsze/najbardziej przejrzyste z tego, co mogę zrobić. Sprawa byłaby prosta, gdyby nie to, że wszystko jest generyczne, ale pozbycie się generyczności sprawiłoby wiele problemów z pilnowaniem typów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1