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

本文关键字:组合 模式 实现 | 更新日期: 2023-09-27 18:25:22

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

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

  1. 为组件(即汽车)创建一个接口
  2. 创建一个类,实现组件(即SimpleCar)
  3. 通过在Component成员上调用方法,创建另一个实现Component接口的类(即创建一个DeluxeCar,通过在SimpleCar成员上调用这些方法来实现这些方法)

现在需要注意的是,重要的部分是,我希望能够在不编写所有包装器方法的情况下完成第3部分,以使DeluxeCar调用SimpleCar上的方法。最好只需要添加一个C#装饰器或一些C#模板魔术。

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

有人能提供一个使用标准C#库或Spring.net的#3示例吗?

更新

亚历克斯的建议很有意思,但仍然不能满足我的需要。你对抽象类的使用有点限制。想象一下,除了Car界面之外,还有一个Boat界面。然后你想创建一个水陆两用车,实现船和车。在C#中没有多重继承,因此不能同时继承AbstractCar和AbstractBoat类。您不得不添加更多的样板,并创建一个AbstractAmphibiousVecchicle抽象类。如果你没有太多不同的类,这可能没问题。但有时你想创建许多不同的类,并混合和匹配这些行为。这在电子游戏中很常见。你可能有很多不同的方面想要处理一个游戏对象(Push it、Throw it、Collect it等)。你将被迫以这种方式创建许多不同的抽象类,每个类都有很多样板。

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

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

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

所以,我相信您在询问继承问题。好吧,我们可以用香草的方式来做这件事:

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

通过这种方式,您只能覆盖要更改的行为。

但是,有一条世界著名的规则说,比起继承,更喜欢构图。

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

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"。

编辑:为此,您可以使用动态代理。著名的教程是这样的。这可能有点像一条学习曲线(可能不是这个问题的最佳解决方案,但我不认识其他人),但总的来说,这是一个很棒的框架和一本有趣的读物。

请注意,我的DynPxy有点生疏,我会做的是声明一个空类,比如

public class RocketCar
{
}

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

但是!在一个类中实现多个接口不是最好的方法!你违反了SRP。如果我们有所有这些接口,为什么不让客户端代码通过单个接口引用它们呢?毕竟,如果RocketCar实现了ICar,那么它的Accelerate方法一定不会意识到它是另一辆车的一部分。换言之,RocketCar必须以这样一种方式行事,即无论在哪里使用,我们都可以用任何其他ICar实现来替代RocketCar。否则,我们将破坏LSP。

因此,将ICar和IRocket分为不同的类是很好的。它们的具体实现可以具有对彼此的具体类型引用,这些引用为它们的交互建模。

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