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

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

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

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

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

24
задан crowne 3 November 2010 в 16:52
поделиться

9 ответов

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

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

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

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

11
ответ дан 28 November 2019 в 23:04
поделиться

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

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

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

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

public class Diesel : Fuel
{
    ...
}

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

public DieselCar : Car<Diesel>
{
     public override void FillTank(Diesel fuel)
     {
          //perform diesel fuel logic here.
     }
}
28
ответ дан 28 November 2019 в 23:04
поделиться

Я согласен, что лучше всего (по крайней мере, не зная ничего больше о вашей проблеме) использовать std:: Последовательности . Но если вы настаиваете на управлении памятью самостоятельно, вы должны управлять ею полностью. Так что C++ путь:

class MyClass
{
private:
 const char *filename;

 MyClass(const MyClass&); // no implementation
 MyClass operator=(const MyClass &); // no implementation

public:
 MyClass() {filename = 0;}
 ~MyClass() {delete[] filename;}

 void func (const char *_filename);
}

void MyClass::func (const char *_filename)
{
 const size_t len = strlen(_filename);
 char * tmp_filename = new char[len + 1];
 strncpy(tmp_filename, _filename, len);
 tmp_filename[len] = '\0'; // I'm paranoid, maybe someone has changed something in _filename :-)
 delete[] filename;
 filename = tmp_filename;
}

и C путь

class MyClass
{
private:
 const char *filename;

 MyClass(const MyClass&); // no implementation
 MyClass operator=(const MyClass &); // no implementation

public:
 MyClass() {filename = 0;}
 ~MyClass() {free(filename);}

 void func (const char *_filename);
}

void MyClass::func (const char *_filename)
{
 free(filename);
 filename = strdup(_filename); // easier than C++, isn't it?
}
-121--2760435-

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

-121--622703-

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

1
ответ дан 28 November 2019 в 23:04
поделиться

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

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

Тогда ваш дизельный автомобиль будет просто конкретным автомобилем, Car .

12
ответ дан 28 November 2019 в 23:04
поделиться

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

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 */);

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

1
ответ дан 28 November 2019 в 23:04
поделиться

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

0
ответ дан 28 November 2019 в 23:04
поделиться

Я думаю, что приемлемым методом будет наличие в вашем базовом классе метода 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
    }
}
0
ответ дан 28 November 2019 в 23:04
поделиться

В системе, подобной 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"

Что, на самом деле, является версией двойной диспетчеризации Common Lisp, как упоминалось в stefaanv .

0
ответ дан 28 November 2019 в 23:04
поделиться

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

interface Car {
    drive();
}

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

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

}

0
ответ дан 28 November 2019 в 23:04
поделиться