2010-07-29 2 views
0

J'ai quelques programmes de sauvegarde personnalisés différents et je veux les combiner en un seul. Ce faisant, je travaille sur ma conception OO pour aider à garder le programme de conduite simple et facile à suivre en extrayant mes informations de serveur et de base de données. J'utilise deux hôtes différents (Discount ASP.net et Rackspace). Les deux vont sur leurs sauvegardes d'une manière légèrement différente.Comment faire pour que ma classe dérivée accepte uniquement un type dérivé spécifique d'un autre objet

J'ai essayé d'examiner quelques approches différentes, mais voici le chemin qui me semble le plus logique.

J'ai ma classe de base:

public abstract class DBServer 
{ 
    public List<Database> Databases { get; set; } 
    public abstract bool MakeBackupCall(Database d); 
} 

puis deux classes dérivées:

public class DASPServer : DBServer 
{ 
    public string APIKey { get; set; } 

    public override bool MakeBackupCall(Database d) 
    { 
     // do some stuff 
     return true; 
    } 
} 

public class RackspaceServer : DBServer 
{ 
    public override bool MakeBackupCall(Database d) 
    { 
     // do some different stuff 
     return true; 
    } 
} 

Le problème vient avec mon objet lié Database. Étant donné que les processus de sauvegarde sont différents pour chaque hôte, j'ai besoin d'informations différentes pour les bases de données. Par exemple, pour Discount ASP.net j'ai besoin d'une version (2000, 2005, 2008) pour la base de données afin que je sache lequel de leurs services Web appeler. Pour Rackspace, j'ai besoin du nom de serveur externe, du nom d'utilisateur de la base de données et des mots de passe pour créer une chaîne de connexion. Pour cette raison, j'ai essayé de créer un objet de base de données avec la hiérarchie suivante.

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

public class DASPDatabase : Database 
{ 
    public string Version { get; set; } 
} 

public class RackspaceDatabase : Database 
{ 
    public string ServerName { get; set; } 
    public string UserName { get; set; } 
    public string Password { get; set; } 

    protected string ConnectionString 
    { 
     get { return string.Format("Data Source={0};Network Library=;Connection Timeout=30;Packet Size=4096;Integrated Security=no;Encrypt=no;Initial Catalog={1};User ID={2};Password={3}", ServerName, Name, UserName, Password); } 
    } 
} 

Ce que je veux faire, est de vous assurer que mon instance DASPServer obtient toujours des cas de DASPDatabases, et même avec Rackspace. Sinon, je voudrais un claquement dans la tête si je vais à ce sujet dans la mauvaise direction.

Un grand merci à l'avance

+0

quelqu'un peut-il penser à un titre plus clair que cela? :) – earthling

Répondre

1

Je serais enclin à extraire complètement la couche d'accès aux données - le mettre derrière une interface (en gros c'est Dependency Inversion), puis développer des implémentations d'accès aux données convient au backend avec lequel vous traitez. La situation dans laquelle vous vous retrouvez est une situation dans laquelle vous simpoly déposer une nouvelle implémentation concrète d'accès aux données dans le répertoire bin (et la configuration appropriée) et vous êtes absent - vous n'avez pas besoin de recompiler et de redéployer tout le système. En fonction des spécificités des dépendances que vous traitez, vous pouvez même combiner les deux implémentations dans le même assemblage. Dans ce cas, il s'agit plus d'un Strategy Pattern.

Dans les deux cas, vous pouvez sélectionner l'implémentation concrète d'accès aux données par config.

0

juste vérifier le type à l'exécution et de jeter un ArgumentException si la base de données est passée du mauvais type.

+0

Cela ressemble à un hack puisque la spécification du paramètre montrera la base de données, mais secrètement, j'ai besoin de DASPDatabase? – earthling

0

Je ne connais aucun moyen de, lors de la compilation, s'assurer que MakeBackupCall() utilise la sous-classe Database correcte. Au moment de l'exécution, cependant, vous pouvez vérifier que le paramètre passé is du sous-type correct.

3

Rendez votre classe DBServer générique, puis spécifiez le type de base de données réel pour les classes d'implémentation.

public abstract class DBServer<TDatabase> where TDatabase : Database 
{ 
    public List<TDatabase> Databases { get; set; } 
    public abstract bool MakeBackupCall(TDatabase d); 
} 

public class DASPServer : DBServer<DASPDatabase> 
{ 
    public string APIKey { get; set; } 

    public override bool MakeBackupCall(DASPDatabase d) 
    { 
     // do some stuff 
     return true; 
    } 
} 

public class RackspaceServer : DBServer<RackspaceDatabase> 
{ 
    public override bool MakeBackupCall(RackspaceDatabase d) 
    { 
     // do some different stuff 
     return true; 
    } 
}  
+0

brillant !! Je vous remercie. Je devrais passer plus de temps à regarder les génériques. C'est la deuxième fois aujourd'hui qu'ils m'ont aidé. – earthling

+0

le problème que je rencontre avec ceci est que je ne peux pas instancier mon objet en tant que DBServer myserver = new DASPServer() afin que je puisse exécuter les méthodes sur un objet DBServer générique, ne se souciant pas du type dérivé spécifique. Un moyen de contourner cela? – earthling

+0

Cette situation devient un peu plus compliquée. Vous devrez peut-être dupliquer l'interface pour qu'il y ait des versions génériques et non génériques. –

1

Vous pouvez cacher la mise en œuvre de base de MakeBackupCall:

public new bool MakeBackupCall(RackspaceDatabase d) 
{ 
    // Do something 
} 

De cette façon, si la méthode est appelée sur une variable RackspaceServer, le compilateur appliquer le type de paramter être BackspaceDatabase. Mais bien sûr cela ne marchera pas si on appelle une variable DBServer, puisque l'implémentation de base sera appelée ...

Un compromis acceptable serait de faire l'implémentation réelle dans une méthode virtuelle protégée qui prend un Database paramètre:

public abstract class DBServer 
{ 
    public List<Database> Databases { get; set; } 

    // Non-virtual; the actual implementation is not done in that method anyway 
    public bool MakeBackupCall(Database d) 
    { 
     return MakeBackupCallCore(d); 
    } 

    // Actual implementation goes there 
    protected abstract MakeBackupCallCore(Database d); 
} 

public class RackspaceServer : DBServer 
{ 
    // Hide the base method 
    public new bool MakeBackupCall(BackspaceDatabase d) 
    { 
     return MakeBackupCallCore(d); 
    } 

    // Do the actual implementation here, and ensure it is really a BackspaceDatabase 
    protected virtual bool MakeBackupCallCore(Database d) 
    { 
     BackspaceDatabase bd = d as BackspaceDatabase; 
     if (bd == null) 
      throw new ArgumentException("d must be a BackspaceDatabase"); 

     // do some different stuff with bd 
     return true; 
    } 
} 

de cette façon, vous pouvez appliquer le type à la compilation lorsque vous utilisez explicitement BackspaceServer et utiliser encore la mise en œuvre dérivée lorsque vous utilisez un DbServer sans connaître son type réel.

Une approche similaire est utilisée dans les classes de ADO.NET: par exemple, SqlConnection.CreateCommand() renvoie un SqlCommand, même si la base DbConnection.CreateCommand() retourne un DbCommand. La méthode de base est cachée, et l'implémentation réelle est protégée abstraite (CreateDbCommand)