避免策略模式中的双重切换案例

时间:2017-11-15 09:49:39

标签: c# switch-statement strategy-pattern

我知道这很多东西但是请耐心等待我,我不知道如何全面地描述这个问题。想象一下,有一个mashine有2个设备A& B。每个设备都有2个可以打开和关闭的轴。 (轴实际上有更多的属性要设置,但为了简单起见,我们只关注1):

        mashine
           |
      ------------
      |          |
      A          B
    ------    ------
    |    |    |    |
    X    Y    X    Y
   ---  ---  ---  ---
   |||  |||  |||  |||
   ESP  ESP  ESP  ESP

E: Enable(bool) | S: Start(bool) | P: Position(double)

mashine由以下类表示(我无法更改!):

// MASHINE

public static class Mashine
{
    public static bool Enable_B_X { get; set; }
    public static bool Enable_B_Y { get; set; }

    public static bool Enable_A_X { get; set; }
    public static bool Enable_A_Y { get; set; }
    // actually much more properties for each axis and device
}

我的目标是编写一个控件类,它将提供可用于设置这些变量的方法。我尝试使用策略模式(或至少类似于此)。 mashine部分树的模式使我尝试将模式应用两次(因为它有2个分割级别)。一旦进入可互换装置,一个进入可互换轴。这是我到目前为止的代码:

//设备控制接口和2个不同的设备类A和B

public class DeviceControl
{
    public virtual IAxis Axis { get; set; }

    public void Enable()
    {
        Axis.Enable = true;
    }
    public void Disable()
    {
        Axis.Enable = false;
    }
}

public class DeviceControl_A : DeviceControl
{
    public override IAxis Axis
    {
        get { return base.Axis as IAxis_A; }
        set { base.Axis = value as IAxis_A; }
    }
}

public class DeviceControl_B : DeviceControl
{
    public override IAxis Axis
    {
        get { return base.Axis as IAxis_B; }
        set { base.Axis = value as IAxis_B; }
    }
}

轴表示(接口和显式类):基本上它们用于将staticMashine中的变量映射到不同轴的属性

public interface IAxis
{
    bool Enable { get; set; }
}

// These Interfaces are to ensure that Axis_A goes only into Device A
// and Axis_B only with device B
public interface IAxis_A : IAxis { }

public interface IAxis_B : IAxis { }

public class X_Axis_A : IAxis_A
{
    public bool Enable
    {
        get => Mashine.Enable_A_X;
        set => Mashine.Enable_A_X = value;
    }
}

public class Y_Axis_A : IAxis_A
{
    public bool Enable
    {
        get => Mashine.Enable_A_Y;
        set => Mashine.Enable_A_Y = value;
    }
}

public class X_Axis_B : IAxis_B
{
    public bool Enable
    {
        get => Mashine.Enable_B_X;
        set => Mashine.Enable_B_X = value;
    }
}

public class Y_Axis_B : IAxis_B
{
    public bool Enable
    {
        get => Mashine.Enable_B_Y;
        set => Mashine.Enable_B_Y = value;
    }
}

这是Control Class,它提供了依赖于设备和相应轴来控制mashine的方法:

public enum Device { A, B }

public enum Axis { X, Y }


public class Control
{
    public DeviceControl devControl;

    public void Disable(Device dev, Axis dim)
    {
        // initialize
        InitAxisAndDevice(dev, dim);
        devControl.Disable();       
    }

    public void Enable(Device dev, Axis dim)
    {
        InitAxisAndDevice(dev, dim);
        devControl.Enable();
    }


    private void InitAxisAndDevice(Device dev, Axis dim)
    {
        switch (dev)
        {
            case Device.A:
                this.devControl = new DeviceControl_A();

                switch (dim)
                {
                    case Axis.X: this.devControl.Axis = new X_Axis_A(); break;
                    case Axis.Y: this.devControl.Axis = new Y_Axis_A(); break;
                    case Axis.Z: this.devControl.Axis = new Z_Axis_A(); break;
                }
                break;
            case Device.B:
                this.devControl = new DeviceControl_B();

                switch (dim)
                {
                    case Axis.X: this.devControl.Axis = new X_Axis_B(); break;
                    case Axis.Y: this.devControl.Axis = new Y_Axis_B(); break;
                    case Axis.Z: this.devControl.Axis = new Z_Axis_B(); break;
                }
                break;
        }
    }
}

