繁体   English   中英

装饰者模式和C#

[英]Decorator pattern and C#

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

为什么这只发生在C#中,而不是在Java中。 我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();

        }
    }
}

C#中的装饰者

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

入门

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

public interface IComputer
{
    string getComputer();
}

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

这里的Computersealed 它不一定是,但在这种情况下完成,以显示装饰器存在您的具体类旁边 ,而不是从它派生

没有用于装饰器的抽象基类

装饰器实现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());
    }
}

使用不使用抽象装饰器看它工作。

如果我们不能改变基类怎么办?

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

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

因此,我们假设基类是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());
    }
}

但结果是一样的,如这里所见。

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

虽然它实际上并没有实现装饰器,但如果Computer.getComputer()virtual ,那么您的代码将起作用。 在您的代码中,在Maincomputer属于Computer类型。 由于getComputer()不是virtual ,因此调用Computer.getComputer()而不是预期的KeyBoard.getComputer() 由于在Java中,每个方法都是virtual ,因此不会发生这种问题。

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

以下行中的computer.getComputer()

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

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

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

为什么这种情况只发生在C#中?

因为默认情况下,所有方法都是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 "";
    }
}

Computer.getComputer方法未标记为virtual ,并且ComponentDecorator.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.getComputeroverrideComponentDecorator.getComputer代码将按预期工作。

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

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

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

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

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

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

在此输入图像描述

这里还有一些样本 - 装饰模式

这里的代码示例都没有实际实现装饰器模式(好吧,当我写这个时它们没有...)。 如果具体的类和装饰器是同一继承树的一部分,它就会失败。 在这种情况下,实际存储对具体对象的引用是没有意义的,因为您可以简单地调用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();
    }
}

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

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

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

我问题的真正解决方案是

public abstract override String getComputer() 

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM