装饰者模式和C#

时间:2014-09-04 10:48:09

标签: c# design-patterns decorator

我尝试在C#中运行以下示例程序我得到输出"你正在使用计算机"而不是"你得到的是一台电脑和一个磁盘,一台显示器和一个KeyBoard"。

为什么这种情况只发生在C#中而不是Java中。 我使用相同的代码获得相应的输出。

如果我调试我发现创建的对象层次结构是正确的但是调用computer.getComputer()总是去超级类而不是驱动类,这就是问题。

请帮我解决这个问题。

namespace DecoratorTest1
{

    public class Computer
    {
        public Computer()
        {
        }

        public  String getComputer()
        {
            return "computer";
        }
    }


    public abstract class ComponentDecorator : Computer
    {
        public abstract String getComputer();
    }

    public class Disk : ComponentDecorator
    {
        Computer computer;
        public Disk(Computer c)
        {
            computer = c;
        }


        public override String getComputer()
        {
            return computer.getComputer() + " and a disk";
        }

    }

    public class Monitor : ComponentDecorator
    {
        Computer computer;
        public Monitor(Computer c)
       {
            computer = c;
        }
        public override String getComputer()
        {
            return computer.getComputer() + " and a Monitor";
        }

    }

    public class KeyBoard : ComponentDecorator
    {
        Computer computer;
        public KeyBoard(Computer c)
       {
            computer = c;
        }

        public  override String getComputer()
        {
            return computer.getComputer() + " and a KeyBoard";
        }

        public string call()
        {
            return "";
        }

    }



    class Program
    {
        static void Main(string[] args)
        {
            Computer computer = new Computer();
            computer = new Disk(computer);
            computer = new Monitor(computer);
            computer = new KeyBoard(computer);


            Console.WriteLine(" You are getting a " + computer.getComputer());
            Console.ReadKey();

        }
    }
}

7 个答案:

答案 0 :(得分:10)

C#中的装饰器

使用decorator pattern时,想法是让几个类实现相同的接口。其中之一是接口的常规具体实现,Computer在您的情况下。其他人为Computer的行为添加了一些内容。我们可以摆脱ComponentDecorator。您可以创建一个实现IComputer界面的抽象装饰器类,但您不必这样做。

入门

我们首先创建界面并使具体Computer实现它:

public interface IComputer
{
    string getComputer();
}

public sealed class Computer : IComputer
{
    public string getComputer()
    {
        return "computer";
    }
}

Computer这里是sealed。它不是必须的,但是在这种情况下完成以表明装饰器存在于旁边你的具体类,而不是从它派生

没有装饰器的抽象基类

装饰者实施IComputer而不是ComponentDecorator

public class Disk : IComputer
{
    IComputer _computer;
    public Disk(IComputer computer)
    {
        _computer = computer;
    }

    public String getComputer()
    {
        return _computer.getComputer() + " and a disk";
    }
}

public class Monitor : IComputer
{
    IComputer _computer;
    public Monitor(IComputer computer)
    {
        _computer = computer;
    }

    public String getComputer()
    {
        return _computer.getComputer() + " and a Monitor";
    }
}

public class KeyBoard : IComputer
{
    IComputer _computer;
    public KeyBoard(IComputer computer)
    {
        _computer = computer;
    }

    public  String getComputer()
    {
        return _computer.getComputer() + " and a KeyBoard";
    }
}

使用装饰器的抽象基类

如果您确实选择使用抽象类来实现装饰器,那么在构造函数中使用IComputer作为依赖项。此外,您应该使用base.getComputer()代替computer.getComputer(),如下所示:

public abstract class ComputerDecorator : IComputer
{
    private IComputer _computer;
    public ComputerDecorator(IComputer computer)
    {
        _computer = computer;
    }

    public virtual string getComputer()
    {
        return _computer.getComputer();
    }
}

public class Disk : ComputerDecorator
{
    public Disk(IComputer computer) : base(computer)
    {
    }

    public override String getComputer()
    {
        return base.getComputer() + " and a disk";
    }
}

public class Monitor : ComputerDecorator
{
    public Monitor(IComputer computer) : base(computer)
    {
    }

    public override String getComputer()
    {
        return base.getComputer() + " and a Monitor";
    }
}

public class KeyBoard : ComputerDecorator
{
    public KeyBoard(IComputer computer) : base(computer)
    {
    }

    public override String getComputer()
    {
        return base.getComputer() + " and a KeyBoard";
    }
}

在这两种情况下,我们都可以用同样的方式将它们包装起来:

class Program
{
    public static void Main(string[] args)
    {
        IComputer computer = new KeyBoard(new Monitor(new Disk(new Computer())));

        Console.WriteLine(" You are getting a " + computer.getComputer());
    }
}

查看它的工作withwithout抽象装饰。

如果我们无法更改基类

