4

我正在实现模板方法模式。客户可以在此模板方法上更改一些设置;但我想阻止模板方法的实现者更改这些设置,因为在这种情况下,他们可能会破坏模板方法(基)类的不变量。如果可能的话,我希望这些东西无法编译。(否则是时候重构策略了:))

例子:

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();
    }
}

一种方法是重构策略,产生以下结果:

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();
    }
}

我很好奇是否有一种语言功能可以指示这种约束,而无需将重构应用于策略。

4

4 回答 4

4

您应该使TemplateMethod' 的设置不可变:

abstract class TemplateMethod
{
    protected TemplateMethod(bool recursive)
    {
        Recursive = recursive;
    }

    public bool Recursive { get; private set; }
    protected abstract bool ItemIsWhitelisted(SomeType item);
    public IEnumerable<SomeType> GetItems() { /* ... */ }
}

class Implementer : TemplateMethod
{
    protected override bool ItemIsWhitelisted(SomeType item)
    {
        Recursive = false; // Oops; Recursive is read-only
        return item.CanFrobinate();
    }
}

升级版

选项 #2。如果难以通过 ctor 传递设置,则可以考虑注入不可变参数对象。像这样的东西(MEF 风格的样本):

public interface ISettings
{
    bool Recursive { get; }
}

abstract class TemplateMethod
{
    [Import]
    public ISettings Settings { get; private set; }
    protected abstract bool ItemIsWhitelisted(SomeType item);
    public IEnumerable<SomeType> GetItems() { /* ... */ }
}

当然,这意味着TemplateMethod也不能更改设置。

选项#3。显式接口实现(如果TemplateMethod应该能够更改设置):

public interface ISettingsWriter
{
    bool Recursive { set; }
}

abstract class TemplateMethod : ISettingsWriter
{
    public bool Recursive { get; private set; }

    bool ISettingsWriter.Recursive
    {
        set { Recursive = value; }
    }

    protected abstract bool ItemIsWhitelisted(SomeType item);
}

class Implementer : TemplateMethod
{
    protected override bool ItemIsWhitelisted(SomeType item)
    {
        Recursive = true; // Oops; Recursive is still read-only;

        return true;
    }
}

当然,这意味着每个想要改变TemplateMethod.Recursive的人都必须转换TemplateMethodISettingsWriter

于 2012-10-10T06:37:40.163 回答
1

我不认为你可以合理地表示这种“每个人都可以调用,但不是我的孩子”的限制(孩子是每个人中的一员,如果不阻止每个人就不能被阻止......)。

完全没有这样的属性是另一种方法(与@Dennis 建议的以某种方式制作术前 R/O 相比)

public IEnumerable<SomeType> GetItems(bool Recursive)...

甚至

public IEnumerable<SomeType> GetItems(IterationSettings settings = DefaultSettings)

请注意,这种方法(类似于迭代的完整策略模式)也解决了外部“受信任”(非子)调用者在迭代中间更改属性时的问题(即,如果“子”触发了某种回调/事件执行)。

于 2012-10-10T06:54:37.510 回答
1

如果您的“框架”没有与源代码一起发布,并且实施者正在使用 dll,您可以这样做:

public abstract class TemplateMethod
{
    internal void DoSomething() { ... }
}

public abstract class Consumer
{
    TemplateMethod _Template = ...;
    protected void DoSomething()
    {
        _Template.DoSomething();
    }
}
于 2012-10-10T07:02:17.120 回答
0

您可以尝试使用堆栈跟踪来了解调用方法是否会更改值。

    private bool _recursive;
    public bool Recursive {
        get
        {
            return _recursive;
        }
        set
        {
            StackTrace stackTrace = new StackTrace();
            if (stackTrace.GetFrame(1).GetMethod().Name == "ItemIsWhitelisted")
            {
                throw new Exception("Please don't");
            }
            _recursive = value;
        }
    }

或者您可以在堆栈跟踪中检查 DeclaringType 方法

stackTrace.GetFrame(1).GetMethod().DeclaringType
于 2012-10-10T06:59:58.973 回答