С# - интерфейсы/абстрактный класс - убедитесь, что событие вызывается в методе

У меня есть интерфейс, определенный как IStore, с двумя методами:

public interface IStore<TEntity>
{
    TEntity Get(object identifier);
    void Put(TEntity entity);
}

Я хочу, чтобы событие было вызвано успехом Put (для справки, Put может хранить строку в БД или файл в файловой системе и т. д.)

Итак, класс, реализующий Istore для типа Product, будет выглядеть примерно так:

class MyStore : IStore<Product>
{
    public Product Get(object identifier)
    {
        //whatever
    }

    public void Put(Product entity)
    {
        //Store the product in db
        //RAISE EVENT ON SUCCESS
    }
}

Что мне нужно, так это способ гарантировать, что каждая реализация IStore вызывает событие - должен ли я вместо этого иметь абстрактный класс или интерфейс?


person Alex    schedule 07.07.2010    source источник


Ответы (7)


мое предложение:

public abstract class Store<TEntity>
{
    public abstract TEntity Get(object identifier);
    public void Put(TEntity entity)
    {
        //Do actions before call
        InternalPut(entity);
        //Raise event or other postprocessing
    }

    protected abstract void InternalPut(TEntity entity);
}

затем переопределите InternalPut в своем классе

person Andrey    schedule 07.07.2010
comment
Это должен был быть мой ответ. На каком-то уровне вы всегда зависите от людей, понимающих контракт, когда разрешаете переопределять методы. Но предоставление такой структуры позволяет вам иметь некоторый контроль над порядком, в котором разрешено происходить. - person unholysampler; 07.07.2010

На самом деле нет способа гарантировать, что каждая реализация IStore вызовет событие. У вас может быть абстрактный класс с методом put, но это все равно не означает, что вы можете иметь метод put в подклассе абстрактного класса, который полностью игнорирует метод абстрактного класса.

В конце концов, лучший способ поощрить создание события — это написать метод, который должен использовать разработчик, с помощью абстрактного класса. Таким образом, они должны изо всех сил стараться не использовать его.

person Dan McClain    schedule 07.07.2010

Вам нужно иметь абстрактный класс, реализующий метод Put из вашего интерфейса. Также вы можете добавить абстрактный метод, такой как PutImpl, примерно так:

public abstract class MyStoreBase : IStore<TEntity>
{
    public abstract TEntity Get(object identifier);

    public abstract void PutImpl(TEntity entity);

    public void Put(TEntity entity)
    {
        // Each inheritor will implement this method.
        PutImpl(entity);

        // But event is fired in base class.
        FireEvent();
    }
}
person Andrew Bezzub    schedule 07.07.2010

Да, вы должны использовать абстрактный класс вместо интерфейса.

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

При этом даже использование абстрактного класса не заставит других разработчиков использовать ваш метод, поскольку они могут перезаписать его.

Я думаю, что лучшим методом было бы использовать метод шаблона:

public abstract class AbstractStore<TEntity>
{
    public TEntity Get(object identifier);
    public sealed void Put(TEntity entity)
    {
        if (DoPut(entity))
        {
            // raise event
        }
    }

    protected abstract bool DoPut(TEntity entity);
}

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

person Thibault Falise    schedule 07.07.2010
comment
Это также не мешает наследовать классы от переопределения поведения. Нет реального способа гарантировать такое поведение. - person Dan Puzey; 07.07.2010

Если вам действительно нужно использовать интерфейс, новый PostSharp 2 может выполнять наследование аспектов. К сожалению, чтобы получить эту функцию, вам необходимо приобрести как минимум личную лицензию за 200 долларов.

С таким аспектом вы бы поместили его в метод, объявленный в вашем интерфейсе, и все реализации вашего интерфейса наследовали бы его.

person Lasse V. Karlsen    schedule 07.07.2010

 abstract class Store<TEntity>
{
     public abstract TEntity Get(object identifier);
     protected abstract void Put(TEntity entity);
     public void PutBase(TEntity entity)
     {
         Put(entity);
         //Raise event here
     }

}

некоторое примечание: я поставил как защищенный, поэтому все производные классы должны его реализовать, но клиентский код не может его вызывать.

person Arseny    schedule 07.07.2010

Написание абстрактного класса для обертывания PutInner-подобного метода - абсолютно лучший способ приблизиться к этому, как уже предлагали другие.

Я бы просто добавил, что если вы хотите, чтобы любая реализация IStore<TEntity> всегда вызывала событие при вызове Put, я бы рекомендовал также добавить это событие в сам интерфейс:

public interface IStore<TEntity>
{
    event EventHandler<EntityEventArgs<TEntity>> EntityAdded;

    TEntity Get(object identifier);
    void Put(TEntity entity);
}

public EntityEventArgs<TEntity> : EventArgs
{
    public TEntity Entity { get; set; }
}

Это не заставляет разработчиков что-либо делать; но это ясно устанавливает ожидание того, что EntityAdded будет повышен при успешном Put.

person Dan Tao    schedule 07.07.2010