Подтвердить что ты не робот

Проблема проектирования ООП

Что такое хороший дизайн в этом простом случае:

Скажем, у меня есть автомобиль базового класса с методом FillTank(Fuel fuel) где топливо также является базовым классом, который имеет несколько классов листьев, дизельное топливо, этанол и т.д.

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

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

4b9b3361

Ответ 1

Если существует жесткая граница между типами автомобилей и видами топлива, то FillTank() не имеет бизнеса в базовом классе Car, так как зная, что у вас есть автомобиль, вы не говорите, какое топливо, Таким образом, для обеспечения правильности во время компиляции в подклассах необходимо определить FillTank() и использовать только подкласс Fuel, который работает.

Но что, если у вас есть общий код, который вы не хотите повторять между подклассами? Затем вы пишете метод protected FillingTank() для базового класса, который вызывает функция подкласса. То же самое касается Fuel.

Но что, если у вас есть волшебный автомобиль, который работает на нескольких видах топлива, скажем, дизель или газ? Затем этот автомобиль становится подклассом как DieselCar, так и GasCar, и вы должны убедиться, что Car объявлен как виртуальный суперкласс, поэтому у вас нет двух экземпляров Car в объекте DualFuelCar. Заполнение резервуара должно выполняться с минимальной или никакой модификацией: по умолчанию вы получите как DualFuelCar.FillTank(GasFuel), так и DualFuelCar.FillTank(DieselFuel), предоставляя вам функцию перегруженного по типу.

Но что, если вы не хотите, чтобы подкласс имел функцию FillTank()? Затем вам нужно переключиться на проверку времени выполнения и сделать то, что, по вашему мнению, вам нужно: сделать проверку подкласса Fuel.type и либо выбросить исключение, либо вернуть код ошибки (предпочитаете последний), если есть несоответствие. В С++ RTTI и dynamic_cast<> - это то, что я бы рекомендовал. В Python isinstance().

Ответ 2

Использовать общий базовый класс (если ваш язык поддерживает его (ниже С#)):

public abstract class Car<TFuel>  where TFuel : Fuel
{
    public abstract void FillTank(TFuel fuel);
}

В основном это обеспечивает соблюдение любого класса, который наследует автомобиль, чтобы указать, какой тип топлива он использует. Кроме того, класс Car накладывает ограничение на то, что TFuel должен быть некоторым подтипом абстрактного класса Fuel.

Допустим, что у нас есть класс Diesel, который прост:

public class Diesel : Fuel
{
    ...
}

И автомобиль, который работает только на дизеле:

public DieselCar : Car<Diesel>
{
     public override void FillTank(Diesel fuel)
     {
          //perform diesel fuel logic here.
     }
}

Ответ 3

Только объектно-ориентированное программирование не может справиться с этой проблемой. Что вам нужно - это общее программирование (здесь показано решение С++):

template <class FuelType>
class Car
{
public:
  void FillTank(FuelType fuel);
};

Ваш дизельный автомобиль - это только конкретный автомобиль, Car<Diesel>.

Ответ 4

a двойная отправка можно использовать для этого: принять некоторое количество топлива перед заполнением. Имейте в виду, что на языке, который не поддерживает его напрямую, вы вводите зависимости

Ответ 5

Похоже, вы просто хотите ограничить тип топлива, который входит в ваш дизельный автомобиль. Что-то вроде:

public class Fuel
{
    public Fuel()
    {
    }
}

public class Diesel: Fuel
{
}

public class Car<T> where T: Fuel
{
    public Car()
    {
    }

    public void FillTank(T fuel)
    {
    }
}

public class DieselCar: Car<Diesel>
{
}

Сделал бы трюк, например.

var car = new DieselCar();
car.FillTank(/* would expect Diesel fuel only */);

По существу, вы здесь делаете, чтобы Car имел конкретные типы топлива. Он также позволяет вам создать автомобиль, который будет поддерживать любой тип Fuel (шанс был бы прекрасным!). Однако, в вашем случае, DieselCar, вы просто получите класс из автомобиля и ограничите его использованием только топлива Diesel.

Ответ 6

используйте оператор is для проверки против принятых классов, и вы можете выдать исключение в конструкторе

Ответ 7

Я думаю, что принятый метод будет иметь метод ValidFuel(Fuel f) в вашем базовом классе, который выдает какой-то NotImplementedException (разные языки имеют разные термины), если "листовые" автомобили не переопределяют его.

FillTank может быть тогда полностью в базовом классе и вызвать ValidFuel, чтобы убедиться, что он действителен.

public class BaseCar {
    public bool ValidFuel(Fuel f) {
        throw new Exception("IMPLEMENT THIS FUNCTION!!!");
    }

    public void FillTank(Fuel fuel) {
        if (!this.ValidFuel(fuel))
             throw new Exception("Fuel type is not valid for this car.");
        // do what you'd do to fill the car
    }
}

public class DieselCar:BaseCar {
    public bool ValidFuel(Fuel f) {
        return f is DeiselFuel
    }
}

Ответ 8

В CLOS-подобной системе вы можете сделать что-то вроде этого:

(defclass vehicle () ())
(defclass fuel () ())
(defgeneric fill-tank (vehicle fuel))
(defmethod fill-tank ((v vehicle) (f fuel)) (format nil "Dude, you can't put that kind of fuel in this car"))

(defclass diesel-truck (vehicle) ())
(defclass normal-truck (vehicle) ())
(defclass diesel (fuel) ())
(defmethod fill-tank ((v diesel-truck) (f diesel)) (format nil "Glug glug"))

дает вам такое поведение:

CL> (fill-tank (make-instance 'normal-truck) (make-instance 'diesel))
"Dude, you can't put that kind of fuel in this car"
CL> (fill-tank (make-instance 'diesel-truck) (make-instance 'diesel))
"Glug glug"

Какая, на самом деле, общая версия Lisp двойной отправки, как упоминалось stefaanv.

Ответ 9

вы можете расширить свой оригинальный интерфейс автомобиля

interface Car {
    drive();
}

interface DieselCar extends Car {
    fillTank(Diesel fuel);
}

interface SolarCar extends Car {
    chargeBattery(Sun fuel);
}

}