Как создать фабрику, которая может возвращать производные типы?

Я создал фабричный класс под названием AlarmFactory как таковой...

1    class AlarmFactory
2    {
3        public static Alarm GetAlarm(AlarmTypes alarmType)  //factory ensures that correct alarm is returned and right func pointer for trigger creator.
4        {
5            switch (alarmType)
6            {
7                case AlarmTypes.Heartbeat:
8                    HeartbeatAlarm alarm = HeartbeatAlarm.GetAlarm();
9                    alarm.CreateTriggerFunction = QuartzAlarmScheduler.CreateMinutelyTrigger;
10                    return alarm;
11
12                   break;
13                default:
14                
15                    break;
16            }
17        }
18    }

Тревога сердцебиения происходит от Alarm. Я получаю сообщение об ошибке компиляции «невозможно неявно преобразовать тип... Существует явное преобразование (вам не хватает приведения?)». Как мне настроить это, чтобы возвращать производный тип?

ИЗМЕНИТЬ

СПАСИБО ВСЕМ ЗА ВАШИ ОТВЕТЫ. Я исправил ошибку компиляции в течение десяти минут, поэтому я не публиковал всю ошибку. Но я оценил различные подходы, которые были упомянуты.

Для записи это было «Невозможно неявно преобразовать тип« goAlarmsCS.HeartbeatAlarm »в« goAlarmsCS.Alarm ». Существует явное преобразование (вам не хватает приведения?)». (Я думаю.) Ошибка возникала в строке 8.

Сет


person Seth Spearman    schedule 02.06.2010    source источник
comment
Пожалуйста, опубликуйте всю ошибку компилятора + сообщите нам, в какой строке вы ее получили.   -  person Lasse V. Karlsen    schedule 02.06.2010
comment
Если HeartbeatAlarm действительно является производным от Alarm, вы сможете вернуть его без ошибок. Возможно, нам понадобится увидеть больше вашего кода и больше подробностей об ошибке компилятора, включая строку, чтобы помочь.   -  person GBegen    schedule 02.06.2010
comment
Что возвращает HeartbeatAlarm.GetAlarm(): тип Alarm или тип HeartbeatAlarm? Я предполагаю, что именно здесь срабатывает ошибка времени компиляции (конечно, понижение от Alarm -> HeartbeatAlarm)   -  person code4life    schedule 02.06.2010
comment
HeartbeatAlarm.GetAlarm возвращает HeartbeatAlarm.   -  person Seth Spearman    schedule 02.06.2010
comment
Вы пропустили фактическую часть сообщения об ошибке, необходимую для диагностики проблемы. Опубликуйте небольшую, завершенную программу, демонстрирующую ошибку. Это служит двум целям. Во-первых, это позволяет читателям взглянуть на реальный код, создающий проблему, вместо того, чтобы заставлять нас гадать на основе неполного кода и сообщения об ошибке, из которого вы умело удалили самую важную информацию. Во-вторых, сделав это, вы, вероятно, сами найдете свою ошибку. Ничто не находит ошибку быстрее, чем попытка сделать маленькую копию.   -  person Eric Lippert    schedule 02.06.2010


Ответы (4)


Ниже приведено решение, включающее специальную функцию GetHeartbeatAlarm для получения объекта HeartbeatAlarm, а также общую функцию GetAlarm для возврата сигнала тревоги, тип которого определяется общим параметром. Внизу есть пример кода, показывающий, как это будет называться:

enum AlarmTypes
{
    Heartbeat,
    AnotherAlarm,
    // ...
}

delegate void CreateTriggerDelegate();

class Alarm
{
    // ...

    public CreateTriggerDelegate CreateTriggerFunction { get; set; }
}

class HeartbeatAlarm : Alarm
{
    // ...

    public static HeartbeatAlarm GetAlarm()
    {
        return new HeartbeatAlarm();
    }
}

class QuartzAlarmScheduler
{
    public static CreateTriggerDelegate CreateMinutelyTrigger { get; set; }
}

class AlarmFactory
{
    public static Alarm GetAlarm(AlarmTypes alarmType)  //factory ensures that correct alarm is returned and right func pointer for trigger creator.
    {
        switch (alarmType)
        {
            case AlarmTypes.Heartbeat:
                return GetHeartbeatAlarm();
            default:
                throw new ArgumentException("Unrecognized AlarmType: " + alarmType.ToString(), "alarmType");
        }
    }

    static Alarm _GetAlarm<T>()
        where T : Alarm
    {
        Type type = typeof(T);
        if (type.Equals(typeof(HeartbeatAlarm)))
            return GetHeartbeatAlarm();
        else
            throw new ArgumentException("Unrecognized generic Alarm argument: " + type.FullName, "T");
    }

