Это плохой дизайн?

Представьте, что у меня есть интерфейс под названием IVehicle.

Из этого интерфейса я извлекаю несколько конкретных типов, таких как автобус и автомобиль (все могут двигаться, замедляться, выключать двигатель и т. Д.). Мой интерфейс не имеет полей.

Будет ли плохой дизайн иметь один класс с полями (например, максимальная скорость транспортного средства) и использовать его для каждого конкретного типа? Это будет плохой дизайн? Кроме того, если я храню состояние в этом классе (например, использую поля), то должно ли оно быть статическим?

Спасибо

12 ответов

Я бы добавил TopSpeed к интерфейсу как свойство, как это:

interface IVehicle
{
    void Move();
    void SlowDown();
    void SwitchOffEngine();

    Int32 TopSpeed { get; }
}

Затем вы реализуете интерфейс в ваших конкретных объектах следующим образом:

class Car : IVehicle
{
    public void Move() { }
    public void SlowDown() { }
    public void SwitchOffEngine() { }

    public int TopSpeed
    {
        get { return 120; }
    }
}

class Bus : IVehicle
{
    public void Move() { }
    public void SlowDown() { }
    public void SwitchOffEngine() { }

    public int TopSpeed
    {
        get { return 98; }
    }
}

Я думаю, что это будет лучший подход и не требует базового класса.

Причина, по которой я считаю, что это лучший подход, потому что TopSpeed это свойство, которое является специфическим для реализации и не будет иметь никакого значения в базовом классе. Так как базовый класс потребует, чтобы любая реализация в любом случае установила это значение, я думаю, что лучше изменить интерфейс, чтобы включить его.

Вы говорите об абстрактном классе, и нет, это не плохой дизайн. Вот для чего они (в основном, интерфейсы, но с некоторой базовой реализацией).

Государство не должно храниться статически. Будет полная реализация этого класса (в форме одного из производных классов) каждый раз, когда вы создаете Автомобиль, Автобус и т. Д.

Вы не нарушаете OO, в вашем случае -

IVehicle определяет "если вы автомобиль, что вы должны быть в состоянии сделать"

AbstractVehicle определяет "если вы автомобиль, это минимум, который вы должны делать, и это то, что вы должны делать"

ConcreteVehicle определяет "Я - транспортное средство, это мой способ делать то, что я должен делать, поскольку я - транспортное средство".

Вам не нужно ставить все свои свойства как статические. Что-то вроде максимальной скорости или максимальной скорости может оставаться неизменным, но вы также можете сохранять их как постоянные, ваш вызов.

Я второй DannySmurf, абстрактные классы очень полезны. Я очень часто использую шаблон скрытия-абстрактного-класса-за-интерфейсом в библиотеках классов, чтобы все классы реализации, даже абстрактный родительский класс, можно было использовать в качестве внутренних классов, то есть я не раскрываю реализации клиенту вообще.

Я думаю, что вы должны использовать суперклассы, а не интерфейсы. Поэтому, скорее всего, интерфейс IVehicle является избыточным, и у вас должен быть просто абстрактный суперкласс под названием Vehicle. Поскольку верно, что каждое транспортное средство может двигаться и, следовательно, может измерять свою скорость, вы можете добавить скорость как поле в суперкласс.

Это поле скорости ни в коем случае не является статичным, потому что оно будет создаваться для каждого конкретного экземпляра Vehicle; если у вас есть две шины, они могут иметь разные скорости, поэтому поле не может быть статичным, но должно соответствовать конкретным экземплярам.

Просто чтобы прояснить ситуацию...

Интерфейс не может иметь полей.

Тем не менее, он может иметь свойства. Свойства, которые неизменно должны быть реализованы через код объектами, которые реализуют интерфейс.

Если вы не планируете приводить ссылку на объект через интерфейс обратно к конкретному типу, вы должны поместить некоторые свойства, которые имеют смысл в интерфейс, иначе вы не сможете получить к ним доступ.

Другими словами, если у вас есть это:

IVehicle vehicle = GetVehicle();
// want to get speed of vehicle?
Car car = (Car)vehicle;
Double speed = car.Speed;

Общие свойства должны быть помещены в общий интерфейс. Поскольку транспортное средство является движущимся объектом, скорость имеет смысл иметь там.

Тем не менее, количество двигателей будет иметь смысл для самолетов и некоторых лодок, но не для автомобилей, и такой тип информации, вероятно, придется размещать в некоторых более конкретных интерфейсах.

Статика будет означать разделение между экземплярами, я сомневаюсь, что это будет хорошая идея.

Однако общий план использования интерфейсов вместо простой иерархии классов для отделения доступа к объекту и его использования от его фактического типа и идентичности является хорошей идеей и приводит к хорошему дизайну.

Лучший план - выбрать уровень абстракции, который имеет смысл для вашей модели, и согласиться с этим. Вы можете очень легко перепроектировать такое решение. Например, вы могли бы понять, что некоторые транспортные средства вообще не имеют двигателей, а некоторые могут двигаться в более чем одном измерении (то есть не только вперед / назад, но вверх / вниз, в стороны и т. Д.) И выбирать уровень это подходит вашей модели, это хорошее начало.

Например, если вы собираетесь моделировать транспортные проблемы, с которыми сталкивается округ, независимо от того, может ли самолет двигаться только вперед / назад или у него есть двигатели с вертикальным поворотом или нет, это спорный вопрос, поэтому вам, вероятно, не следует пытаться спроектировать его в решение.

Будет ли плохой дизайн иметь один класс с полями (например, максимальная скорость транспортного средства) и использовать его для каждого конкретного типа? Это будет плохой дизайн?

Нет.

