Search code examples
c#oopdesign-patternsdesign-principles

How to not violating the OCP when you want to choose between different classes which are inherited from an Interface?


I have an Interface lets say ISendOut which I've inherited two different classes from it for example TransferViaSerialPort and TransferViaWirelessModule (I mean implement this Interface in these two classes). How can I design my software to both giving the ability to the user to choose (IN THE UI) between the methods of sending his/her data out via SerialPort or WirelessModule and not violate the OCP? Because if I want to have a "Switch Case" or an "If/Else" statement I will Violate the OCP.


Solution

  • You need to use the Factory Pattern. And to make the Factory Pattern dynamic you can use Reflection and to show the Types of your Classes in the UI which are Implemented from ISendOut you can use Custom Attributes or other methods like using a 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);
        }
    }
    

    Later I add UsbSendOut without modifying existing code (so not Breaking the OCP)

    [DisplayName("Usb")]
    public class UsbSendOut : ISendOut
    {
        public void Send(string data)
        {
            MessageBox.Show("data sent through usb.");
        }
    }