Делегаты (выражения Лямбда) Vs Интерфейсы и абстрактные классы - программирование
Подтвердить что ты не робот

Делегаты (выражения Лямбда) Vs Интерфейсы и абстрактные классы

Я искал аккуратный ответ на этот вопрос дизайна без успеха. Я не мог найти помощь ни в ". Рекомендации по разработке .NET Framework" , ни в "Руководстве по программированию на С#" . В основном я должен представить шаблон как API, чтобы пользователи могли определять и интегрировать свои алгоритмы в мои рамки следующим образом:

1)

// This what I provide
public abstract class AbstractDoSomething{
   public abstract SomeThing DoSomething();
}

Пользователям необходимо реализовать этот абстрактный класс, они должны реализовать метод DoSomething (который я могу вызвать изнутри и использовать его)

2)

Я узнал, что это также может быть достигнуто с помощью делегатов:

public sealed class DoSomething{
   public String Id;
   Func<SomeThing> DoSomething;
}

В этом случае пользователь может использовать класс DoSomething следующим образом:

DoSomething do = new DoSomething()
{
  Id="ThisIsMyID",
  DoSomething = (() => new Something())
}

Вопрос

Какой из этих двух опций лучше всего подходит для простой, удобной и наиболее понятной для публикации как API?

ИЗМЕНИТЬ

В случае 1: регистрация выполняется таким образом (если MyDoSomething extends AbstractDoSomething:

MyFramework.AddDoSomething("DoSomethingIdentifier", new MyDoSomething());

В случае 2: регистрация выполняется следующим образом:

MyFramework.AddDoSomething(new DoSomething());
4b9b3361

Ответ 1

Какой из этих двух вариантов лучше всего подходит для простого, удобного и наиболее понятного для раскрытия как API?

Первый из них более "традиционен" с точки зрения ООП и может быть понятнее многим разработчикам. Он также может иметь преимущества в том, что позволяет пользователю управлять временем жизни объектов (т.е. Вы можете позволить классу реализовать IDisposable и удалять экземпляры при выключении и т.д.), А также легко расширяться в будущих версиях в способ, который не нарушает обратной совместимости, поскольку добавление виртуальных членов в базовый класс не нарушит API. Наконец, его можно будет проще использовать, если вы хотите использовать что-то вроде MEF, чтобы составить это автоматически, что упростит/удалит процесс "регистрации" с точки зрения пользователя (поскольку они могут просто создать подкласс и отбросить его в папку, и он автоматически обнаруживается/используется).

Второй - более функциональный подход, и он проще во многих отношениях. Это позволяет пользователю реализовать ваш API с гораздо меньшими изменениями в их существующем коде, поскольку им просто нужно обернуть необходимые вызовы в лямбда с закрытием вместо создания нового типа.

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

MyFramework.AddOperation("ThisIsMyID", () => DoFoo());

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

Ответ 2

Я бы пошел с абстрактным классом/интерфейсом, если:

  • Требуется DoSomething
  • DoSomething обычно становится действительно большим (так что реализация DoSomething может быть разделена на несколько частных/защищенных методов).

Я бы пошел с делегатами, если:

  • DoSomething можно рассматривать как событие (OnDoingSomething)
  • DoSomething является необязательным (поэтому вы по умолчанию его не делегируете делегатом)

Ответ 3

Хотя лично, если бы в моей руке, я бы всегда шел по модели делегата. Мне просто нравится простота и элегантность функций более высокого порядка. Но при реализации модели будьте осторожны с утечками памяти. Подписка событий является одной из наиболее распространенных причин утечки памяти в .Net. Это означает, что если у вас есть объект, у которого есть некоторые события, исходный объект никогда не будет удален до тех пор, пока все события не будут отписаны, поскольку событие создает сильную ссылку.

Ответ 4

Как типично для большинства этих типов вопросов, я бы сказал, "это зависит".:)

Но я думаю, что причина использования абстрактного класса в сравнении с лямбдой действительно сводится к поведению. Обычно я думаю о том, что лямбда используется как функция обратного вызова - где вы хотите, чтобы что-то обычное произошло, когда что-то еще происходит. Я делаю это в своем клиентском коде:  - позвонить по телефону  - вернуть некоторые данные  - теперь вызывается мой обратный вызов для обработки этих данных соответственно

Вы можете сделать то же самое с лямбдами - они специфичны и предназначены для особых ситуаций.

Использование абстрактного класса (или интерфейса) действительно сводится к тому, что поведение вашего класса определяется окружением вокруг него. Что происходит, с каким клиентом я сталкиваюсь и т.д.? Эти более крупные вопросы могут предполагать, что вы должны определить набор поведений, а затем позволить вашим разработчикам (или потребителям вашего API) создавать свои собственные наборы поведения на основе их требований. Конечно, вы можете сделать то же самое с lambdas, но я думаю, что было бы сложнее разрабатывать, а также более сложно четко общаться с вашими пользователями.

Итак, я предполагаю, что мое грубое эмпирическое правило: - использовать lambdas для специфического обратного вызова или индивидуального поведения побочных эффектов; - использовать абстрактные классы или интерфейсы для обеспечения механизма настройки поведения объекта (или, по крайней мере, большинства первичного поведения объекта).

Извините, я не могу дать вам четкое определение, но я надеюсь, что это поможет. Удачи!

Ответ 5

Несколько вещей, которые следует учитывать:

Сколько разных функций/делегатов необходимо переубедить? Если функции могут функционировать, inheretance будет группировать "наборы" переопределений в более понятном виде. Если у вас есть одна функция регистрации, но многие части могут быть делегированы разработчику, это классический случай шаблона "Шаблон", который имеет наибольший смысл для наследования.

Сколько различных реализаций одной и той же функции потребуется? Если только один, то inheretance хорошо, но если у вас много реализаций, делегат может сэкономить накладные расходы.

Если существует несколько реализаций, будет ли программа переключаться между ними? Или он будет использовать только одну реализацию. Если требуется переключение, делегаты могут быть проще, но я бы предостерег об этом, особенно в зависимости от ответа на # 1. См. Шаблон стратегии.

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

Другими вариантами могут быть события и методы расширения.