2013-07-05 11 views
18

Ich habe 2 Klassen:ein Verfahren teilen

public class Articles 
{ 
    private string name; 

    public Articles(string name) 
    { 
     this.name = name; 
    } 

    public void Output() 
    { 
     Console.WriteLine("The class is: " + this.GetType()); 
     Console.WriteLine("The name is: " + name); 
    } 
} 

Und

public class Questionnaire 
{ 
    private string name; 

    public Questionnaire(string name) 
    { 
     this.name = name; 
    } 

    public void Output() 
    { 
     Console.WriteLine("The class is: " + this.GetType()); 
     Console.WriteLine("The name is: " + name); 
    } 
} 

Ich möchte ein Verfahren schreiben, die eine Ganzzahl (1 Bedeutung nehmen Articles sollte zurückgegeben werden, 2 bedeutet Questionnaire) und einen Namen.

Diese Methode muss eine Instanz einer dieser beiden Klassen zurück:

public [What type??] Choose(int x, string name) 
    { 
     if (x == 1) 
     { 
      Articles art = new Articles(name); 
      return art; 
     } 
     if (x == 2) 
     { 
      Questionnaire ques = new Questionnaire(name); 
      return ques; 
     } 
    } 

Welche Rückgabetyp soll ich verwenden, so kann ich Output() auf das Ergebnis nennen?

+6

Wenn Sie denken, dass beide Typen (viele) Gemeinsamkeiten haben, sollten Sie beide von derselben Basisklasse erben lassen oder zumindest die gleiche Schnittstelle implementieren. –

+0

Dies wird allgemein als [Enteneingabe] (http://en.wikipedia.org/wiki/Duck_typing) bezeichnet - auf jeden Fall mit C# möglich (siehe ['dynamic'] (http://msdn.microsoft.com/de-de/) Bibliothek/dd264741.aspx)), aber überlegen Sie sich, stark typisierte Lösungen in den Antworten zu verwenden. –

Antwort

27

Warum nicht eine base class, die Output definiert hat. Dann gib die Basis zurück.

public abstract class BaseType { 
    public abstract void Output(); 
} 

Beide Articles und Questionaire sollten diese BaseType erben.

public class Articles : BaseType { 
    // Output method here 
} 

public class Questionaire : BaseType { 
// Output method here 
} 

Dann können Sie tun:

public static BaseType Choose(int x, string name) 
{ 
    if (x == 1) 
    { 
     Articles art = new Articles(name); 
     return art; 
    } 
    if (x == 2) 
    { 
     Questionnaire ques = new Questionnaire(name); 
     return ques; 
    } 
} 

Sie könnten auch diese über eine interface erreichen.

public interface IInterface { 
    void Output(); 
} 

public class Articles : IInterface { 
    // Output method here 
} 

public class Questionaire : IInterface { 
// Output method here 
} 

Sie würden dann die Wählen Sie ändern müssen, um Methode IInterface zurückzukehren, anstatt BaseType. Was auch immer Sie wählen, bleibt Ihnen überlassen.

Hinweis: auch wenn Sie nicht original Klassen ändern können, können Sie immer noch diese Ansätze verwenden, bevor zu dynamic zugreifen von Wrapper-Klassen bereitstellt, die die Schnittstelle implementieren und entweder erbt Original oder leitet Anrufe an entsprechende Methode:

public class ArticlesProxy : Articles, IInterface 
{ 
    public ArticlesProxy(string name) : base(name){} 

} 

public class QuestionaireProxy : Questionaire, IInterface { 
    Questionaire inner; 
    public QuestionaireProxy(string name) { inner = new Questionaire(name); } 

    public void Output() { inner.Output();} 

} 
+0

Eine gute Praxis ist entweder ein Basistyp, wie eine druckbare Klasse, pro Beispiel oder eine Schnittstelle wie IPrintable. –

+1

+1, da es genauso gültig ist wie eine Schnittstelle. –

+0

Beachten Sie, dass ich diesen Namen verwendet, so dass es sich auf Ihre Klassen bezieht –

5

Wenn sie nicht die gleiche Basisklasse oder Schnittstelle teilen, sind Sie entweder mit object oder dynamic fest.

+0

Das ist wirklich nie notwendig - das ist nur schlecht Code-Design – TGlatzer

+3

Ich habe nicht vorgeschlagen, es war gutes Design. Nur das mit dem Design wie gesagt, das sind die Optionen. –

15

Wie wäre es etwa so:

public interface IHasOutput 
{ 
    void Output(); 
} 

public class Articles : IHasOutput 

public class Questionnaire : IHasOutput 

und dann:

public static IHasOutput Choose... 

Sie können natürlich Ihre Schnittstelle anrufen, was Sie möchten, außer IHasOutput, ich weiß einfach nicht, wie ich es nennen soll. Dafür gibt es Schnittstellen. Zwei verschiedene konkrete Implementierungen, die sich eine gemeinsame Schnittstelle teilen. Wenn Sie es jetzt anrufen, können Sie Folgendes tun:

und es spielt keine Rolle, welche konkrete Implementierung zurückgegeben wird. Sie wissen, dass es eine gemeinsame Schnittstelle implementiert.

+0

+1 wurde gleich geschrieben Lösung, als deine erschien !! grrr :) –

+0

@jimtollan, tolle Köpfe denken gleich! –

+1

