Announcement

Collapse
No announcement yet.

Überladung vs. Abstrakte Klasse

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Überladung vs. Abstrakte Klasse

    Hallo zusammen,

    Das Problem hatte ich jetzt schon ein paar mal und irgendwie ist mir noch immer keine vernünftige Lösung dafür eingefallen. Evtl. wisst ihr ja wie man so etwas sauber löst.

    Folgende Struktur sei gegeben:

    [highlight=c#]
    public abstract class Parent
    {
    public abstract String String1 { get; set; }
    public abstract String String2 { get; set; }
    }

    public class Child1:Parent
    {
    public String String1 { get; set; }
    public String String2 { get; set; }

    public String String4 { get; set; }
    }

    //Natürlich machen die Properties in der Applikation etwas anderes
    public class Child2:Parent
    {
    public String String1 { get; set; }
    public String String2 { get; set; }

    public String String5 { get; set; }
    }
    [/highlight]

    Das wäre mal die Basis. An einer anderen Stelle im Programm würde ich nun gerne etwas mit den Childklassen machen und anhand der Childklasse entscheiden was passieren soll.

    1. Versuch:

    [highlight=c#]
    public static class Converter
    {
    public static String Convert(Child1 child)
    {
    //mach was mit child
    }

    public static String Convert(Child2 child)
    {
    //mach was mit child
    }

    public static String Convert(Parent parent)
    {
    //mach was mit parent
    }
    }
    [/highlight]

    Das Problem hier ist allerdings, dass immer nur die letzte Funktion verwendet wird.

    Dann hab ich noch folgende Variante, funktioniert zwar aber irgendwie find ichs nicht so toll:

    [highlight=c#]
    public static class Converter
    {
    public static String Convert(Parent parent)
    {
    if(parent.GetType() == typeof(Child1))
    //mach was
    ;

    if(parent.GetType() == typeof(Child2))
    //mach was
    ;

    //mach was mit parent
    }
    }
    [/highlight]

    aber irgendwie kann das ja auch nicht das gelbe vom Ei sein. Gefühlt ist das irgendwie schlecht und da muss es eine schickere Lösung geben. Hat jemand vielleicht eine Idee?

    Mir wäre noch eine Lösung mit einem Dictionary<Type,IConverter> eingefallen. Dort kann man für jeden Typ einen IConverter hinterlegen, ansonsten wird der Standardkonverter verwendet. Aber irgendwie find ich das auch nicht richtig. Ich komm aber irgendwie auch nicht auf die "richtige" Lösung insofern es die gibt.

    Würde mich freuen wenn hier jemand eine Idee hat

    Danke schon mal an alle

    Grüße
    FanderlF

  • #2
    Hallo Florian,

    zuerst ein paar Hinweise
    • In .net gibt es keine Funktionen sondern nur Methoden (mit und ohne Rückgabewert)
    • Im 1. Listing müssen die Properties die geerbt werden virtual sein.


    Zurück zur Aufgabe:
    Wenn ich das Vorhaben richtig verstanden habe soll ein "Double Dispatch" umgesetzt werden. Hierzu eignet sich das Visitor/Besucher-Muster. Ich hab dazu mal ein (für mich) verständliches Beispiel erstellt da das Muster m.E. meist nicht richtig rüberkommt


    mfG Gü
    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

    Comment


    • #3
      warum muss die Property virtual sein? Ich kann sie doch auch als abstract deklarieren. Ich würde sagen das kommt auf den Sinn der Properties an. Wenn es wirklich AutoProperties werden, dann könnte ich auch virtual verwenden, da Sie in den SubKlassen ja als AutoProperties verwenden werden. Wenn die in den Subklassen allerdings unterschiedliche Implementierungen haben sollen, dann verwende ich abstract. Dadurch weiss ich auch, dass ich sie in den Subklassen implementieren MUSS. Aber das nur am Rande

      Zum Thema Double Dispatch:
      Das heisst ich implementiere praktisch für jeden Subklassen Typ einen IConverter, werfe die alle in eine Liste und probiere dann einen nach dem anderen durch.

      In etwa so:

      [highlight=c#]
      public interface IConverter
      {
      bool CanConvert(Parent parent);
      String Convert(Parent parent);
      }

      public class Child1Converter:IConverter
      {
      public bool CanConvert(Parent parent) { return parent is Child1; }

      public bool Convert(Parent parent)
      {
      var child = (Child1)parent;

      string result = ...;//hier aus child einen String generieren

      return result;
      }
      }

      //Für Child2 genauso

      //Für Parent genauso

      public static ConverterFactory
      {
      public static IEnumerable<IConverter> GetConverters()
      {
      return new List<IConverter> { new Child1Converter(), new Child2Converter };
      }

      public static IConverter GetStandardConverter()
      {
      return new ParentConverter();
      }
      }

      public static class ParentToStringConverter
      {
      private static IEnumerable<IConverter> converters = ConverterFactory.GetConverters();
      private static IConverter standardConverter = ConverterFactory.GetStandardConverter();

      public static String Convert(Parent parent)
      {
      string result ="";

      bool converted = false;


      foreach(var converter in converters)
      {
      if(converter.CanConvert(parent))
      {
      result = converter.Convert(parent);
      converted = true;
      }
      }

      if(!converted)
      result = standardConverter(parent);

      return result;
      }
      }
      [/highlight]

      Aber irgendwie glaub ich raff ichs immer noch nicht so ganz. "Gefühlt" ist es noch nicht richtig.

      Ach ich hab glaub ich grad verstanden warum das beim Visitor Pattern funktioniert. Im Visitor Pattern implementieren beide nur dieselbe Schnittstelle (IVisitorElement), allerdings besitzen die Orte (Österreich, Kuba) keine gemeinsame Superklasse. Dann funktioniert nämlich dieses Pattern nicht mehr.
      Zuletzt editiert von fanderlf; 09.06.2010, 18:00.

      Comment


      • #4
        Hallo Florian,

        warum muss die Property virtual sein?
        Ich meinte override und hab leider virtual geschrieben - sorry. In deinem 1. Listing müssen die Eigenschaften welche vom Parent geerbt werden überschrieben werden. D.h.:
        [highlight=c#]
        public abstract class Parent
        {
        public abstract String String1 { get; set; }
        public abstract String String2 { get; set; }
        }

        public class Child1:Parent
        {
        public override String String1 { get; set; }
        public override String String2 { get; set; }

        public String String4 { get; set; }
        }
        [/highlight]
        Da ich aber weiß dass dir das klar sollen wir das hier nicht weiter behandeln - dein Code war nur exemplarisch, das ist mir schon klar.

        Zum Rest melde ich mich gleich wieder....muss was dringendes erledigen.


        mfG Gü
        "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

        Comment


        • #5
          Hallo, da bin ich wieder

          Mir ist noch was eingefallen: Eventuell könnte deine 1. Variante mit den Ko-/Kontravarianz-Features in .net 4.0 funktionieren - damit habe ich mich aber noch nicht beschäftig.

          Die Visitor-Lösung:
          [highlight=c#]
          public abstract class Parent
          {
          public abstract string String1 { get; set; }
          public abstract string String2 { get; set; }

          public abstract string Accept(IConverter visitor);
          }
          //-------------------------------------------------------------------------
          public class Child1 : Parent
          {
          public override string String1 { get; set; }
          public override string String2 { get; set; }
          public string String4 { get; set; }

          public override string Accept(IConverter visitor)
          {
          return visitor.Convert(this);
          }
          }
          //-------------------------------------------------------------------------
          public class Child2 : Parent
          {
          public override string String1 { get; set; }
          public override string String2 { get; set; }
          public string String5 { get; set; }

          public override string Accept(IConverter visitor)
          {
          return visitor.Convert(this);
          }
          }
          [/highlight]
          [highlight=c#]
          public interface IConverter
          {
          string Convert(Child1 child1);
          string Convert(Child2 child2);
          }
          //-------------------------------------------------------------------------
          public class Converter : IConverter
          {
          public string Convert(Child1 child1)
          {
          return "Child1";
          }
          //---------------------------------------------------------------------
          public string Convert(Child2 child2)
          {
          return "Child2";
          }
          }
          [/highlight]
          [highlight=c#]
          static void Main(string[] args)
          {
          List<Parent> list = new List<Parent>
          {
          new Child1(),
          new Child2()
          };

          IConverter converter = new Converter();
          list.ForEach(i =>
          {
          string s = i.Accept(converter);
          Console.WriteLine(s);
          });
          Console.ReadKey();
          }
          [/highlight]
          Mit dem Visitor wird also quasi deine 2. Variante mit der Typprüfung umgesetzt allerdings erledigt die Runtime diese Überprüfung.

          Anstatt ein eigenes Interface IVisitorElement zu implementierren habe ich gleich die (abstrakte) Basisklasse verwendet, da die eh schon existiert.
          Zusätzlich hab ich die Schnittstelle IVisitor in IConverter umgenannt da dies besser zum Kontext deiner Aufgabe passt. Genauso wurde Visit in Convert umbenannt.

          Hierbei ergibt sich quasi gratis der Vorteil dass die IConverter einfach getauscht werden können.

          mfG Gü
          "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

          Comment


          • #6
            Das Problem hierbei ist nur dass ich Child1 und Child2 von dem Konverter wissen lassen müsste. Ausserdem war der String return Wert natürlich hypothetisch. Das ginge ja auch auch ganz einfach über .ToString().
            Vielleicht fang ich anders herum an. Alsooooo...

            Ich habe praktisch eine Objektstruktur mit abstraktem Parent und Childs. So und nun sollen diese irgendwie in ein ExcelFile gelangen.
            Dazu hab ich mir ein ExcelExport Klasse definiert die so aussieht:

            [highlight=c#]
            public interface IExport
            {
            void Export(IEnumerable<Parent> parents);
            }

            public class ExcelExport:IExport
            {
            public void Export(IEnumerable<Parent> parents)
            {
            foreach(var parent in parents)
            WriteLineToExcelFile(parent);
            }
            }
            [/highlight]

            So nun möchte ich aber dass Parent nichts von ExcelExport und der Komponente weiss. So sollte man das wohl auch machen
            Aber wie gestalte ich nun die Funktion WriteLineToExcelFile? Dort hänge ich momentan. Ich kann natürlich schon ne Lösung mit drölfzig ifs machen, aber irgendwie muss das doch auch schicker gehen

            Comment


            • #7
              Hallo,

              es kann sein dass wir uns da in etwas zu komplizierten verrant haben. Daher nachfolgend noch meine ursprünglichen Gedanken und unten die aktuellen.

              Ursprünglich:

              So sollte man das wohl auch machen
              Trennung ist gut und recht, aber in bestimmten Fällen (oder eigentlich immer) gibt es irgendwo eine Abhängigkeit. Genauso wäre der triviale Methodenaufruf
              [highlight=c#]
              parent.WriteLineToExcelFile()
              [/highlight]
              aber den willst du nicht. Dies würde das Problem lösen denn Parent ist polymorph und dann wird automatisch die korrekte Überladung gewählt.
              Der Nachteil dabei ist der eben die Abhängigkeit die entsteht. Beim Visitor wäre die Abhängigkeit nur gegen die Schnittstelle und das sehe ich nicht schlimm. Depency Injection wird ja auch akzeptiert (auch wenn es Alternativen gibt)

              Wenn nur
              [highlight=c#]
              WriteLineToExcelFile(parent);
              [/highlight]
              verwendet werden soll so kann doch auf die Eigenschaften des polymorphen Parents zugegriffen werden und die Runtime erledigt die Auswahl des korrekten Typs.


              Aktuell:
              Aber wie gestalte ich nun die Funktion WriteLineToExcelFile?
              In dem die Methode auf die Eigenschaften des polymorphen Parent-Objektes zugreift denn somit werden automatisch die korrekten Eigenschaft des Childs je nach Typ verwendet.

              Sollte nun keine meiner Gedanken stimmen so habe ich wohl nicht verstanden was erreicht werden soll. D.h. was soll WriteLineToExcelFile mit dem Parent-Objekt anfangen? Diese Methode darf nur auf die Eigenschaften zugreifen und die Anforderung etwas nach Excel zu schreiben obliegt dem Converter -> daher sollte Parent bzw. die davon abgeleiteten Klassen sich nicht darum kümmern. Alle Informationen des Objekts sind in den Eigenschaften und diese sind auch vom Converter zugreifbar.


              mfG Gü
              "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

              Comment


              • #8
                http://entwickler-forum.de/showthread.php?t=57573

                Da hatten wir die identische Problematik schonmal durchgekaut. Vielleicht helfen die Gedanken von damals weiter.

                Comment


                • #9
                  Guten Morgen Günther,

                  Hierbei

                  Code:
                  WriteLineToExcelFile(parent);
                  ist nur wieder das Problem dass Parent nicht alle Properties der Unterklassen kennt. Ich kann zwar schon so etwas wie Parameter1 bis 5 einführen, allerdings finde ich das nicht sehr elegant auf die gleiche Sache mit 2 unterschiedlichen Properties zuzugreifen.

                  Ich habe mir für meine Benutzeroberfläche schon ein Interface und diverse Klassen gebaut die mir Polymorphe Struktur Grid-tauglich machen. Diese ganzen Unterklassen sollen nämlich auch in einem Gitter angezeigt werden.
                  Eventuell verwende ich gleich diese Klassen für den Export.

                  Allerdings gibts in der Factory die diese Klassen erzeugt dasselbe Problem. Ich finde es hält sich zwar in Grenzen und es gibt schlimmere Sachen, aber schick ist es trotzdem irgendwie nicht

                  Die Factory sieht so aus:

                  [highlight=c#]
                  public interface ParentVisualizable
                  {
                  String Parameter1Value { get; set; }
                  String Parameter1Name { get; }

                  //Für alle Parameter die im Gitter angezeigt werden sollen
                  }

                  private class Child1Visualizable: IVisualizable
                  {
                  private Child1 _child1;

                  public Child1Visualizable(Child1 child1)
                  {
                  _child1 = child1;
                  }

                  public Parameter1Value
                  {
                  get { return _child1.String4; }
                  set { _child1.String4 = value; }
                  }

                  public Parameter1Name { get { return "String4"; } }

                  // restliche Parameter
                  }

                  public static ParentVisualizableFactory
                  {
                  public static IVisualizable Create(Parent parent)
                  {
                  if(parent is Child1)
                  return new Child1Visualizble((Child1)parent);

                  //falls es keinen speziellen gibt, nimm standard implementierung
                  return StandardVisualizable(parent);
                  }
                  }
                  [/highlight]

                  Ich habe mich an der Stelle auch schon gefragt ob ich das irgendwie anders hinbekomme.

                  Und Danke an Ralf für den Link. Ich muss mich da nochmal tiefer reinlesen

                  Comment


                  • #10
                    Hallo Florian,

                    Code:
                    WriteLineToExcelFile(parent);
                    Du könntest auch per Reflektion über die PropertyInfos des jeweiligen Typs iterieren. Der BinaryFormatter macht das auch so.
                    Das wäre dann in diesem Fall - glaube ich - das einfachste.


                    Die Factory würde ich auf jedenfalls generisch machen -> erspart die if (type is Type) Abfrage. Ralf hat im Link dazu auch ein schönes Beispiel gepostet (gegen Ende) wie die Typen registriert werden können.
                    Anmerkung: Wenn die Typinfos aus einer Konfig-Datei bezogen werden hättest du schon einen einfachen konfigurierbaren ServiceLocator


                    mfG Gü
                    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                    Comment


                    • #11
                      So ich hab jetzt folgende Lösung:

                      [highlight=c#]
                      public static ConverterFactory
                      {
                      static ConverterFactory()
                      {
                      RegisterConverter<Child1, Child1Converter>();
                      RegisterConverter<Child2, Child2Converter>();
                      }

                      private Dictionary<Type, Type> _mappings;

                      private static void RegisterConverter<TParentClass,TConvertClass>()
                      where TParentClass:Parent
                      where TConvertClass:IConverter
                      {
                      _mapping.Add(typeof(TParentClass), typeof(TConvertClass));
                      }

                      public static IConverter GetConverter(Parent parent)
                      {
                      var parentType = parent.GetType();
                      Type converterType = null;

                      if(_mappings.TryGetValue(parentType,converterType) )
                      return (IConverter)Activator.CreateInstance(converterType , parent);

                      return StandardConverter(parent);
                      }
                      }
                      [/highlight]

                      Der Vorteil hier ist, ich muss nicht gleich für jedes Subklasse ein Converter Objekt erzeugen. Es wird dann eben einfach mit dem Standardkonverter ausgegeben, der auf der Parent Klasse arbeitet bzw. ich kann für verschiedene Child klassen denselben Konverter wählen.

                      Das ganze ist zwar leider nicht mehr 100% typsicher, aber ich glaube das kann ich verkraften.

                      Comment


                      • #12
                        Ich würde über IConverter noch eine generische Version von IConverter packen mit dem konkreten zu konvertierenden Typen als den generischen Parameter und dann in der RegisterConverter Methode prüfen ob der Converter auch zur konvertierenden Klasse passt.

                        [Highlight=C#] private static void RegisterConverter<TParentClass,TConvertClass>()
                        where TParentClass:Parent
                        where TConvertClass:IConverter<TParentClass>[/Highlight]

                        Comment


                        • #13
                          cool genau das wollte ich noch. Ich hatte es mit

                          [highlight=c#]
                          private static void RegisterConverter<TParentClass,TConvertClass>()
                          where TParentClass:Parent
                          where TConvertClass:IConverter, new(TParentClass)
                          [/highlight]

                          versucht, aber leider funktioniert die new Einschränkung nur mit einem leeren Konstruktor.

                          Trotzdem noch eine super Idee die ich einbauen werde.

                          Comment


                          • #14
                            Jetzt hab ich doch noch ein Problem. Wenn ich ein generisches Interface machen, dann habe ich ja nachher wieder das Problem, dass ich das nicht mehr universell verwenden kann. Weil dann muss ich beim Verwenden des Interface immer den konkreten Typ angeben. Das hilft mir dann an dieser Stelle leider nicht weiter.

                            Hab aber schon eine andere Lösung

                            [highlight=c#]
                            private static void RegisterConverter<TParentClass,TConvertClass>()
                            where TParentClass:Parent
                            where TConvertClass:ConverterBase<TParentClass>, IConverter
                            {
                            _mapping.Add(typeof(TParentClass), typeof(TConvertClass));
                            }
                            [/highlight]

                            So sieht es jetzt bei mir aus. Damit kann ich jetzt nur Typen registrieren deren Converter als Konstruktorparameter die Klasse erwartet die der Typ hat und die zusätzlich das IConverter interface implementieren

                            Comment

                            Working...
                            X