インターフェイスのメンバーを宣言するときに実装を定義できます。 最も一般的なシナリオは、数の少ないクライアントによって既にリリースされ、使用されているインターフェイスにメンバーを安全に追加することです。
このチュートリアルで学習する内容は次のとおりです。
- 実装を含むメソッドを追加して、インターフェイスを安全に拡張します。
- 柔軟性を高めるために、パラメーター化された実装を作成します。
- 実装者がオーバーライドの形式で、より具体的な実装を提供できるようにします。
[前提条件]
C# コンパイラを含め、.NET が実行されるようにコンピューターを設定する必要があります。 C# コンパイラは、 Visual Studio 2022 または .NET SDK で使用できます。
シナリオの概要
このチュートリアルは、顧客関係ライブラリのバージョン 1 から始まります。 GitHub のサンプル リポジトリでスターター アプリケーションを取得できます。 このライブラリを構築した会社は、既存のアプリケーションを持つ顧客がライブラリを採用することを意図しています。 彼らは、ライブラリのユーザーが実装するための最小限のインターフェイス定義を提供しました。 顧客のインターフェイス定義を次に示します。
public interface ICustomer
{
IEnumerable<IOrder> PreviousOrders { get; }
DateTime DateJoined { get; }
DateTime? LastOrder { get; }
string Name { get; }
IDictionary<DateTime, string> Reminders { get; }
}
順序を表す 2 つ目のインターフェイスを定義しました。
public interface IOrder
{
DateTime Purchased { get; }
decimal Cost { get; }
}
これらのインターフェイスから、チームはユーザーが顧客に対してより良いエクスペリエンスを作成するためのライブラリを構築できます。 彼らの目標は、既存の顧客とのより深い関係を築き、新しい顧客との関係を改善することでした。
次は、次のリリース用にライブラリをアップグレードします。 要求された機能の 1 つを使用すると、多数の注文を持つ顧客のロイヤルティ割引が有効になります。 この新しいロイヤルティ割引は、顧客が注文を行うたびに適用されます。 特定の割引は、個々の顧客のプロパティです。
ICustomer
の実装ごとに、ロイヤルティ割引に対して異なるルールを設定できます。
この機能を追加する最も自然な方法は、ロイヤルティ割引を適用する方法で ICustomer
インターフェイスを強化することです。 この設計提案は、経験豊富な開発者の間で懸念を引き起こしました。「インターフェースは一旦公開されると不変です!」と。 破壊的変更を加えないでください。インターフェイスのアップグレードには、既定のインターフェイス実装を使用する必要があります。 ライブラリの作成者は、インターフェイスに新しいメンバーを追加し、それらのメンバーの既定の実装を提供できます。
既定のインターフェイス実装を使用すると、開発者はインターフェイスをアップグレードしながら、すべての実装者がその実装をオーバーライドできるようになります。 ライブラリのユーザーは、既定の実装を非破壊的な変更として受け入れることができます。 ビジネスルールが異なる場合、彼らはそれを上書きできます。
既定のインターフェイス メソッドを使用したアップグレード
チームは、最も可能性の高い既定の実装 (顧客のロイヤルティ割引) に同意しました。
アップグレードでは、割引の対象となる必要がある注文数と割引の割合という 2 つのプロパティを設定する機能を提供する必要があります。 これらの機能により、既定のインターフェイス メソッドに最適なシナリオになります。
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
、変数がインターフェイスの型である必要があります。
パラメーター化を指定する
既定の実装は制限が厳しすぎます。 このシステムの多くのコンシューマーは、購入数、メンバーシップの長さ、または異なる割合の割引に対して異なるしきい値を選択できます。 これらのパラメーターを設定する方法を提供することで、より多くの顧客に対してより優れたアップグレード エクスペリエンスを提供できます。 既定の実装を制御するこれら 3 つのパラメーターを設定する静的メソッドを追加してみましょう。
// 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;
}
その小さなコード フラグメントには、多くの新しい言語機能が示されています。 インターフェイスに、フィールドやメソッドを含む静的メンバーを含めることができるようになりました。 異なるアクセス修飾子も有効になります。 他のフィールドはプライベートで、新しいメソッドはパブリックです。 任意の修飾子は、インターフェイス メンバーで使用できます。
ロイヤルティ割引の計算に一般的な数式を使用するが、パラメーターが異なるアプリケーションでは、カスタム実装を提供する必要はありません。静的メソッドを使用して引数を設定できます。 たとえば、次のコードは、1 か月以上のメンバーシップを持つ顧客に報酬を与える "顧客感謝" を設定します。
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 のサンプル リポジトリでスターター アプリケーションを取得できます。
これらの新機能は、これらの新しいメンバーに適切な既定の実装がある場合に、インターフェイスを安全に更新できることを意味します。 複数のクラスによって実装される単一の機能的なアイデアを表現するために、インターフェイスを慎重に設計します。 これにより、同じ機能のアイデアに対して新しい要件が検出されたときに、これらのインターフェイス定義を簡単にアップグレードできます。
.NET