    public static T GetAlarm<T>()
        where T : Alarm
    {
        return (T)_GetAlarm<T>();
    }

    public static HeartbeatAlarm GetHeartbeatAlarm()
    {
        HeartbeatAlarm alarm = HeartbeatAlarm.GetAlarm();
        alarm.CreateTriggerFunction = QuartzAlarmScheduler.CreateMinutelyTrigger;
        return alarm;
    }
}

class Example
{
    static void GetAlarmExamples()
    {
        HeartbeatAlarm alarm;

        alarm = AlarmFactory.GetHeartbeatAlarm();

        alarm = AlarmFactory.GetAlarm<HeartbeatAlarm>();

        alarm = (HeartbeatAlarm)AlarmFactory.GetAlarm(AlarmTypes.Heartbeat);
    }
}
person Dr. Wily's Apprentice    schedule 02.06.2010
comment
Спасибо за ваш невероятно обстоятельный ответ. Сет - person Seth Spearman; 03.06.2010

Лучше всего было бы сделать Alarm интерфейсом или, если это невозможно, создать интерфейс IAlarm и наследовать от этого интерфейса как Alarm, так и HeartbeatAlarm.

AlarmFactory.GetAlarm должен вернуть экземпляр IAlarm. Аналогично, HeartbeatAlarm.GetAlarm() должен возвращать экземпляр IAlarm.

Это должно устранить любые ошибки компилятора, а плюсом является то, что все отношения строго договорные, что должно сделать код гораздо более удобным для будущего.

person code4life    schedule 02.06.2010
comment
У меня сложилось впечатление, что HeartbearAlarm и Alarm были типами Quartz, хотя я никогда не использовал Quartz. - person Powerlord; 03.06.2010
comment
Они не кварцевые... хотя я использую кварц в этом проекте. - person Seth Spearman; 03.06.2010

Вы пробовали то, что предлагает компилятор?

return (Alarm) alarm;

Редактировать: это совершенно неправильно, но я оставляю этот ответ здесь, чтобы сохранить обсуждение в комментариях.

person Powerlord    schedule 02.06.2010
comment
Эта строка отлично компилируется без преобразования. - person code4life; 02.06.2010
comment
Извините, я только изучаю С#. Что, если я ХОЧУ вернуть производный тип? Не будет ли приведение к (Alarm) возвращать тип Alarm, а не HeartbeatAlarm? Сет - person Seth Spearman; 02.06.2010
comment
@Seth, ваш тип возвращаемого значения — «Тревога», возвращаемый объект будет «Тревога» с приведением или без него. Если вы хотите явно использовать производный тип, вам придется привести его к производному типу после получения объекта. Что-то вроде тревоги DerivedAlarm = (DerivedAlarm)AlarmFactory.GetAlarm(AlarmTypes.DerivedAlarm); - person Anthony Pegram; 02.06.2010
comment
@Seth: вы неправильно понимаете, как работают преобразования ссылок. Вы путаете то, что компилятор знает во время компиляции (этот объект является своего рода Animal) с что объект представляет собой во время выполнения (Giraffe). Преобразование жирафа в животное просто говорит компилятору забыть, что вы знали, что это жираф. Во время выполнения это все еще жираф. - person Eric Lippert; 03.06.2010
comment
Эрик, спасибо за оба ваших комментария. И ваше объяснение вышеизложенного. Я думал, что это может быть что-то вроде этого. Так что, хотя я использую Animal, это все еще жираф. Вот почему Giraffe = (Giraffe)AnimalFactory.GetAnimal работает. Спасибо. Сет. P.S. Мне нравится ваш блог. - person Seth Spearman; 03.06.2010
comment
Упс, перечитав вопрос, я должен был понять, что проблема была в назначении переменной после ее возврата. Я бы удалил этот ответ, но вместе с ним удалилось бы и обсуждение. - person Powerlord; 03.06.2010

Как некоторые уже предположили, избыточный интерфейс — это простой способ добиться этого. Похоже, чистый пример может помочь будущим посетителям.

public interface IAlarm
{
    public int bpm { get; set; }    // declared base props
}

public class Alarm : IAlarm
{
    public int bpm { get; set; }    // implemented base props
}

public class HeartbeatAlarm : Alarm
{
    public int min { get; set; }    // extended type specific props
}

public class FactoryMethods
{
    public static T AlarmFactory<T>(int bpm) where T : IAlarm, new()
    {
        // interfaces have no constructor but you can do this
        T alarm = new T() {
            bpm = bpm
        };

        return alarm;
    }
}

// C# will automagically infer the type now..
HeartbeatAlarm heartbeatAlarm = FactoryMethods.AlarmFactory<HeartbeatAlarm>(40);
person djabraham    schedule 03.04.2015