Sie sollten 'IEntity' in' IHasOutput' umbenennen, da es sich um ein sehr spezifisches * Verhalten * und nicht um einen gemeinsamen * Deskriptor * der gesamten Klasse handelt. Aber es ist immer noch eine sehr gute Antwort. –

0

Sie haben 3 Möglichkeiten:

1) Stellen Sie Fragebogen und Artikel aus der gleichen Basisklasse erben und die Art dieser Basisklasse machen den Rückgabetyp Ihrer Methode.

2) Geben Sie den Rückgabetyp Objekt ein.

3) Geben Sie den Rückgabetyp Dynamisch ein.

1

Der flexibelste Weg, um dieses Problem zu lösen, besteht darin, eine Schnittstelle sowie eine abstrakte Basisklasse zu schreiben, die es implementiert. Auf diese Weise haben Sie die Freiheit, eine Klasse von der Basisklasse abzuleiten oder die Schnittstelle direkt zu implementieren, wenn die Basisklasse in einem speziellen Fall Ihre Anforderungen nicht erfüllt oder wenn eine Klasse bereits von einer anderen Klasse abgeleitet ist. Machen Sie auch die Methode Output virtuell; Dadurch können Sie es bei Bedarf überschreiben. Machen Sie auch die name geschützt; dies ermöglicht es Ihnen, es in abgeleiteten Klassen verwenden

public interface IHasOutput 
{ 
    void Output(); 
} 

public abstract class OutputBase : IHasOutput 
{ 
    protected string _name; 

    public OutputBase(string name) 
    { 
     _name = name; 
    } 

    #region IHasOutput Members 

    public virtual void Output() 
    { 
     Console.WriteLine("The class is: " + this.GetType()); 
     Console.WriteLine("The name is: " + _name); 
    } 

    #endregion 

    public static IHasOutput Choose(int x, string name) 
    { 
     switch (x) { 
      case 1: 
       return new Articles(name); 
      case 2: 
       return new Questionnaire(name); 
      default: 
       return null; 
     } 
    } 
} 

public class Articles : OutputBase 
{ 
    public Articles(string name) 
     : base(name) 
    { 
    } 
} 

public class Questionnaire : OutputBase 
{ 
    public Questionnaire(string name) 
     : base(name) 
    { 
    } 
} 

UPDATE

Eine weitere sehr einfache Art und Weise das Problem zu lösen ist ToString außer Kraft zu setzen:

public override string ToString() 
{ 
    return String.Format("The class is: {0}\r\nThe name is: {1}", 
         this.GetType(), _name); 
} 

Sie würden es nennen so:

object obj = Factory.Choose(1, "Test"); 
Console.WriteLine(obj); 

Keine Schnittstelle und keine Basisklasse erforderlich! Nun, um genau zu sein, die Basisklasse ist natürlich object.

7

Die hier angegebenen Antworten sind großartig, aber eine Sache, die ich nicht mag, ist der Parameter x, der wählt, welcher Typ erstellt werden soll. Das erzeugt die Verwendung von magic number, die sogar später für Sie Kopfschmerz werden kann.

Sie können hier die Vorteile von Generika nehmen, das heißt machen Methode Choose:

public static T Choose<T>(string name) 
     // type constraint to ensure hierarchy. 
     where T : BaseClass // BaseClass have common functionality of both class. 
    { 
     // Unfortunately you can't create instance with generic and pass arguments 
     // to ctor. So you have to use Activator here. 
     return (T)Activator.CreateInstance(typeof(T), new[] { name }); 
    } 

Verbrauch:

Articles article = ClassWithChooseMethod.Choose<Articles>("name"); 
Questionnaire questionnaire = ClassWithChooseMethod.Choose<Questionnaire>("name2"); 

Demo

bearbeiten

Als @OlivierJaco t-Descombes in Kommentar x erwähnt, die Typ kann Benutzereingabe sein. In diesem Fall können Sie enum mit den jeweiligen Werten erstellen:

enum ArticleType { 
    Articles = 1, 
    Questionnaire = 2 
} 

Und haben Überlastung von Choose:

public static BaseClass Choose(ArticleType type, string name) { 
    switch (type) { 
     case ArticleType.Articles: 
      return ClassWithChooseMethod.Choose<Articles>(name); 
     case ArticleType.Questionnaire: 
      return ClassWithChooseMethod.Choose<Questionnaire>(name); 
     default: 
      return default(BaseClass); 
    } 
} 

und Nutzung:

var obj = ClassWithChooseMethod.Choose((ArticleType)userInput, "some name"); 

Diese Sie Möglichkeit Ihren Code sauber zu halten gibt und nützlich für zukünftige Wartung (zB können Sie die Logik der Klassenerstellung in Choose ändern).

P.S. Sie könnten daran interessiert sein, mehr über factory pattern zu lesen.

+0

Und woher bekommst du den Typ 'T'? Höchstwahrscheinlich ist der Wert von "x" nicht fest codiert, sondern kommt von einer Benutzereingabe. –

+0

@ OlivierJacot-Descombes Vielleicht ist es, vielleicht nicht. Es wurde nicht in OP erwähnt. Und selbst wenn es so wäre, würde ich 'enum' erstellen, um Typen zu handhaben und' switch' außerhalb der 'Choose' Methode zu verwenden, oder ich hätte seine nicht generische Überladung, die intern eine generische Methode verwenden würde, um den entsprechenden Typ zurückzugeben. – Leri