具有许多属性的类的装饰器模式

13

我有这个简单的类:

public class DataBag
{
    public string UserControl { get; set; }
    public string LoadMethod { get; set; }
    public dynamic Params { get; set; }
    public int Height { get; set; }

    public DataBag(string Control, 
        object vars, string lm)
    {
        UserControl = Control;
        LoadMethod = lm;
        Params = vars;
        Height = 0;
    }
}

我想创建一个装饰器,并添加一堆自己的属性。问题是,如何最简洁、优雅地访问这些装饰后的属性?

目前我有两个选项:为每个装饰过的属性提供一个get-set对(这似乎很繁琐,难以口头表达,基本上这就是我想避免的)或者从DynamicObject继承DataBag,然后以某种方式使用TryGetMember方法获取装饰后的属性(这是动态的,似乎不是在C#中正确的做法)。

有什么建议吗?


通常情况下,装饰器都继承自一个公共接口。在这种情况下,接口会是什么?如果它是动态的,那么装饰器可能不是合适的选择。 - Davin Tryon
你有没有考虑让装饰器从DataBag继承?唯一的问题是,这样你就无法将装饰器从基本类型向下转换。 - McGarnagle
@dbaseman 我一开始使用了继承,但很快就遇到了需要许多具有略微不同属性和方法的类的情况。 - src091
@dtryon 到目前为止还没有任何接口。我知道经典的装饰器需要实现一个接口,但我认为这不是关键点。 - src091
@Anton:如果您提供如何使用装饰器和DataBag的示例,可能会有所帮助...子类化是否足够?您是否无法子类化?装饰器是否需要应用于多个类型?您是否需要考虑同时使用多个装饰器? - JaredReisinger
3个回答

9

在实现装饰器时,我通常会按照以下步骤进行。首先,提取被装饰对象的接口,并使被装饰对象实现该接口:

public interface IDataBag
{
    string UserControl { get; set; }
    string LoadMethod { get; set; }
    dynamic Params { get; set; }
    int Height { get; set; }
}

接下来创建一个装饰器,将所有调用委托给被装饰对象(所有装饰器都将继承自此装饰器):

public class DataBagDecorator : IDataBag
{
    private IDataBag _dataBag;

    public DataBagDecorator(IDataBag dataBag)
    {
        _dataBag = dataBag;
    }

    public virtual string UserControl
    {
        get { return _dataBag.UserControl; }
        set { _dataBag.UserControl = value; }
    }

    // other members
}

最后 - 创建装饰器:

public class FooDataBag : DataBagDecorator
{
    public FooDataBag(IDataBag dataBag) 
        : base(dataBag) { }

    public override string UserControl
    {
        // added behavior
        get { return "Foo" + base.UserControl; }
        set { base.UserControl = value; }
    }

    // you don't need to override other members
}

使用方法:

IDataBag dataBag = new FooDataBag(new DataBag());

1
更新:@JeremyDWill 正确指出,您无法从其参数之一推导出通用类型... < p >如果您将装饰器视为“添加新属性的子类”,则可以执行以下操作:

public class MyDecorator<T> : T
{
    public int MyDecoratorProperty1 { get; set; }
    public int MyDecoratorProperty2 { get; set; }
}

然后,您可以创建MyDecorator<DataBag>MyDecorator<OtherClass>等实例。现有属性是可访问的,因为MyDecorator<>是特定于通用参数类型的,并从该类派生。

您可以创建一个包含装饰对象的包装器:

public class MyDecorator<T>
{
    public MyDecorator(T decoratedObject)
    {
        this.DecoratedObject = decoratedObject;
    }

    public T DecoratedObject { get; private set; }

    public int MyDecoratorProperty1 { get; set; }
    public int MyDecoratorProperty2 { get; set; }
}

优点是很容易获取装饰属性:myObj.MyDecoratorProperty1。缺点是现在您必须通过DecoratedObject成员才能访问基本对象:

DataBag bag = new DataBag("", null, null);
MyDecorator<DataBag> deco = new MyDecorator<DataBag>(bag);
deco.DecoratedObject.Height = 2;

如果您无法从装饰器进行子类化(比如需要同时支持多个装饰器),则必须使用“附加属性”等类似方法……您的装饰器类必须维护一个包含原始对象和装饰属性的字典。借助一些扩展方法,只要您事先知道被装饰对象的类型(或者愿意装饰 任何 对象),就可以使这些属性看起来像是已经定义为所装饰类的本地成员:

public static class AttachedDecorator
{
    private class Properties
    {
        public int MyDecoratorProperty1 { get; set; }
        public int MyDecoratorProperty2 { get; set; }
    }

    private static Dictionary<object, Properties> map = new Dictionary<object, Properties>();

    public static int GetMyDecoratorProperty1(object obj)
    {
        Properties props;
        if (map.TryGetValue(obj, out props))
        {
            return props.MyDecoratorProperty1;
        }

        return -1; // or some value that makes sense if the object has no decorated property set
    }

    public static int GetMyDecoratorProperty2(object obj) { /* ... */ }

    public static void SetMyDecoratorProperty1(object obj, int value)
    {
        Properties props;
        if (!map.TryGetValue(obj, out props))
        {
            props = new Properties();
            map.Add(obj, props);
        }

        props.MyDecoratorProperty1 = value;

    }

    public static void SetMyDecoratorProperty2(object obj, int value) { /* ... */ }
}

public static class DecoratorExtensions
{
    private static int GetMyDecoratorProperty1(this object obj)
    {
        return AttachedDecorator.GetMyDecoratorProperty1(obj);
    }

    private static void SetMyDecoratorProperty1(this object obj, int value)
    {
        return AttachedDecorator.GetMyDecoratorProperty1(obj, value);
    }
    // ...
}

你的代码可能看起来像这样:

DataBag myData = new DataBag();
myData.SetMyDecoratorProperty1(7);
Console.WriteLine("prop1: {0}", myData.GetMyDecoratorProperty1());

1
它与简单继承MyDataBagDecorator:DataBag有何不同?通常,一个装饰器对象会创建多个修饰对象,而不是一个修饰器对象用于不同类型的多个对象。 - Sergey Berezovskiy
@lazyberezovsky:装饰器通常不特定于单个类。如果是这样,它们只是一个子类,而不是装饰器。至于有多个装饰器,这完全取决于上下文,这就是为什么那一段开头是“如果您无法从被装饰者进行子类化...”。碰巧的是,我正在编写我的“附加装饰器”解决方案,与您添加评论的时间相同! - JaredReisinger
在这种情况下,装饰器不会了解T的成员,因此无法访问它们。我认为这是最好的解决方案,但应该强制T实现一个接口。 - Nick Bray
1
你的第一个代码块,按照当前的写法,无法编译;你不能从一个泛型类型参数继承。请参考https://dev59.com/42025IYBdhLWcg3wpHh-中的例子。 - JeremyDWill
@JeremyDWill:哎呀...我敢肯定我过去做过这件事(不仅仅是使用C++模板)。我会看看能否解决它。 - JaredReisinger

1

这是最简单的装饰方式:

public class PrettyBag : DataBag
{
    public int Decoration1 { get; set; }
    public int Decoration2 { get; set; }
}

如果您想创建一个外观并隐藏一些DataBag属性而不仅仅是添加属性,那么您可以将DataBag的成员设置为protected。

使用接口,您可以执行以下操作:

    public interface IDataBag
    {
       ...
    }
    public class DataBag : IDataBag
    {
       ...
    }
    public interface IPrettyBag : IDataBag
    {
        int Decoration1 { get; set; }
        int Decoration2 { get; set; }
    }
    public class BigBag : IPrettyBag
    {
        public int Decoration1 { get; set; }
        public int Decoration2 { get; set; }
    }
    public interface SmallBag : IPrettyBag
    {
        public int Decoration1 { get; set; }
        public int Decoration2 { get; set; }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接