避免策略模式中的双重 switch case
Avoid double switch case in strategy pattern
我知道这是很多东西,但请耐心等待,我不知道如何全面地描述这个问题。假设有一台机器有 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 由以下 class 表示(我无法更改!):
// 机器
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
}
我的目标是编写一个控件 class,它将提供可用于设置这些变量的方法。我尝试使用策略模式(或至少与此类似的东西)。 mashine 零件树的模式让我尝试应用该模式两次(因为它有 2 个拆分级别)。一次在可互换设备中,一个在可互换轴中。这是我目前的代码:
// 设备控制接口和 2 个不同的设备 Classes 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; }
}
}
Axis representation (Interface and Explicit Classes): 基本上它们是为了将 static
class Mashine
中的变量映射到不同的属性上轴
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;
}
}
这是控件 Class,它提供了依赖于设备和适当轴的机器控制方法:
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;
}
}
}
问题一
如何在 InitAxisAndDevice
方法中避免这种双重 switch 情况?
问题二
有没有更好的方法来确保只有 A 型轴与设备 A 一起使用?
我有一种强烈的感觉,我在这个模式的应用中误解了一些东西。是否有不同的 approach/pattern 更适合解决此控制映射问题?
非常感谢任何帮助。提前谢谢你。
编辑
由于我解释的太笼统了,这里是一个用例。
所有轴都有一组几乎相同的变量,必须设置为:[enable(bool), position(double), start(bool)]
抽象点是有一个单一的控件class,它可以使用一种方法Enable
f.e。根据设备类型和轴类型启用任何轴。
我希望它变得更清晰一些
考虑以下代码。这个想法是创建一个设备轴、设备和机器的抽象表示作为接口。然后使用工厂为(给定不可更改)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 Device
和enum Axis
并没有真正的优势。比较以下代码:
// controllers
Control yourControl;
IMachineModel myMachineModel;
// usage
yourControl.Enable(Device.A, Axis.X);
myMachineModel.A.X.Enable();
如果你问我,这里使用 Control
的优势并没有真正改进代码。
编辑其他参数
也许 Enable
和 Disable
已经过度设计了?换一些通用的 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*/),
};
}
// ...
我知道这是很多东西,但请耐心等待,我不知道如何全面地描述这个问题。假设有一台机器有 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 由以下 class 表示(我无法更改!):
// 机器
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
}
我的目标是编写一个控件 class,它将提供可用于设置这些变量的方法。我尝试使用策略模式(或至少与此类似的东西)。 mashine 零件树的模式让我尝试应用该模式两次(因为它有 2 个拆分级别)。一次在可互换设备中,一个在可互换轴中。这是我目前的代码:
// 设备控制接口和 2 个不同的设备 Classes 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; }
}
}
Axis representation (Interface and Explicit Classes): 基本上它们是为了将 static
class Mashine
中的变量映射到不同的属性上轴
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;
}
}
这是控件 Class,它提供了依赖于设备和适当轴的机器控制方法:
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;
}
}
}
问题一
如何在 InitAxisAndDevice
方法中避免这种双重 switch 情况?
问题二
有没有更好的方法来确保只有 A 型轴与设备 A 一起使用?
我有一种强烈的感觉,我在这个模式的应用中误解了一些东西。是否有不同的 approach/pattern 更适合解决此控制映射问题?
非常感谢任何帮助。提前谢谢你。
编辑
由于我解释的太笼统了,这里是一个用例。 所有轴都有一组几乎相同的变量,必须设置为:[enable(bool), position(double), start(bool)]
抽象点是有一个单一的控件class,它可以使用一种方法Enable
f.e。根据设备类型和轴类型启用任何轴。
我希望它变得更清晰一些
考虑以下代码。这个想法是创建一个设备轴、设备和机器的抽象表示作为接口。然后使用工厂为(给定不可更改)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 Device
和enum Axis
并没有真正的优势。比较以下代码:
// controllers
Control yourControl;
IMachineModel myMachineModel;
// usage
yourControl.Enable(Device.A, Axis.X);
myMachineModel.A.X.Enable();
如果你问我,这里使用 Control
的优势并没有真正改进代码。
编辑其他参数
也许 Enable
和 Disable
已经过度设计了?换一些通用的 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*/),
};
}
// ...