【问题标题】:How to have my derived class only accept a specific derived type of another object如何让我的派生类只接受另一个对象的特定派生类型
【发布时间】:2011-03-22 23:41:06
【问题描述】:

我有几个不同的自定义备份程序,我想将它们组合成一个。 这样做,我正致力于我的 OO 设计,通过抽象我的服务器和数据库信息来帮助保持驱动程序简单易懂。

我正在使用两个不同的主机(折扣 ASP.net 和 Rackspace)。他们都以稍微不同的方式进行备份。

我尝试了几种不同的方法,但这是我认为最有意义的路线。

我有我的基类:

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

然后是两个派生类:

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;
    }
}

问题在于我的相关对象Database。 由于每个主机的备份过程不同,因此我需要不同的数据库信息。例如,对于 Discount ASP.net,我需要数据库的版本(2000、2005、2008),这样我就知道要调用他们的哪些 Web 服务。对于 Rackspace,我需要外部服务器名称、数据库用户名和密码来创建连接字符串。因此,我尝试创建具有以下层次结构的数据库对象。

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); }
    }
}

我想要做的是确保我的 DASPServer 实例始终获取 DASPDatabases 实例,对于 Rackspace 也是如此。或者,如果我在错误的方向上做这件事,我会想要一个耳光。

在此先感谢

【问题讨论】:

  • 谁能想到比这更清晰的标题? :)

标签: c# .net architecture inheritance oop


【解决方案1】:

只需在运行时检查类型,如果传递的数据库类型错误,则抛出ArgumentException。

【讨论】:

  • 这听起来像个 hack,因为参数规范会显示 Database,但我偷偷需要 DASPDatabase?
【解决方案2】:

我不知道有什么方法可以在编译时确保MakeBackupCall() 使用正确的Database 子类。但是,在运行时,您可以检查传递的参数 is 的子类型是否正确。

【讨论】:

    【解决方案3】:

    使您的 DBServer 类通用,然后为实现类指定实际的数据库类型。

    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;
        }
    }    
    

    【讨论】:

    • 太棒了!!谢谢你。我应该花更多时间研究泛型。这是他们今天第二次帮助我。
    • 我遇到的问题是我无法将我的对象实例化为 DBServer myserver = new DASPServer() 所以我可以在通用 DBServer 对象上执行方法,而不关心哪个特定派生输入它。有什么办法吗?
    • 这种情况确实变得有点棘手。您可能必须复制接口,以便有通用和非通用版本。
    【解决方案4】:

    您可以隐藏MakeBackupCall 的基本实现:

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

    这样,如果在RackspaceServer 变量上调用该方法,编译器将强制参数的类型为BackspaceDatabase。但是如果在 DBServer 变量上调用它当然不会起作用,因为会调用基本实现......

    一个可接受的折衷方案是在采用Database 参数的受保护虚拟方法中进行实际实现:

    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;
        }
    }
    

    这样,当您显式使用BackspaceServer 时,您可以在编译时强制执行该类型,并且在您使用DbServer 时仍然使用派生实现而不知道其实际类型。

    在 ADO.NET 类中使用了类似的方法:例如,SqlConnection.CreateCommand() 返回 SqlCommand,即使基础 DbConnection.CreateCommand() 返回 DbCommand。基本方法是隐藏的,实际实现是protected abstract (CreateDbCommand)

    【讨论】:

      【解决方案5】:

      我倾向于完全抽象出数据访问层——把它放在一个接口后面(基本上是Dependency Inversion),然后开发适合您正在处理的后端的数据访问实现。您最终遇到的情况是,您只需将新的具体数据访问实现放入 bin 目录(和适当的配置),然后您就离开了 - 您不必重新编译和重新部署整个系统。

      根据您正在处理的依赖项的具体情况,您甚至可以将这两个实现组合到同一个程序集中。在这种情况下,它更像是Strategy Pattern。

      在这两种情况下,您都可以通过配置选择具体的数据访问实现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-28
        • 1970-01-01
        • 1970-01-01
        • 2012-03-05
        • 1970-01-01
        • 1970-01-01
        • 2011-11-14
        • 2016-12-12
        相关资源
        最近更新 更多