Как заставить мой производный класс принимать только определенный производный тип другого объекта - PullRequest
0 голосов
/ 30 июля 2010

У меня есть несколько разных программ резервного копирования, и я хочу объединить их в одну.Делая так, я работаю над своим OO-дизайном, чтобы упростить и ускорить выполнение программы вождения, абстрагируя информацию о моем сервере и базе данных.

Я использую два разных хоста (Discount 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) для базы данных, чтобы я знал, какой из их веб-сервисов вызывать.Для 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.В качестве альтернативы, я хотел бы получить удар в голову, если я собираюсь сделать это в неправильном направлении.

Большое спасибо заранее

Ответы [ 5 ]

3 голосов
/ 30 июля 2010

Сделайте ваш класс 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;
    }
}    
1 голос
/ 30 июля 2010

Я был бы склонен полностью абстрагироваться от уровня доступа к данным - поместить его за интерфейс (в основном это Dependency Inversion ), а затем разработать реализации доступа к данным, подходящие для бэкэнда, с которым вы имеете дело.В результате вы получите простую установку новой конкретной реализации доступа к данным в каталог bin (и соответствующую конфигурацию), и вас уже нет - вам не нужно перекомпилировать и заново развертывать всю систему.

В зависимости от специфики зависимостей, с которыми вы имеете дело, вы можете даже объединить две реализации в одну сборку.В этом случае это скорее шаблон стратегии .

. В обоих случаях вы можете выбрать конкретную реализацию доступа к данным через config.

1 голос
/ 30 июля 2010

Вы можете скрыть базовую реализацию 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. Базовый метод скрыт, а фактическая реализация защищена абстрактно (CreateDbCommand)

0 голосов
/ 30 июля 2010

Я не знаю, как во время компиляции убедиться, что MakeBackupCall() использует правильный подкласс Database.Однако во время выполнения вы можете проверить, что переданный параметр is имеет правильный подтип.

0 голосов
/ 30 июля 2010

Просто проверьте тип во время выполнения и выведите ArgumentException, если переданная база данных имеет неправильный тип.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...