Что является хорошим дизайном в этом простом случае:
Скажем, у меня есть Автомобиль базового класса с методом FillTank(Fuel fuel)
где топливо является также базовым классом, которые имеют несколько листовых классов, дизеля, этанол и т.д.
На моем листовом автомобильном классе DieselCar.FillTank(Fuel fuel)
только определенный тип топлива позволяется (никакие неожиданности там:)). Теперь вот мое беспокойство, согласно моему интерфейсу, каждый автомобиль может быть заправлен с любым топливом, но это кажется неправильным мне в каждом FillTank()
реализация проверяет входное топливо на корректный тип и если не бросают ошибку или что-то.
Как я могу перепроектировать такой случай к более точному, это даже возможно? Как разработать базовый метод, который берет базовый класс для входа, не получая эти "странные результаты"?
Если существует жесткая граница между типами автомобилей и видами топлива, то 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 ()
.
Используйте общий базовый класс (если ваш язык поддерживает его (ниже - 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.
}
}
Я согласен, что лучше всего (по крайней мере, не зная ничего больше о вашей проблеме) использовать 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-Для этого можно использовать двойную отправку : принять некоторое количество топлива перед наполнять. Помните, что в языке, который не поддерживает его напрямую, вы вводите зависимости
Само по себе объектно-ориентированное программирование не может решить эту проблему. Что вам нужно, так это универсальное программирование (здесь показано решение C ++):
template <class FuelType>
class Car
{
public:
void FillTank(FuelType fuel);
};
Тогда ваш дизельный автомобиль будет просто конкретным автомобилем, Car
.
Похоже, что вы просто хотите ограничить тип топлива, которое поступает в ваш дизельный автомобиль. Что-то вроде:
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
топлива.
используйте оператор is
для проверки допустимых классов, и вы можете создать исключение в конструкторе
Я думаю, что приемлемым методом будет наличие в вашем базовом классе метода 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
}
}
В системе, подобной 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 .
вы можете расширить исходный интерфейс автомобиля
interface Car {
drive();
}
interface DieselCar extends Car {
fillTank(Diesel fuel);
}
interface SolarCar extends Car {
chargeBattery(Sun fuel);
}
}