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

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

戦略にリファクタリングを適用せずにこの制約を示す言語機能があるかどうか興味があります。

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

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ます。

于 2012-10-10T06:37:40.163 に答える
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 に答える