テンプレートメソッドパターンを実装しています。クライアントがこのテンプレート メソッドで変更できる設定がいくつかあります。しかし、テンプレート メソッドの実装者がこれらの設定を変更するのを防ぎたいと思います。そのような場合、テンプレート メソッド (基本) クラスの不変条件を破ることができるからです。できればコンパイルに失敗してほしいです。(そうでなければ、戦略にリファクタリングする時が来ました:))
例:
abstract class TemplateMethod
{
// Clients of implementers of this template method change these settings before
// calling GetItems()
public SomeType RootItem { get; set; }
public bool Recursive { get; set; }
protected abstract bool ItemIsWhitelisted(SomeType item);
public IEnumerable<SomeType> GetItems()
{
var stack = new Stack<T>();
stack.Push(RootItem);
while (!stack.empty())
{
var current = stack.Pop();
if (Recursive)
{
foreach (var item in current.Children)
stack.Push(item);
}
if (!ItemIsWhitelisted(current))
yield return current;
}
}
}
class Implementer : TemplateMethod
{
protected override bool ItemIsWhitelisted(SomeType item)
{
Recursive = false; // Oops; TemplateMethod.GetItems didn't expect this
// to change inside ItemIsWhitelisted
return item.CanFrobinate();
}
}
1 つの方法は、戦略をリファクタリングして、次のようにすることです。
interface IImplementer
{
bool ItemIswhitelisted(SomeType item);
}
sealed class NoLongerATemplateMethod
{
// Clients of implementers of this template method change these settings before
// calling GetItems()
public SomeType RootItem { get; set; }
public bool Recursive { get; set; }
public IImplementer impl { get; set; } // would be private set in real code
public IEnumerable<SomeType> GetItems()
{
var stack = new Stack<T>();
stack.Push(RootItem);
while (!stack.empty())
{
var current = stack.Pop();
if (Recursive)
{
foreach (var item in current.Children)
stack.Push(item);
}
if (!impl.ItemIsWhitelisted(current))
yield return current;
}
}
}
class Implementer : IImplementer
{
public bool ItemIsWhitelisted(SomeType item)
{
Recursive = false; // No longer compiles
return item.CanFrobinate();
}
}
戦略にリファクタリングを適用せずにこの制約を示す言語機能があるかどうか興味があります。