Как я могу моделировать связь между двумя полиморфными классами без использования перечисления

#c# #oop #enums #polymorphism #model-associations

#c# #ооп #перечисления #полиморфизм #модель-ассоциации

Вопрос:

Я использую an enum для обозначения двух разных классов продуктов — SensorDevice и [data] CaptureDevice . Они предназначены для использования физически подключенными друг к другу, но не каждая модель датчика может использоваться каждой моделью приемника, поэтому я создал перечисление для разных платформ, поэтому графический интерфейс будет отображать только совместимые аксессуары для данного устройства:

 enum Platform {
    Standard,
    Deluxe,
    Jumbo
}

abstract class CaptureDevice
{
    public Platform platform;

    public bool IsCompatibleWith(SensorDevice sd)
    {
        return sd.Platforms.Contains(this.Platform); // <- this is type-checking!
    }
}

abstract class SensorDevice
{
    public IEnumerable<Platform> Platforms;

    public bool IsCompatibleWith(CaptureDevice cd)
    {
        return this.Platforms.Contains(cd.Platform); // <- this is type-checking!
    }
}
  

Я начал находить это довольно неприятным, поскольку перечисление жестко запрограммировано. Я рассматриваю возможность использования рефакторинга «Заменить код типа полиморфизмом», но я не совсем уверен, как это сделать в данной ситуации. Есть предложения?

Комментарии:

1. Как вы выбираете, что Platform для чего Device ? Это по имени правила или полностью произвольно? Можете ли вы показать некоторые реализованные устройства?

2. Пусть platfom спросит датчик, совместим ли он. Это позволит вам скрыть перечисление платформ на датчике, а затем при необходимости реорганизовать датчики, чтобы исключить использование перечисления платформы там. Однако это может быть больше работы, поскольку есть польза.

3. @romain-aga правило является произвольным. Теоретически это связано с геометрическими и электрическими характеристиками датчика, но также с коммерческими соображениями и соображениями портфолио. Так что да, это произвольно. Кроме того, нет никакой уверенности в том, что в будущем другие модели платформы будут недоступны, с нежелательной необходимостью «перекомпилировать» перечисление.

4. @ReneA Если я правильно понимаю, в настоящее время это происходит при вызове aCaptureDevice.IsCompatibleWith(aSensorDevice) . То, что я ищу, — это способ реализовать эту подпись без использования enum вообще!

Ответ №1:

Вы могли бы сделать следующее, чтобы избавиться от enum .

Замените перечисление классом.

 public abstract class Platform {}
  

Добавьте Device класс, который отвечает, совместим ли он с a Platform .

 public abstract class Device
{
    public abstract bool IsCompatibleWith(Platform platform);
}
  

Создайте CaptureDevice подкласс Device .

 public abstract class CaptureDevice : Device
{
    public Platform Platform;

    public override bool IsCompatibleWith(Platform platform)
    {
        // I'm using type comparison for the sake of simplicity,
        // but you can implement different business rules in here.

        return this.Platform.GetType() == platform.GetType();
    }

    public bool IsCompatibleWith(SensorDevice sd)
    {
       // We are compatible if sensor is compatible with my platform.

        return sd.IsCompatibleWith(this.Platform);
    }
}
  

Создайте SensorDevice подкласс Device .

 public abstract class SensorDevice : Device
{
    public IEnumerable<Platform> Platforms;

    public override bool IsCompatibleWith(Platform platform)
    {
        // I'm using type comparison again.

        return this.Platforms.Any(p => p.GetType() == platform.GetType());
    }

    public bool IsCompatibleWith(CaptureDevice cd)
    {
        // We are compatible if capture is compatible with one of my platforms. 

        return this.Platforms.Any(p => cd.IsCompatibleWith(p));
    }
}
  

В принципе, это все, что вам нужно сделать. Чтобы проиллюстрировать, как это работает, я добавил приведенный ниже код.

 // Platforms
public class StandardPlatform : Platform { }
public class DeluxPlatform : Platform { }
public class JumboPlatform : Platform { } 

// Capture device(s)
public class CD1 : CaptureDevice
{
    public CD1()
    {
        Platform = new StandardPlatform();
    }
}

// Sensor device(s)
public class SD1 : SensorDevice
{
    public SD1()
    {
        Platforms = new List<Platform>
        {
            new StandardPlatform(),
            new DeluxPlatform()
        };
    }
}

public class SD2 : SensorDevice
{
    public SD2()
    {
        Platforms = new List<Platform> {new JumboPlatform()};
    }
}

// Somewhere in the code...
var cd1 = new CD1();
var sd1 = new SD1();

Console.WriteLine(cd1.IsCompatibleWith(sd1)); // True
Console.WriteLine(sd1.IsCompatibleWith(cd1)); // True

var sd2 = new SD2();
Console.WriteLine(sd2.IsCompatibleWith(cd1)); // False
Console.WriteLine(cd1.IsCompatibleWith(sd2)); // False
  

Ответ №2:

РЕДАКТИРОВАТЬ: если вы хотите избавиться от перечисления, почему бы не использовать файл конфигурации?

Вы настраиваете свои платформы следующим образом:

 <Platforms>
    <Platform name="Standard">
        <Device>Namespace.of.your.Device.CaptureDevice</Device>
        <Device>Namespace.of.another.Device.AnotherDevice</Device>
    </Platform>
    <Platform name="Deluxe">
        <Device>Namespace.of.your.Device.CaptureDevice</Device>
    </Platform>
</Platforms>
  

Другой синтаксис:

 <Platforms>
    <Platform>
        <Name>Standard</Name>
        <Devices>
            <Device>Namespace.of.your.Device.CaptureDevice</Device>
            <Device>Namespace.of.another.Device.AnotherDevice</Device>
        <Devices>
    </Platform>
</Platforms>
  

Тогда у вас будет та же базовая структура, вы могли бы сделать что-то вроде этого:

 abstract class ADevice<T> where T : ADevice<T>
{
    public ADevice<T>(T device , string filename)
    {
        // Parsing of the file
        // Plus, setting the Platforms Property
    }

    // Or (but you should keep the first constructor to see the filename dependency)
    public ADevice<T>(T device)
    {
        // Parsing of the file
    }

    public IEnumerable<Platform> Platforms { get; private set; }

    public bool IsCompatibleWith(T device)
    {
        return this.Platforms.Contains(device.Platform); // <- this is type-checking!
    }
}
  

Затем:

 abstract class CaptureDevice : ADevice<CaptureDevice>
{
    public CaptureDevice(CaptureDevice device, string filename)
        : base(device, filename)
    {
    }

    // Or (But still same comment)
    public CaptureDevice(CaptureDevice device)
        : base(device, defaultFilename)
    {
    }
}
  

То же самое для SensorDevice

И вместо Enum , вы можете создать простой класс на данный момент как:

 public class Platform
{
    public Platform(string name)
    {
        Name = name;
    }

    public string Name { get; }
}
  

Комментарии:

1. @heltonbiker, я отредактировал свой пост, если вы все еще ищете решение по поводу этого перечисления. Это может вас заинтересовать.