,该怎么办?

用户InBetween建议可能无法更改基类。如果基类已经实现了接口,那不是问题。因此,我们假设它不像您的代码中那样。

在这种情况下要实现装饰器,我们首先需要为我们的基类创建一个adapter并在它旁边实现我们的装饰器。

因此,让我们假设基类是Computer,我们无法改变它:

public sealed class Computer
{
    public string getComputer()
    {
        return "computer";
    }
}

要创建适配器,我们像以前一样创建IComputer接口,并创建一个包裹Computer的类:

public sealed class ComputerAdapter : IComputer
{
    private Computer _computer;
    public ComputerAdapter(Computer computer)
    {
        _computer = computer;
    }

    public string getComputer()
    {
        return _computer.getComputer();
    }
}

装饰器与前一个示例保持不变,因为它们已经实现了IComputer。由于我们现在必须将Computer传递给我们的ComputerAdapter实例,因此对其进行了一些更改:

class Program
{
    public static void Main(string[] args)
    {
        Computer sealedComputer = new Computer();
        IComputer computer = new KeyBoard(new Monitor(new Disk(new ComputerAdapter(sealedComputer))));

        Console.WriteLine(" You are getting a " + computer.getComputer());
    }
}

但结果是一样的,可以看出here

为什么你的代码在Java中工作,而不是在C#中工作?

虽然它实际上并没有实现装饰器,但如果Computer.getComputer()virtual,您的代码将会正常工作。在代码的Main中,computer的类型为Computer。由于getComputer()不是virtual,因此调用Computer.getComputer()而不是预期的KeyBoard.getComputer()。因为在Java中,每个方法总是virtual,所以这个问题不会发生。

您的C#编译器应该给您一个警告:来自子类的getComputer()隐藏了原始实现。警告表示您正在进行的操作将编译,但可能无法按预期执行,这就是这种情况。

答案 1 :(得分:3)

computer.getComputer()位于以下行

Console.WriteLine(" You are getting a " + computer.getComputer());

调用Computer的getComputer版本,因为它是编译时类型(因为方法不是虚拟的)。

如果您需要多态行为,则需要将计算机类中的getComputer标记为virtual。然后,您可以完全删除那个不添加任何内容的ComponentDecorator类。

  

为什么这种情况只发生在C#中而不是Java?

因为默认情况下,所有方法都是java中的虚拟(可以覆盖)。在c#中它不是。您需要明确标记virtual

因此,您的完整实施将成为

public class Computer
{
    public Computer()
    {
    }

    public virtual String getComputer()
    {
        return "computer";
    }
}

public class Disk : Computer
{
    Computer computer;
    public Disk(Computer c)
    {
        computer = c;
    }


    public override String getComputer()
    {
        return computer.getComputer() + " and a disk";
    }

}

public class Monitor : Computer
{
    Computer computer;
    public Monitor(Computer c)
    {
        computer = c;
    }
    public override String getComputer()
    {
        return computer.getComputer() + " and a Monitor";
    }
}

public class KeyBoard : Computer
{
    Computer computer;
    public KeyBoard(Computer c)
    {
        computer = c;
    }

    public override String getComputer()
    {
        return computer.getComputer() + " and a KeyBoard";
    }

    public string call()
    {
        return "";
    }
}

答案 2 :(得分:0)

Computer.getComputer方法未标记为virtualComponentDecorator.getComputer方法未标记为override。在C#中,您可以在派生类中创建一个方法,该方法与基类中的方法具有相同的签名,而不会出现编译器错误(尽管您会收到警告)。这样做的结果是派生类中的方法"隐藏"基类中的方法而不是覆盖它,所以如果通过类型为派生类的引用调用方法,则获得派生类的实现,但是如果通过引用类型作为基类调用方法你得到基类实现。例如:

void Main()
{
    DerivedHide d1 = new DerivedHide();
    Console.WriteLine(d1.GetName()); // "DerivedHide"
    Base b = d1;
    Console.WriteLine(b.GetName());  // "Base"

    DerivedOverride d2 = new DerivedOverride();
    Console.WriteLine(d2.GetName());// "DerivedOverride"
    b = d2;
    Console.WriteLine(b.GetName()); // "DerivedOverride"
}

public class Base
{
    public virtual string GetName(){ return "Base"; }
}

public class DerivedHide : Base
{
    public string GetName() { return "DerivedHide"; } // causes compiler warning
}

public class DerivedOverride : Base
{
    public override string GetName() { return "DerivedOverride"; }
}

如果您将virtual添加到Computer.getComputeroverride添加到ComponentDecorator.getComputer,您的代码将按预期方式运行。

