Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Вы можете определить реализацию, когда объявляете члена интерфейса. Наиболее распространенный сценарий заключается в безопасном добавлении членов в интерфейс, который уже выпущен и используется ненумеримыми клиентами.
В этом руководстве описано, как:
- Безопасное расширение интерфейсов путем добавления методов с помощью реализаций.
- Создайте параметризованные реализации, чтобы обеспечить большую гибкость.
- Разрешите разработчикам предоставить более конкретную реализацию в виде переопределения.
Предпосылки
Необходимо настроить компьютер для запуска .NET, включая компилятор C#. Компилятор C# доступен с Visual Studio 2022 или пакета SDK для .NET.
Обзор сценария
В этом руководстве рассматривается версия 1 библиотеки управления отношениями с клиентами. Вы можете получить начальное приложение в репозитории примеров на сайте GitHub. Компания, которая создала эту библиотеку, намеревалась, чтобы клиенты с существующими приложениями приняли её библиотеку. Они предоставили минимальные определения интерфейса для пользователей своей библиотеки для реализации. Ниже приведено определение интерфейса для клиента:
public interface ICustomer
{
IEnumerable<IOrder> PreviousOrders { get; }
DateTime DateJoined { get; }
DateTime? LastOrder { get; }
string Name { get; }
IDictionary<DateTime, string> Reminders { get; }
}
Они определили второй интерфейс, представляющий заказ:
public interface IOrder
{
DateTime Purchased { get; }
decimal Cost { get; }
}
Из этих интерфейсов команда может создать библиотеку для своих пользователей, чтобы создать лучший интерфейс для своих клиентов. Их целью было создание более глубоких отношений с существующими клиентами и улучшение отношений с новыми клиентами.
Теперь пришло время обновить библиотеку для следующего выпуска. Одна из запрошенных функций обеспечивает скидку на лояльность для клиентов с большим количеством заказов. Эта новая скидка на лояльность применяется всякий раз, когда клиент делает заказ. Конкретная скидка — это свойство каждого отдельного клиента. Каждая реализация ICustomer
может задавать разные правила для скидки на лояльность.
Самый естественный способ добавить эту функциональность — улучшить ICustomer
интерфейс с помощью метода, чтобы применить любую скидку на лояльность. Это предложение по проектированию вызвало озабоченность среди опытных разработчиков: "Интерфейсы неизменяемы после того, как они были выпущены! Не делай критическое изменение!" Для обновления интерфейсов следует использовать реализации интерфейса по умолчанию. Авторы библиотеки могут добавлять новых членов в интерфейс и предоставлять реализацию по умолчанию для этих членов.
Реализации интерфейса по умолчанию позволяют разработчикам обновлять интерфейс, при этом предоставляя возможность всем тем, кто его реализует, переопределить данную реализацию. Пользователи библиотеки могут принять реализацию по умолчанию в качестве неисключаемого изменения. Если их бизнес-правила отличаются, они могут их изменить.
Обновление с помощью методов интерфейса по умолчанию
Команда согласилась с наиболее вероятной реализацией по умолчанию: скидка на лояльность для клиентов.
Обновление должно предоставить функциональные возможности для задания двух свойств: количество заказов, необходимых для получения скидки, и процент скидки. Эти функции делают его идеальным сценарием для методов интерфейса по умолчанию. Вы можете добавить метод в ICustomer
интерфейс и предоставить наиболее вероятные реализации. Все существующие и любые новые реализации могут использовать реализацию по умолчанию или предоставить собственную.
Сначала добавьте новый метод в интерфейс, включая текст метода:
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
Автор библиотеки написал первый тест, чтобы проверить реализацию:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
Reminders =
{
{ new DateTime(2010, 08, 12), "childs's birthday" },
{ new DateTime(1012, 11, 15), "anniversary" }
}
};
SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);
c.AddOrder(o);
o = new SampleOrder(new DateTime(2103, 7, 4), 25m);
c.AddOrder(o);
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Обратите внимание на следующую часть теста:
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Приведение от SampleCustomer
к ICustomer
необходимо. Классу SampleCustomer
не нужно предоставлять реализацию для ComputeLoyaltyDiscount
; это предоставляется интерфейсом ICustomer
.
SampleCustomer
Однако класс не наследует элементы из его интерфейсов. Это правило не изменилось. Чтобы вызвать любой метод, объявленный и реализованный в интерфейсе, переменная должна быть типом интерфейса в ICustomer
этом примере.
Укажите параметризацию
Реализация по умолчанию слишком ограничена. Многие потребители этой системы могут выбирать разные пороговые значения для количества покупок, другой длины членства или другой процентной скидки. Вы можете обеспечить более широкий интерфейс обновления для других клиентов, предоставив возможность задать эти параметры. Давайте добавим статический метод, который задает эти три параметра, управляющие реализацией по умолчанию:
// Version 2:
public static void SetLoyaltyThresholds(
TimeSpan ago,
int minimumOrders = 10,
decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;
public decimal ComputeLoyaltyDiscount()
{
DateTime start = DateTime.Now - length;
if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))
{
return discountPercent;
}
return 0;
}
В этом фрагменте кода отображается множество новых возможностей языка. Теперь интерфейсы могут включать статические элементы, включая поля и методы. Также включены различные модификаторы доступа. Другие поля являются закрытыми, новый метод является открытым. Любой из модификаторов разрешен в членах интерфейса.
Приложения, использующие общую формулу для вычисления скидки на лояльность, но разные параметры, не должны предоставлять пользовательскую реализацию; они могут задать аргументы через статический метод. Например, следующий код задает "оценку клиентов", которая вознаграждает любого клиента с более чем одним месяцем членства:
ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Расширение реализации по умолчанию
Добавленный до сих пор код предоставил удобную реализацию для тех сценариев, когда пользователи хотят что-то вроде реализации по умолчанию или предоставить несвязанный набор правил. В качестве завершающей возможности давайте немного рефакторим код, чтобы дать пользователям возможность разрабатывать сценарии на основе стандартной реализации.
Рассмотрим стартап, который хочет привлечь новых клиентов. Они предлагают 50% скидку от первого заказа нового клиента. В противном случае существующие клиенты получают стандартную скидку. Автор библиотеки должен переместить реализацию по умолчанию в protected static
метод, чтобы любой класс, реализующий этот интерфейс, может повторно использовать код в их реализации. Реализация элемента интерфейса по умолчанию вызывает этот общий метод, а также:
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);
protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
DateTime start = DateTime.Now - length;
if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))
{
return discountPercent;
}
return 0;
}
В реализации класса, реализующего этот интерфейс, переопределение может вызвать статический вспомогательный метод и расширить эту логику, чтобы предоставить скидку "новый клиент":
public decimal ComputeLoyaltyDiscount()
{
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
}
Полный код можно просмотреть в репозитории примеров на сайте GitHub. Вы можете получить начальное приложение в репозитории примеров на сайте GitHub.
Эти новые функции означают, что интерфейсы можно безопасно обновлять при наличии разумной реализации по умолчанию для этих новых членов. Тщательно проектируйте интерфейсы для выражения отдельных функциональных идей, реализованных несколькими классами. Это упрощает обновление определений этих интерфейсов при обнаружении новых требований для той же функциональной идеи.