I'm implementing the template method pattern. There are a couple of settings that clients can change on this template method; but I would like to prevent implementers of the template method from changing these settings, because in such cases they might be able to break invariants of the template method (base) class. I'd like such things to fail to compile if possible. (Otherwise tis time to refactor to strategy :) )
Example:
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();
}
}
One method is to refactor to strategy, producing the following:
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();
}
}
I'm curious if there's a language feature which indicates this constraint without applying refactor to strategy.