是否有可能有一个派生类无法调用的方法,但类的消费者可以调用?

时间:2012-10-10 06:25:11

标签: c# template-method-pattern

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

示例:

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 个答案:

答案 0 :(得分: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();
    }
}

<强> UPD

选项#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的人都必须将TemplateMethod投放到ISettingsWriter

答案 1 :(得分:1)

我认为你不能合理地代表“每个人都可以调用,但不是我的孩子”的限制(孩子是每个人中的一个,并且不能阻止所有人也不能阻止......)。

完全不具备这些属性是另一种方法(与以@Dennis建议的某种方式制作预制R / O相比)

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

甚至

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

请注意,这种方法(类似于迭代的完整策略模式)也解决了外部“可信”(非子)调用者在迭代过程中更改属性时的问题(即,是否存在某种类型的回调/事件“孩子”实施)。

答案 2 :(得分:1)

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

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

public abstract class Consumer
{
    TemplateMethod _Template = ...;
    protected void DoSomething()
    {
        _Template.DoSomething();
    }
}

答案 3 :(得分: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