ООП дизайн проблема - PullRequest
       29

ООП дизайн проблема

24 голосов
/ 18 февраля 2010

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

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

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

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

Ответы [ 9 ]

28 голосов
/ 18 февраля 2010

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

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.
     }
}
12 голосов
/ 18 февраля 2010

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

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

Тогда ваша дизельная машина - это просто конкретная машина, Car<Diesel>.

11 голосов
/ 18 февраля 2010

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

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

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

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

1 голос
/ 18 февраля 2010

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

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.

1 голос
/ 18 февраля 2010

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

0 голосов
/ 18 февраля 2010

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

interface Car {
    drive();
}

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

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

}

0 голосов
/ 18 февраля 2010

В 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 голосов
/ 18 февраля 2010

Я думаю, что принятый метод будет иметь метод 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 голосов
/ 18 февраля 2010

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

...