(顺便说一句,C#中的约定(与Java不同)是在PascalCase而不是camelCase中编写方法名称,因此Computer.GetComputer将优先于Computer.getComputer。)

答案 3 :(得分:0)

为了在示例中使用装饰器模式,您需要Computer.GetComputer()为虚拟。在Java中,我认为默认情况下所有方法都是虚拟的。在C#中并非如此,您明确需要通过virtual关键字将方法定义为虚拟方法。这就是为什么代码在java中工作但不在C#中工作。

这仍然不是代码中唯一的问题。即使您使Computer.GetComputer()为虚拟,输出也保持不变。另一个问题是你有效地隐藏了Computer.GetComputer()中的基类ComponentDecorator方法(C#编译器允许你忽略new关键字,尽管它会给你一个警告)。要使方法保持虚拟,您需要将方法定义为public abstract override String getComputer();。虽然看起来很奇怪,abstract override在C#中完全有效:What is the use of 'abstract override' in C#?这又适用于Java,因为默认情况下ComponentDecorator.GetComputer也是虚拟的。

通过这两项更改,您的代码可以正常运行,但我同意其他答案,因为您最好直接继承Computer而不是使用DecoratorComponent。如果Computer不在您的代码库中且方法GetComputer不是虚拟的,那么您将不得不使用不同的模式。

答案 4 :(得分:0)

如果我要实现装饰模式,我会选择这样的东西 -

public interface IComponent
{
    String getComputer();
}
public class Computer : IComponent
{
    public Computer()
    {
    }

    public virtual String getComputer()
    {
        return "computer";
    }
}

public interface IComponentDecorator : IComponent
{
}

public class Disk : IComponentDecorator
{
    IComponent computer;
    public Disk(IComponent c)
    {
        computer = c;
    }


    public String getComputer()
    {
        return computer.getComputer() + " and a disk";
    }

}

public class Monitor : IComponentDecorator
{
    IComponent computer;
    public Monitor(IComponent c)
    {
        computer = c;
    }
    public String getComputer()
    {
        return computer.getComputer() + " and a Monitor";
    }

}

public class KeyBoard : IComponentDecorator
{
    IComponent computer;
    public KeyBoard(IComponent c)
    {
        computer = c;
    }

    public String getComputer()
    {
        return computer.getComputer() + " and a KeyBoard";
    }

    public string call()
    {
        return "";
    }

}



class Program
{
    static void Main(string[] args)
    {
        IComponent computer = new Computer();
        computer = new Disk(computer);
        computer = new Monitor(computer);
        computer = new KeyBoard(computer);


        Console.WriteLine(" You are getting a " + computer.getComputer());
        Console.ReadKey();

    }
}

输出? -

You are getting a computer and a disk and a Monitor and a KeyBoard

enter image description here

以下是一些示例 - Decorator Pattern

答案 5 :(得分:0)

这里的代码示例都没有实际实现装饰器模式(好吧,当我写这个时它们没有...)。如果具体的类和装饰器是同一继承树的一部分,它就会失败。在这种情况下,实际存储对具体对象的引用是没有意义的,因为您只需调用base

在装饰器模式中,您的具体类和装饰器应该实现一个通用接口。它不依赖于继承或多态性。

public interface IComponent
{
    String getComputer();
}

public class Computer : IComponent
{
    public String getComputer()
    {
        return "computer";
    }
}

public abstract class ComponentDecorator
{
    protected ComponentDecorator(IComponent component)
    {
        this.Component = component;
    }

    protected IComponent Component { get; private set; }
}

public class Disk : ComponentDecorator, IComponent
{
    public Disk(IComponent c) : base(c)
    {
    }

    public String getComputer()
    {
        return this.Component.getComputer() + " and a disk";
    }
}

public class Monitor : ComponentDecorator, IComponent
{
    public Monitor(IComponent c)
        : base(c)
    {
    }

    public String getComputer()
    {
        return this.Component.getComputer() + " and a monitor";
    }
}

class Program
{
    static void Main(string[] args)
    {
        IComponent computer = new Monitor(new Disk(new Computer()));

        Console.WriteLine(" You are getting a " + computer.getComputer());
        Console.ReadKey();
    }
}

我提取了一个界面。但是,您可以使用完全相同的抽象类。抽象装饰器只是重复使用存储组件引用,但仅此而已。

关键是装饰者不应该从具体类继承,因为这会破坏装饰者模式的点。正如你所看到的,我甚至没有让基础装饰器实现接口来证明你绝对不需要后期绑定。

BTW:getComputer()违反了C#惯例。它应该是C#中的属性,并以大写字母开头。除了特定于语言的惯例之外,该方法的名称也与其意图有关。

答案 6 :(得分:0)

我的问题的真正解决方案是

public abstract override String getComputer() 
InBetween建议的ComponentDecorator中的

;因为我想要装饰计算机,所以它应该保持不变,即使在我的情况下(我正在使用的实际应用程序)也无法在计算机类中进行任何更改。