问题1

如何在InitAxisAndDevice方法中避免这种双开关案例?

问题2

有没有更好的方法来确保只有A型轴与设备A一起使用?

我有强烈的感觉,我在这种模式的应用中误解了一些东西。可能有一种不同的方法/模式更适合解决这种控制问题的映射吗?

任何帮助都非常适合。提前谢谢。

修改

由于我的解释太模糊,这是一个用例。 所有轴都有一组几乎相同的变量,必须设置如下:[enable(bool),position(double),start(bool)]

抽象点是拥有一个可以使用一个方法Enable f.e的控件类。根据设备类型和轴类型启用任何轴。

我希望它变得更清晰

1 个答案:

答案 0 :(得分:2)

请考虑以下代码。我们的想法是创建设备轴,设备和机器的抽象表示作为接口。然后使用工厂为({给定不可更改的)Mashine创建具体设备和轴。

public static class Mashine
{
    public static bool Enable_B_X { get; set; }
    public static bool Enable_B_Y { get; set; }

    public static bool Enable_A_X { get; set; }
    public static bool Enable_A_Y { get; set; }
}

/// <summary>
/// Represents a single axis of a single device
/// </summary>
public interface IDeviceAxis
{
    void Enable();
    void Disable();
}
// general device that has two axis, but doesn't care about anything else
public interface IDevice
{
    IDeviceAxis X { get; }
    IDeviceAxis Y { get; }
}
// data model for an alternative Mashine representation
public interface IMachineModel
{
    IDevice A { get; }
    IDevice B { get; }
}

用法:

public enum Device { A, B }

public enum Axis { X, Y }

// your control class
public class Control
{
    public IMachineModel devControl;

    public Control()
    {
        // MachineFactory see below
        devControl = MachineFactory.GetMachine();
    }

    public void Disable(Device dev, Axis dim)
    {
        GetAxis(dev, dim).Disable();
    }

    public void Enable(Device dev, Axis dim)
    {
        GetAxis(dev, dim).Enable();
    }

    private IDeviceAxis GetAxis(Device dev, Axis dim)
    {
        var device = GetDevice(dev);
        switch (dim)
        {
            case Axis.X:
                return device.X;
            case Axis.Y:
                return device.Y;
            default:
                throw new ArgumentException("Invalid Axis", "dim");
        }
    }
    private IDevice GetDevice(Device dev)
    {
        switch (dev)
        {
            case Device.A:
                return devControl.A;
            case Device.B:
                return devControl.B;
            default:
                throw new ArgumentException("Invalid Device", "dev");
        }
    }
}

缺少部分:接口的具体实现和获取整个机器表示的工厂:

// concrete machine factory
public static class MachineFactory
{
    // factory for the whole Mashine wrapper
    public static IMachineModel GetMachine()
    {
        return new MachineModel
        {
            A = GetDeviceA(),
            B = GetDeviceB(),
        };
    }

    // factory methods specify the connection from the wrapper classes to the Mashine

    private static IDevice GetDeviceA()
    {
        return new MachineDevice(x => Mashine.Enable_A_X = x, y => Mashine.Enable_A_Y = y);
    }
    private static IDevice GetDeviceB()
    {
        return new MachineDevice(x => Mashine.Enable_B_X = x, y => Mashine.Enable_B_Y = y);
    }

    // concrete implementations can be used for the target Mashine

    private class MachineDeviceAxis : IDeviceAxis
    {
        Action<bool> _setterFunction;
        public MachineDeviceAxis(Action<bool> setter)
        {
            _setterFunction = setter;
        }

        public void Enable()
        {
            _setterFunction(true);
        }

        public void Disable()
        {
            _setterFunction(false);
        }
    }

    private class MachineDevice : IDevice
    {
        public MachineDevice(Action<bool> xSetter, Action<bool> ySetter)
        {
            X = new MachineDeviceAxis(xSetter);
            Y = new MachineDeviceAxis(ySetter);
        }
        public IDeviceAxis X { get; private set; }

        public IDeviceAxis Y { get; private set; }
    }

    private class MachineModel : IMachineModel
    {
        public IDevice A { get; set; }

        public IDevice B { get; set; }
    }
}

关于机器抽象的使用,我认为使用enum Deviceenum Axis没有真正的优势。比较以下代码:

// controllers
Control yourControl;
IMachineModel myMachineModel;
// usage
yourControl.Enable(Device.A, Axis.X);
myMachineModel.A.X.Enable();

如果你问我,在这里使用Control的好处并没有真正改善代码。

关于其他参数的修改

也许EnableDisable已经过度设计了?一些通用Set(value)如何改为:

public interface IAxisParameter<TParameter>
{
    void Set(TParameter value);
}

public interface IDeviceAxis
{
    IAxisParameter<bool> Enabled { get; }

    IAxisParameter<double> Position { get; }

    IAxisParameter<bool> Start { get; }
}

用法:

public class Control
{
    public void Disable(Device dev, Axis dim)
    {
        GetAxis(dev, dim).Enabled.Set(false);
    }

    public void Enable(Device dev, Axis dim)
    {
        GetAxis(dev, dim).Enabled.Set(true);
    }

    // rest of the code as in the original answer above
}

然后相应地更改内部机器实现:

// inside factory

    // added implementation
    private class MachineParameter<TParam> : IAxisParameter<TParam>
    {
        Action<TParam> _setterFunction;
        public MachineParameter(Action<TParam> setter)
        {
            _setterFunction = setter;
        }
        public void Set(TParam value)
        {
            _setterFunction(value);
        }
    }

    // changed implementation
    private class MachineDeviceAxis : IDeviceAxis
    {
        public IAxisParameter<bool> Enabled { get; set; }

        public IAxisParameter<double> Position { get; set; }

        public IAxisParameter<bool> Start { get; set; }
    }

    // changed factory methods

    private static IDeviceAxis GetDeviceAxisA_X()
    {
        return new MachineDeviceAxis
        {
            Enabled = new MachineParameter<bool>(x => Mashine.Enable_A_X = x),
            Position = null, // TODO
            Start = null, // TODO
        };
    }
    private static IDeviceAxis GetDeviceAxisA_Y()
    {
        return new MachineDeviceAxis
        {
            Enabled = new MachineParameter<bool>(y => Mashine.Enable_A_Y = y),
            Position = null, // TODO
            Start = null, // TODO
        };
    }
    private static IDevice GetDeviceA()
    {
        return new MachineDevice
        {
            X = GetDeviceAxisA_X(),
            Y = GetDeviceAxisA_Y(),
        };
    }
    private static IDevice GetDeviceB()
    {
        // TODO same thing for device B
        return null;
    }

概念应该清楚......最后你必须为实际机器的每个属性编写一些代码。 也许将工厂内的参数创建重新组织成@Fildor在其链接代码示例中所做的事情是个好主意。

编辑:保持工厂代码更紧凑

不是为每个参数使用单独的方法使工厂膨胀,而是重新创建参数设置器,以便在一个地方的所有设备中为一个参数类型创建设置器(例如CreateEnabledParameterSetters):

// concrete machine factory
public static class MachineFactory
{
    // factory for the whole Mashine wrapper
    public static IMachineModel GetMachine()
    {
        var enabledSetters = CreateEnabledParameterSetters();
        return new MachineModel
        {
            A = GetDevice(enabledSetters, 0 /*A*/),
            B = GetDevice(enabledSetters, 1 /*B*/),
        };
    }

    // factory methods specify the connection from the wrapper classes to the Mashine

    private static Action<bool>[,] CreateEnabledParameterSetters()
    {
        return new Action<bool>[,]
        {
            { x => Mashine.Enable_A_X = x, x => Mashine.Enable_A_Y = x },
            { x => Mashine.Enable_B_X = x, x => Mashine.Enable_B_Y = x },
        };
    }
    private static IDeviceAxis GetDeviceAxis(Action<bool>[,] enabledSetters, int deviceIndex, int axisIndex)
    {
        return new MachineDeviceAxis
        {
            Enabled = new MachineParameter<bool>(enabledSetters[deviceIndex, axisIndex]),
            Position = null, // TODO
            Start = null, // TODO
        };
    }
    private static IDevice GetDevice(Action<bool>[,] enabledSetters, int deviceIndex)
    {
        return new MachineDevice
        {
            X = GetDeviceAxis(enabledSetters, deviceIndex, 0 /*X*/),
            Y = GetDeviceAxis(enabledSetters, deviceIndex, 1 /*Y*/),
        };
    }

// ...