Как не нарушать OCP, если вы хотите выбирать между различными классами, которые унаследованы от интерфейса? - PullRequest
4 голосов
/ 06 марта 2012

У меня есть интерфейс, скажем, ISendOut, который я унаследовал от него два разных класса например TransferViaSerialPort и TransferViaWirelessModule (я имею в виду реализовать этот интерфейс в этих двух классах). Как я могу спроектировать свое программное обеспечение так, чтобы оно давало пользователю возможность выбирать (В ИДЕ) между методами отправки его / ее данных через SerialPort или WirelessModule и не нарушать OCP ? Потому что, если я захочу получить заявление «Switch Case» или «If / Else», я нарушу OCP.

Ответы [ 4 ]

2 голосов
/ 07 марта 2012

Вам необходимо использовать фабричный шаблон.А чтобы сделать Factory Pattern динамическим, вы можете использовать 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.");
    }
}
1 голос
/ 06 марта 2012

Вы передаете свою реализацию ISendOut в качестве параметра, например, конструктору, и позволяете C # динамической диспетчеризации выполнить "случай переключения", как вы его выразили.

Вот почему интерфейсы так полезны: у вас есть косвенное обращение, и вы можете ввести зависимость для удовлетворения OCP.

1 голос
/ 06 марта 2012

Создание класса 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, но это легко исправить с помощью фабрики в нем).

Обновление

Вы знаете, что с этим не так? Я хочу дать возможность пользователю выбрать метод отправки данных в пользовательском интерфейсе. Теперь представьте, что у нас будет гораздо больше методов отправки, то есть отправки через инфракрасный порт или ... поэтому, позволяя пользователю выбирать между различными методами, я должен иметь в своем пользовательском интерфейсе утверждение if, которое будет нарушать OCP. Потому что каждый новый тип рассылки заставляет меня иметь новое условие if / else

Мой подход переносит нарушение OCP только в один класс, а не в каждое место, где используется интерфейс ISendOut.

Я также упомянул фабрику, в которой я имею в виду фабричную модель (ни абстрактная фабрика, ни фабричный метод). Вы можете использовать его для сопоставления между строками конфигурации и конкретными классами и использовать эту фабрику внутри UserConfiguredCommunicationModule для создания правильной реализации ISendOut.

Вы также можете использовать шаблон локатора службы в UserConfiguredCommunicationModule для определения правильной реализации.

Эта точка не имеет значения, что вы выбираете, вам нужен UserConfiguredCommunicationModule аналогичный класс для инкапсуляции процесса выбора.

0 голосов
/ 06 марта 2012
...