在C#中用最少的样板实现组合模式?

时间:2013-07-30 06:47:39

标签: c# aop spring.net postsharp

我在这里查看过很多帖子,我对这个问题的答案并不完全清楚。

我希望能够使用合成实现一个接口,并在组件上调用所有方法:

  1. 为Component(即Car)创建一个接口。
  2. 创建一个类,实现Component(即SimpleCar)
  3. 创建另一个实现Component接口的类(即创建一个DeluxeCar,通过在SimpleCar成员上调用它们来实现这些方法),方法是在Component成员上调用它们。
  4. 现在需要注意的是,重要的是我希望能够在不编写所有包装方法的情况下完成第3部分,以使DeluxeCar调用SimpleCar上的方法。最好只需要添加C#装饰器或一些C#模板魔术。

    我见过两种解决方案:  1. ExtensionMethods - 我相信你可以实现一个CarComposite接口并在其上放置扩展方法(类似于C. Lawrence Wenham的回答:Multiple Inheritance in C#)。但是,我不认为您可以使Composite接口也实现Component接口。至少我还没有得到一个有效的例子。也许我错了。  2.面向方面编程。使用PostSharp或其他AOP框架执行步骤3.我无法找到有关如何执行此操作的示例代码。此外,我真的想避免使用PostSharp,因为我不喜欢许可证,并希望获得完全免费的解决方案。我认为PostSharp声明你无论如何都不能使用他们的免费版本(参见AspectInheritance http://www.postsharp.net/purchase)。 Spring.Net的一个工作示例会有很多帮助。

    有人可以提供#3的示例请使用标准C#库或Spring.net吗?

    更新

    h.alex的建议很有意思,但仍然不能满足我的需要。您对抽象类的使用有点限制。想象一下除了Car界面之外还有一个Boat界面。然后你想要创建一个实现船和汽车的两栖车。在C#中没有多重继承,因此您不能继承AbstractCar和AbstractBoat类。您不得不添加更多样板并创建AbstractAmphibiousVechicle抽象类。如果您没有太多不同的类,这可能没问题。但有时你想创建许多不同的类并混合和匹配这些行为。这在电子游戏中很常见。您可能希望能够对游戏对象执行大量不同的方面(推送,投掷,收集等)。你将被迫以这种方式制作许多不同的抽象类,每个抽象类都有很多样板。

    我想要这样的东西,PostSharp有,但我真的想知道是否有一个限制较少的许可证框架可以做同样的事情。这可以在Spring.net中轻松完成吗? http://doc.postsharp.net/postsharp-3.0/##PostSharp-3.0.chm/html/T_PostSharp_Aspects_CompositionAspect.htm

    http://doc.postsharp.net/postsharp-3.0/Default.aspx#PostSharp-3.0.chm/html/20c81e23-af91-4688-a672-b0f3cce793a9.htm

1 个答案:

答案 0 :(得分:1)

所以,我相信你在询问遗产。 嗯,可以做到的香草方式是:

public interface ICar
{
    void ShiftGearUp();
    void ShiftGearDown();
    void SwitchLightsOn();
    void SwitchLightsOff();
    void Brake();
    void Accelerate();
}

public class Car : ICar
{
    public virtual void ShiftGearUp() { }
    public virtual void ShiftGearDown() { }
    public virtual void SwitchLightsOn() { }
    public virtual void SwitchLightsOff() { }
    public virtual void Brake() { }
    public virtual void Accelerate() { }
}

public class DeluxeCar : Car
{
    public override void SwitchLightsOn()
    {
        //calls the implementation in the Car class.
        base.SwitchLightsOn();
        //implement custom behavior.
        this.AdjustCabinAmbientLighting();
    }

    private void AdjustCabinAmbientLighting() { }
}

这样您只能覆盖要更改的行为。

但是,世界着名的规则是prefer composition over inheritance

这将使我们以这种方式形成解决方案:

public interface ICar
{
    void ShiftGearUp();
    void ShiftGearDown();
    void SwitchLightsOn();
    void SwitchLightsOff();
    void Brake();
    void Accelerate();
}

public abstract class AbstractCar : ICar
{
    protected ITransmission Transmission;
    protected ILights Lights;
    protected IEngine Engine;
    protected IBrakes Brakes;

    public void ShiftGearUp()
    {
        this.Transmission.ShiftUp();
    }

    public void ShiftGearDown()
    {
        this.Transmission.ShiftDown();
    }

    public void SwitchLightsOn()
    {
        this.Lights.SwitchOn();
    }

    public void SwitchLightsOff()
    {
        this.Lights.SwitchOff();
    }

    public void Brake()
    {
        this.Brakes.Brake();
    }

    public void Accelerate()
    {
        this.Engine.Accelerate();
    }
}

public class Car : AbstractCar
{
    public Car()
    {
        this.Lights = new AcmeLights();
        //todo
        //this.Engine = init engine object;
        //this.Brakes = init brakes object;
        //this.Transmission = init transmission object;
    }
}

public class DeluxeCar : AbstractCar
{
    public DeluxeCar()
    {
        this.Lights = new FancyLights();
        //todo
        //this.Engine = init engine object;
        //this.Brakes = init brakes object;
        //this.Transmission = init transmission object;
    }
}

public interface ITransmission
{
    void ShiftUp();
    void ShiftDown();
}

public interface ILights
{
    void SwitchOn();
    void SwitchOff();
}

public interface IEngine
{
    void Accelerate();
}

public interface IBrakes
{
    void Brake();
}

public class AcmeLights : ILights
{
    private LightSwitch Switch;

    public void SwitchOn() { this.Switch.On(); }
    public void SwitchOff() { this.Switch.Off(); }
}

public class FancyLights : ILights
{
    private LightSwitch Switch;
    private CabinAmbientLightingController AmbientLightingController;

    public void SwitchOn()
    {
        this.Switch.On();
        this.AmbientLightingController.AdjustLightLevel();
    }

    public void SwitchOff()
    {
        this.Switch.Off();
        this.AmbientLightingController.AdjustLightLevel();
    }
}

public class LightSwitch
{
    public void On() { }
    public void Off() { }
}

public class CabinAmbientLightingController
{
    public void AdjustLightLevel() { }
}

编辑:然后您可以使用您选择的依赖注入工具通过其构造函数注入Car和Deluxe汽车的依赖项。这可能是“AOP”。

编辑: 为此,您可以使用动态代理。着名的教程是this。可能是一个学习曲线(可能不是这个问题的最佳解决方案,但我不知道其他任何问题) - 但总的来说它是一个很棒的框架和有趣的阅读。

我会做什么,请注意我的DynProxy有点生锈,声明是一个空类,如

public class RocketCar
{

}

然后生成一个没有目标的类代理,并使用拦截器转发调用。因此,我们应该编写一个通用的拦截器,我们可以注入依赖关系(就像上面的两个具体的汽车)。所以,我们只有一个这样的类,并根据需要配置和使用它。我相信这应该可以在一天或两天内编码:) 这个想法是“没有实现接口的类”部分here。 我们将使RocketCar实现IRocket和ICar。

但是! 在一个类中实现许多接口并不是最好的做事方式!你打破了SRP。 如果我们仍然拥有所有这些接口,为什么不让客户端代码通过各个接口引用它们呢?毕竟,如果RocketCar实现了ICar,那么它的Accelerate方法一定不能意识到它是不同汽车的一部分。换句话说,RocketCar必须以这样的方式运行,即我们可以将RocketCar替换为任何其他ICar实现,无论它在何处使用。否则我们会打破LSP

因此,将ICar和IRocket放在单独的类中是件好事。这些的具体实现可以具有彼此的具体类型引用,其模拟它们的交互。

我并不是说你不应该让类实现多个接口,但这应该是一个例外而不是规则。