ИМХО, трудности с наследованием в C++ (я не очень хорошо знаю случай с Java и другими языками) помогли установить традицию, согласно которой "неконечные классы должны быть абстрактными" (на самом деле это элемент "More Effective C++" от Скотт Мейерс, если я хорошо помню). Но, во всяком случае, помещение полей и конкретной функциональности в базовый класс - это неплохой дизайн ОО.

На некоторых языках может быть полезно следовать этой стратегии. В классическом ОО и его языке флагов (Eiffel) это (наличие конкретных, полезных базовых классов, если ваш дизайн приводит вас к такому выбору) - это именно то, что нужно. Существуют языковые механизмы, предназначенные для решения любых потенциальных проблем, которые могут возникнуть из-за полиморфного использования вашего кода или множественного наследования.

Итак, это не плохой ОО дизайн; Это уж точно. Но вы должны принять во внимание, что в некоторых языках ваша естественная идея может традиционно избегаться в пользу менее естественных, но более практичных альтернатив.

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

Кроме того, если я храню состояние в этом классе (например, использую поля), то должно ли оно быть статическим?

Нет.

Будет ли плохой дизайн иметь один класс с полями (например, максимальная скорость транспортного средства) и использовать его для каждого конкретного типа? Это будет плохой дизайн?

Нет, не плохой дизайн. Альтернатива состоит в том, чтобы иметь абстрактное / чистое виртуальное свойство для каждого поля (например, максимальная скорость) ... если у вас много этих свойств, и если свойства просты (например, просто вернуть число, на самом деле ничего не делать сложным), тогда ваше представление о классе, который содержит значения специализированных свойств, кажется более простым, чем использование множества абстрактных / чисто виртуальных свойств.

Кроме того, если я храню состояние в этом классе (например, использую поля), то должно ли оно быть статическим?

Это нормально, если он статический, если и только, если он применяется для каждого экземпляра класса. Например, можно определить "максимальную скорость" как статическую (все шины имеют одинаковую максимальную скорость), но не "текущую скорость" (разные шины фактически движутся с разными скоростями в любой данный момент) .


Просто чтобы прояснить, я думаю, вы предлагаете дизайн, который выглядит примерно так:

interface IVehicle
{
  void accelerate();
}

sealed class VehicleProperties
{
  const int topSpeed;
}

class VehicleImplementation : IVehicle
{
  //const data
  const VehicleProperties vehicleProperties;
  //non-const instance/state data
  int currentSpeed;
  //ctor
  VehicleImplementation(VehicleProperties vehicleProperties)
  {
    this.vehicleProperties = vehicleProperties;
  }
  //implement IVehicle methods
  void accelerate()
  {
    if (currentSpeed => vehicleProperties.topSpeed)
      return;
    ... else todo: accelerate ...
  }
}

class Car : VehicleImplementation
{
  //static properties associated with the Car type
  static VehicleProperties carProperties = new VehicleProperties(120);
  //ctor
  Car()
    : VehicleImplementation(carProperties)
  {}
  //IVehicle methods already implemented by
  //the VehicleImplementation base class
}

Если вы "добавляете поле к интерфейсу", он перестает быть интерфейсом и становится базовым классом. Это может быть плохо или нет, в зависимости от ваших предпочтений.

Шаблон использования интерфейса и избегания базовых классов с переменной-членом вполне подойдет. Это особенно полезно в ситуациях, когда вы передаете интерфейс в другую часть системы.

Но без определенного контекста не существует абсолютного правила, гласящего: "Вы должны использовать интерфейс в качестве базового класса". Использование закрытой переменной-члена в вашем базовом классе может быть хорошим первым приближением к функциональности, которую вы ищете. Другое решение, использующее закрытую функцию, определенную в производном классе, является более многословным, и один только этот факт может усложнить понимание вашего кода (хотя, если все в вашей организации делают это, это не будет проблемой).

Это зависит от того, что вы хотели бы достичь. Это также зависит от того, какой стандарт используется в вашей организации, в вашем приложении или даже от того, какой язык вы используете (например, если вы используете язык с геттерами и сеттерами, вы можете легче заменить переменные-члены в более позднее время).

Является ли переменная-член статической или нет, это отдельная проблема. Вы должны сделать член статическим, если и только он не меняется в течение жизни класса. "Максимальная скорость" звучит так, как будто она соответствует этим критериям. Некоторые языки, такие как C++, имеют концепцию "статических функций", результаты которых не меняются. Я полагаю, что вы могли бы также использовать это, но я нахожу эту дополнительную концепцию слишком запутанной, чтобы иметь смысл.

Вы относитесь к конкретному языку. В C++, например, нет интерфейсов, но вы можете использовать абстрактные классы, такие как интерфейс.

Другой подход, и как я различаю абстрактные классы и интерфейсы для распространенных сценариев, это:

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

Я очень часто использую шаблон скрытия-абстрактного-класса-за-интерфейсом в библиотеках классов, чтобы все классы реализации, даже абстрактный родительский класс, можно было использовать в качестве внутренних классов, то есть я не раскрываю реализации клиенту вообще.

Есть ли пример этого?

Да, это плохая идея. Ваше определение IVehicle является настолько общим, но вы все еще сделали некоторые предположения. Во-первых, атрибут max-speed может не иметь одинаковых единиц измерения. У ребенка типа Автомобиль может быть максимальная скорость, измеренная в уставных милях, в то время как один из Лодки или Самолета будет использовать морские мили. Больше о проблеме такого рода обсуждается в этом посте Codewrights Tale об идентификации объекта. Есть тонкие проблемы с наследованием, которые наша культура приняла, но никогда полностью не исследовала. Если вы хотите понять эти тонкости, изучите, как ФАУ должна приблизиться к определениям того, что означает "Самолет", чтобы правила могли быть определены с некоторым подобием здравомыслия.

Другие вопросы по тегам