2013-08-19 20 views
6

Ho detto 3 classi, Animali, Gatto & Cane.È possibile creare una classe derivata da un costruttore della classe base?

// calling code 
var x = new Animal("Rex"); // would like this to return a dog type 
var x = new Animal("Mittens"); // would like this to return a cat type 

if(x.GetType() == typeof(Dog)) 
{ 
    x.Bark(); 
} 
else 
{ 
    x.Meow(); 
} 


class Animal 
{ 
    public Animal(string name) 
    { 
     // check against some list of dog names ... find rex 
     // return Animal of type Dog. 

     // if not... 

     // check against some list of cat names ... find mittens 
     // return Animal of type Cat. 
    } 
} 

E 'possibile in qualche modo? Se no, c'è qualcosa di simile che posso fare?

+2

In realtà non senza limitare se stessi ... La vostra base di classe avrebbe bisogno di conoscere le sue sottoclassi, che non è molto utile. –

risposta

9

Quello che stai cercando è un "costruttore virtuale" (non possibile in C#) o il modello di fabbrica.

class Animal 
{ 
    // Factory method 
    public static Animal Create(string name) 
    { 
     Animal animal = null; 
     ... // some logic based on 'name' 
      animal = new Zebra(); 

     return animal; 
    } 
} 

Il metodo di fabbrica può anche essere inserito in un'altra classe (di fabbrica). Che dà migliore disaccoppiamento ecc

5

No. Fondamentalmente la correzione corretto è utilizzare un metodo statico che può creare un'istanza del tipo giusto:

var x = Animal.ForName("Rex"); 
var x = Animal.ForName("Mittens"); 

... 

public abstract class Animal 
{ 
    public static Animal ForName(string name) 
    { 
     if (dogNames.Contains(name)) 
     { 
      return new Dog(name); 
     } 
     else 
     { 
      return new Cat(name); 
     } 
    } 
} 

O questo potrebbe essere un metodoesempio in un Tipo AnimalFactory (o qualsiasi altra cosa). Sarebbe un approccio più estensibile - la fabbrica potrebbe implementare un'interfaccia, per esempio, e potrebbe essere iniettata nella classe che aveva bisogno di creare le istanze. Dipende molto dal contesto, però - a volte questo approccio è eccessivo.

In sostanza, una new Foo(...) chiamata sempre crea un'istanza di esattamenteFoo. Mentre un metodo statico dichiarato con un tipo di ritorno Foo può restituire un riferimento a qualsiasi tipo che sia compatibile con .

1

No Non penso che sia possibile nel modo desiderato.

È possibile creare una classe statica con un metodo che restituisce un animale basato su un nome, ad es.

0

Le altre risposte mostrano che è necessario utilizzare un modello di fabbrica ma volevo darti un esempio più "pratico" di come lo faresti. Ho fatto esattamente quello che stai facendo, tuttavia stavo lavorando con lo EPL2 printer language. Quando ho visto X avevo bisogno di creare un'istanza di classe Rectangle, quando ho visto A avevo bisogno di creare un'istanza di classe Text.

(Ho scritto questo a long time ago quindi sono sicuro che alcune delle cose che ho fatto potrebbero essere migliorate).

public partial class Epl2CommandFactory 
{ 
    #region Singelton pattern 
    private static volatile Epl2CommandFactory m_instance; 
    private static object m_syncRoot = new object(); 

    public static Epl2CommandFactory Instance 
    { 
     get 
     { 
      if (m_instance == null) 
      { 
       lock (m_syncRoot) 
       { 
        if (m_instance == null) 
        { 
         m_instance = new Epl2CommandFactory(); 
        } 
       } 
      } 
      return m_instance; 
     } 
    } 
    #endregion 

    #region Constructor 
    private Epl2CommandFactory() 
    { 
     m_generalCommands = new Dictionary<string, Type>(); 
     Initialize(); 
    } 
    #endregion 

    #region Variables 
    private Dictionary<string, Type> m_generalCommands; 

    private Assembly m_asm; 
    #endregion 

    #region Helpers 
    private void Initialize() 
    { 
     Assembly asm = Assembly.GetAssembly(GetType()); 
     Type[] allTypes = asm.GetTypes(); 
     foreach (Type type in allTypes) 
     { 
      // Only scan classes that are not abstract 

      if (type.IsClass && !type.IsAbstract) 
      { 
       // If a class implements the IEpl2FactoryProduct interface, 

       // which allows retrieval of the product class key... 

       Type iEpl2FactoryProduct = type.GetInterface("IEpl2GeneralFactoryProduct"); 
       if (iEpl2FactoryProduct != null) 
       { 
        // Create a temporary instance of that class... 

        object inst = asm.CreateInstance(type.FullName); 

        if (inst != null) 
        { 
         // And generate the product classes key 

         IEpl2GeneralFactoryProduct keyDesc = (IEpl2GeneralFactoryProduct)inst; 
         string key = keyDesc.GetFactoryKey(); 
         m_generalCommands.Add(key, type); 
         inst = null; 
        } 
       } 
      } 
     } 
     m_asm = asm; 
    } 
    #endregion 

    #region Methods 
    public IEpl2Command CreateEpl2Command(string command) 
    { 
     if (command == null) 
      throw new NullReferenceException("Invalid command supplied, must be " + 
              "non-null."); 

     Type type; 
     if (!m_generalCommands.TryGetValue(command.Substring(0, 2), out type)) 
      m_generalCommands.TryGetValue(command.Substring(0, 1), out type); 
     if (type != default(Type)) 
     { 
      object inst = m_asm.CreateInstance(type.FullName, true, 
               BindingFlags.CreateInstance, 
       null, null, null, null); 

      if (inst == null) 
       throw new NullReferenceException("Null product instance. " + 
        "Unable to create necessary product class."); 

      IEpl2Command prod = (IEpl2Command)inst; 
      prod.CommandString = command; 
      return prod; 
     } 
     else 
     { 
      return null; 
     } 
    } 
    #endregion 
} 

Il modo in cui il codice funziona è uso il singleton pattern per creare un factory class così la gente può chiamare var command = Epl2CommandFactory.Instance.CreateEpl2Command("..."); passando la stringa di comando EPL2 e restituisce un'istanza della classe che rappresenta quella classe specifica.

Durante l'inizializzazione uso la riflessione per trovare le classi che supportano l'interfaccia IEpl2GeneralFactoryProduct, se la classe supporta l'interfaccia la fabbrica memorizza il codice a una o due lettere che rappresenta il comando della stampante in un dizionario di tipi.

Quando si tenta di creare il comando, la fabbrica ricerca il comando della stampante nel dizionario e crea la classe corretta, quindi passa la stringa di comando completa su quella classe per un'ulteriore elaborazione.

Ecco una copia di una classe di comando ed è genitori, se si voleva vedere che

Rectangle:

[XmlInclude(typeof(Rectangle))] 
public abstract partial class Epl2CommandBase { } 

/// <summary> 
/// Use this command to draw a box shape. 
/// </summary> 
public class Rectangle : DrawableItemBase, IEpl2GeneralFactoryProduct 
{ 
    #region Constructors 
    public Rectangle() : base() { } 
    public Rectangle(Point startingLocation, int horozontalEndPosition, int verticalEndPosition) 
     : base(startingLocation) 
    { 
     HorizontalEndPosition = horozontalEndPosition; 
     VerticalEndPosition = verticalEndPosition; 
    } 
    public Rectangle(int x, int y, int lineThickness, int horozontalEndPosition, int verticalEndPosition) 
     : base(x, y) 
    { 
     LineThickness = lineThickness; 
     HorizontalEndPosition = horozontalEndPosition; 
     VerticalEndPosition = verticalEndPosition; 
    } 
    #endregion 

    #region Properties 
    [XmlIgnore] 
    public int LineThickness { get; set; } 
    [XmlIgnore] 
    public int HorizontalEndPosition {get; set;} 
    [XmlIgnore] 
    public int VerticalEndPosition { get; set; } 

    public override string CommandString 
    { 
     get 
     { 
      return String.Format("X{0},{1},{2},{3},{4}", X, Y, LineThickness, HorizontalEndPosition, VerticalEndPosition); 
     } 
     set 
     { 
      GenerateCommandFromText(value); 
     } 
    } 
    #endregion 

    #region Helpers 
    private void GenerateCommandFromText(string command) 
    { 
     if (!command.StartsWith(GetFactoryKey())) 
      throw new ArgumentException("Command must begin with " + GetFactoryKey()); 
     string[] commands = command.Substring(1).Split(','); 
     this.X = int.Parse(commands[0]); 
     this.Y = int.Parse(commands[1]); 
     this.LineThickness = int.Parse(commands[2]); 
     this.HorizontalEndPosition = int.Parse(commands[3]); 
     this.VerticalEndPosition = int.Parse(commands[4]); 

    } 
    #endregion 

    #region Members 
    public override void Paint(Graphics g, Image buffer) 
    { 
     using (Pen p = new Pen(Color.Black, LineThickness)) 
     { 
      g.DrawRectangle(p, new System.Drawing.Rectangle(X, Y, HorizontalEndPosition - X, VerticalEndPosition - Y)); 
     } 

    } 

    public string GetFactoryKey() 
    { 
     return "X"; 
    } 
    #endregion 
} 

DrawableItemBase:

public abstract class DrawableItemBase : Epl2CommandBase, IDrawableCommand 
{ 
    protected DrawableItemBase() 
    { 
     Location = new Point(); 
    } 
    protected DrawableItemBase(Point location) 
    { 
     Location = location; 
    } 
    protected DrawableItemBase(int x, int y) 
    { 
     Location = new Point(); 
     X = x; 
     Y = y; 
    } 
    private Point _Location; 
    [XmlIgnore] 
    public virtual Point Location 
    { 
     get { return _Location; } 
     set { _Location = value; } 
    } 

    [XmlIgnore] 
    public int X 
    { 
     get { return _Location.X; } 
     set { _Location.X = value; } 
    } 
    [XmlIgnore] 
    public int Y 
    { 
     get { return _Location.Y; } 
     set { _Location.Y = value; } 
    } 

    abstract public void Paint(Graphics g, Image buffer); 
} 

Epl2CommandBase:

public abstract partial class Epl2CommandBase : IEpl2Command 
{ 
    protected Epl2CommandBase() { } 

    public virtual byte[] GenerateByteCommand() 
    { 
     return Encoding.ASCII.GetBytes(CommandString + '\n'); 
    } 
    public abstract string CommandString { get; set; } 
} 

varie interfacce:

public interface IEpl2GeneralFactoryProduct 
{ 
    string GetFactoryKey(); 
} 
public interface IEpl2Command 
{ 
    string CommandString { get; set; } 
} 

public interface IDrawableCommand : IEpl2Command 
{ 
    void Paint(System.Drawing.Graphics g, System.Drawing.Image buffer); 
} 
Problemi correlati