TransferViaSerialPortとTransferViaWirelessModuleなどの2つの異なるクラスを継承したインターフェイスがありますISendOut
(つまり、この2つのクラスにこのインターフェイスを実装します)。OCPを介してデータを送信する方法と、OCPに違反しない方法のどちらかをユーザーが(UIで)選択できるようにソフトウェアを設計するにはどうすればよいですか?「SwitchCase」または「If/Else」ステートメントが必要な場合は、OCPに違反するためです。SerialPort
WirelessModule
4 に答える
ファクトリパターンを使用する必要があります。また、ファクトリパターンを動的にするには、Reflectionを使用し、UI
実装されているクラスのタイプを表示するには、ISendOut
カスタム属性またはを使用するなどの他のメソッドを使用できますDictionary
。
[System.AttributeUsage(System.AttributeTargets.Class)]
public class DisplayNameAttribute : Attribute
{
public DisplayNameAttribute(string displayName)
{
DisplayName = displayName;
}
public string DisplayName { get; set; }
}
public interface ISendOut
{
void Send(string data);
}
[DisplayName("Wireless")]
public class WirelessSendOut : ISendOut
{
public void Send(string data)
{
MessageBox.Show("data sent through wireless.");
}
}
[DisplayName("Serial")]
public class SerialSendOut : ISendOut
{
public void Send(string data)
{
MessageBox.Show("data sent through serial port.");
}
}
public static class SendOutFactory
{
public static ISendOut CreateSendOut(string typeName)
{
var types = Assembly.GetExecutingAssembly().GetTypes();
var sendOutType = types.First(x => (typeof(ISendOut)).IsAssignableFrom(x) && x.Name == typeName);
return (ISendOut) Activator.CreateInstance(sendOutType);
}
}
public static class SendOutDiscovery
{
public static IEnumerable<NameType> Discover()
{
var types = Assembly.GetExecutingAssembly().GetTypes();
var sendOutTypes = types.Where(x => x != typeof(ISendOut) && (typeof(ISendOut)).IsAssignableFrom(x));
return sendOutTypes.Select(type => GetNameType(type)).ToList();
}
private static NameType GetNameType(Type type)
{
var nameType = new NameType
{
DisplayName = GetDisplayName(type),
TypeName = type.Name
};
return nameType;
}
private static string GetDisplayName(Type type)
{
return ((DisplayNameAttribute)type.GetCustomAttributes(typeof (DisplayNameAttribute), false).First()).DisplayName;
}
}
public class NameType //for binding in UI
{
public string DisplayName { get; set; }
public string TypeName { get; set; }
}
public class SendOutViewModel //sample using in wpf (window contains a combobox)
{
public SendOutViewModel()
{
SendTypes = new ObservableCollection<NameType>(SendOutDiscovery.Discover());
}
public NameType SelectedSendType { get; set; } //bind to selected item in combobox
public ObservableCollection<NameType> SendTypes { get; private set; } //bind to item source of combo
public string Data { get; set; } //data to be sent
public void Send()
{
ISendOut sendOut = SendOutFactory.CreateSendOut(SelectedSendType.TypeName);
sendOut.Send(Data);
}
}
後で、既存のコードを変更せずにUsbSendOutを追加します(OCPを壊さないようにします)
[DisplayName("Usb")]
public class UsbSendOut : ISendOut
{
public void Send(string data)
{
MessageBox.Show("data sent through usb.");
}
}
クラスを作成しますUserConfiguredCommunicationModule
(継承よりも構成を優先します)
public class UserConfiguredCommunicationModule : ISendOut
{
public UserConfiguredUserModule(SerialPort serial, WirelessModule wireless)
{}
public void Send(string data)
{
if (UserIdentity.Current.PrefersSerial)
serial.Send(data);
else
wireless.Send(data);
}
}
その実装を使用すると、OCPを破ることができなくなります(クラス自体はOCPに違反しますが、ファクトリを使用することで簡単に修正できます)。
アップデート
あなたはそれの何が悪いのか知っていますか?UIでデータを送信する方法をユーザーが選択できるようにしたい。ここで、送信の方法がはるかに多いと想像してください。つまり、赤外線を介して送信するなど、ユーザーにさまざまな方法を選択させることで、UIにifステートメントが必要になります。これはOCPに違反します。すべての新しいタイプの送信は、新しいif/else条件を強制するためです。
ISendOut
私のアプローチでは、OCPの違反を、インターフェイスが使用されるすべての場所ではなく、1つのクラスにのみ移動します。
また、ファクトリについても言及しました。これは、ファクトリパターン(抽象ファクトリでもファクトリメソッドでもない)を意味します。これを使用して、構成文字列と具象クラスの間をマッピングし、そのファクトリを内部で使用しUserConfiguredCommunicationModule
て適切なISendOut
実装を作成できます。
内でサービスロケーターパターンを使用しUserConfiguredCommunicationModule
て、正しい実装を解決することもできます。
その点は、何を選択してもUserConfiguredCommunicationModule
、選択プロセスをカプセル化するために同様のクラスが必要です。
戦略パターンを確認して くださいhttps://en.wikipedia.org/wiki/Strategy_pattern
http://www.dofactory.com/Patterns/PatternStrategy.aspx#_self1