У меня действительно есть машина в моем гараже?

Я новичок в программировании на Java, пытаясь повесить ООП.

Итак, я построил этот абстрактный класс:

public abstract class Vehicle{....}

и 2 подкласса:

public class Car extends Vehicle{....}
public class Boat extends Vehicle{....}

Car и Boat также содержат некоторые уникальные поля и методы, которые не являются общими (не имеют одинакового имени, поэтому я не могу определить абстрактный метод для них в Vehicle).

Теперь в mainClass я установил свой новый гараж:

Vehicle[] myGarage= new Vehicle[10];
myGarage[0]=new Car(2,true);
myGarage[1]=new Boat(4,600);

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

boolean carIsAutomatic = myGarage[0].auto;

Компилятор этого не принимает. Я работал над этой проблемой, используя кастинг:

boolean carIsAutomatic = ((Car)myGarage[0]).auto;

Это работает... но это не помогает с помощью методов, просто полей. Значение Я не могу сделать

(Car)myGarage[0].doSomeCarStuff();

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


для будущих читателей, краткое изложение ответов ниже:

  • Да, там Car в myGarage[]
  • Являясь статическим типизированным языком, компилятор Java не будет предоставлять доступ к методам/полям, которые не являются "Транспортными средствами", при доступе к ним через структуру данных на основе суперкласса Vehicle (например, Vehicle myGarage[])
  • Что касается того, как решить, есть два основных подхода ниже:
    • Используйте литье типов типов, которое облегчит проблемы компилятора и оставит любые ошибки в дизайне для запуска.
    • Тот факт, что мне нужно кастинг, говорит о том, что дизайн ошибочен. Если мне нужен доступ к возможностям, не связанным с транспортным средством, тогда я не должен хранить автомобили и лодки в структуре данных на базе транспортного средства. Либо сделайте все эти возможности принадлежащими Vehicle, либо используйте более конкретные структуры (основанные на типе).
  • Во многих случаях состав и/или интерфейсы были бы лучшей альтернативой наследованию. Возможно, тема моего следующего вопроса...
  • Плюс много других хороших идей там, если у вас есть время, чтобы просмотреть ответы.

Ответ 1

Если вам нужно сделать разницу между Car и Boat в вашем гараже, тогда вы должны хранить их в разных структурах.

Например:

public class Garage {
    private List<Car> cars;
    private List<Boat> boats;
}

Затем вы можете определить методы, специфичные для лодок или специфические для автомобилей.

Почему полиморфизм тогда?

Скажем, Vehicle имеет вид:

public abstract class Vehicle {
   protected int price;
   public getPrice() { return price; }
   public abstract int getPriceAfterYears(int years);
}

Каждая Vehicle имеет цену, поэтому ее можно поместить внутри абстрактного класса Vehicle.

Тем не менее, формула, определяющая цену после n лет, зависит от транспортного средства, поэтому она оставила для класса реализации определить ее. Например:

public Car extends Vehicle {
    // car specific
    private boolean automatic;
    @Override
    public getPriceAfterYears(int years) {
        // losing 1000$ every year
        return Math.max(0, this.price - (years * 1000));  
    }
}

Класс Boat может иметь другое определение для getPriceAfterYears и конкретные атрибуты и методы.

Итак, теперь в классе Garage вы можете определить:

// car specific
public int numberOfAutomaticCars() {
    int s = 0;
    for(Car car : cars) {
        if(car.isAutomatic()) {
            s++;
        }
    }
    return s;
}
public List<Vehicle> getVehicles() {
    List<Vehicle> v = new ArrayList<>(); // init with sum
    v.addAll(cars);
    v.addAll(boats);
    return v;
}
// all vehicles method
public getAveragePriceAfterYears(int years) {
    List<Vehicle> vehicules = getVehicles();
    int s = 0;
    for(Vehicle v : vehicules) {
        // call the implementation of the actual type!
        s += v.getPriceAfterYears(years);  
    }
    return s / vehicules.size();
}

Интерес полиморфизма заключается в том, чтобы вызвать getPriceAfterYears на Vehicle без, заботясь о реализации.

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

Примечание. Конечно, дизайн здесь можно легко улучшить. Это просто пример, демонстрирующий точки.

Ответ 2

Чтобы ответить на ваш вопрос, вы можете узнать, что именно находится в вашем гараже, вы делаете следующее:

Vehicle v = myGarage[0];

if (v instanceof Car) {
   // This vehicle is a car
   ((Car)v).doSomeCarStuff();
} else if(v instanceof Boat){
   // This vehicle is a boat
   ((Boat)v).doSomeBoatStuff();
}

ОБНОВЛЕНИЕ. Как вы можете прочитать из комментариев ниже, этот метод подходит для простых решений, но это не очень хорошая практика, особенно если у вас в вашем гараже огромное количество автомобилей. Так что используйте его, только если вы знаете, что гараж останется маленьким. Если это не так, выполните поиск "Избегайте instanceof" при переполнении стека, есть несколько способов сделать это.

Ответ 3

Если вы работаете с базовым типом, вы можете получить доступ только к общедоступным методам и его полям.

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

Car car = (Car)myGarage[0];
car.doSomeCarStuff();

Или короче без временного поля:

((Car)myGarage[0]).doSomeCarStuff();

Поскольку вы используете объекты Vehicle, вы можете вызывать только методы из базового класса без них. Поэтому для вашего гаража может быть целесообразно отличать объекты в разных массивах - или в лучших списках - массив часто не является хорошей идеей, так как он менее гибкий в обработке, чем класс Collection.

Ответ 4

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

Чтобы ответить на комментарий:

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

Один пример использования абстрактного класса:

    abstract class Vehicle {

    protected abstract void identifyWhereIAm();
    protected abstract void startEngine();
    protected abstract void driveUntilIArriveHome();
    protected abstract void stopEngine();

    public void navigateToHome() {
        identifyWhereIAm();
        startEngine();
        driveUntilIArriveHome();
        stopEngine();
    } 
}

Вы будете использовать те же самые шаги для каждого автомобиля, но выполнение шагов будет отличаться по типу транспортного средства. Автомобиль может использовать GPS, лодка может использовать сонар, чтобы определить, где он находится.

Ответ 5

Я новичок в программировании на Java, пытаясь повесить ООП.

Только мои 2 цента - я постараюсь сделать так, как уже было сказано много интересного. Но на самом деле здесь есть два вопроса. Один о "ООП" и один о том, как он реализован на Java.

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

Вы можете объяснить компилятору, что "вы действительно знаете, что это Vehicle является Car", используя явный листинг (Car). компилятор поверит вам, даже если в Java есть проверка во время выполнения, что может привести к ClassCastException, которые предотвращают дальнейшие убытки, если вы лжете (другой язык, такой как С++, не будет проверять при запуске, время - вы должны знать, что вы делаете)

Наконец, если вам действительно нужно, вы можете положиться на идентификацию типа времени выполнения (т.е. instanceof), чтобы проверить "реальный" тип объекта перед тем, как его выполнить. Но это в основном считается плохой практикой в ​​Java.

Как я уже сказал, это Java-способ реализации ООП. Существует целый ряд языков class, широко известных как "динамические языки", которые проверяются только во время выполнения, если операция разрешена на объекте или нет. С этими языками вам не нужно "продвигать" все распространенные методы к некоторому (возможно абстрактному) базовому классу, чтобы удовлетворить систему типов. Это называется утиная печать.

Ответ 6

Вы спросили своего дворецкого:

Дживс, помни мой гараж на острове Ява? Пойдите, проверьте, что первый автомобиль, припаркованный там, автоматический.

и ленивый Дживс сказал:

но сэр, что, если это транспортное средство, которое не может быть автоматическим или неавтоматическим?

Что все.

Хорошо, что на самом деле не все, так как реальность более утиная, чем статически типизированная. Вот почему я сказал, что Дживс ленив.

Ответ 7

Ваша проблема здесь на более фундаментальном уровне: вы построили Vehicle таким образом, чтобы Garage нужно было знать больше о своих объектах, чем отдает интерфейс Vehicle. Вы должны попытаться построить класс Vehicle с точки зрения Garage (и вообще с точки зрения всего, что будет использовать Vehicle): какие вещи они должны делать с их транспортными средствами? Как я могу сделать это с помощью моих методов?

Например, из вашего примера:

bool carIsAutomatic = myGarage[0].auto;

Ваш гараж хочет знать о двигателе автомобиля для... причин? Во всяком случае, нет необходимости в том, чтобы это было просто открыто Car. Вы по-прежнему можете выставить нереализованный метод isAutomatic() в Vehicle, а затем реализовать его как return True в Boat и return this.auto в Car.

Было бы еще лучше иметь трехзначное перечисление EngineType (HAS_NO_GEARS, HAS_GEARS_AUTO_SHIFT, HAS_GEARS_MANUAL_SHIFT), что позволило бы вашему коду обосновать фактические характеристики родового Vehicle чисто и точно. (В любом случае, вам понадобится это различие для обработки мотоциклов.)

Ответ 8

В вашем гараже есть Транспортные средства, поэтому в представлении статического управления компилятора у вас есть Автомобиль, а как .auto - поле Car, к которому вы не можете получить доступ, динамически это автомобиль, поэтому приведение не создает проблем, если это будет Лодка, и вы попытаетесь сделать бросок на Автомобиль, создаст исключение во время выполнения.

Ответ 9

Это хорошее место для применения шаблона проектирования Visitor.

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

Это работает, создавая объект Visitor и позволяя нашему Vehicle классу accept() посетителю.

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

Демонстрация, например:

public class VisitorDemo {

    // We'll use this to mark a class visitable.
    public static interface Visitable {

        void accept(Visitor visitor);
    }

    // This is the visitor
    public static interface Visitor {

        void visit(Boat boat);

        void visit(Car car);

    }

    // Abstract
    public static abstract class Vehicle implements Visitable {

            // NO OTHER RANDOM ABSTRACT METHODS!

    }

    // Concrete
    public static class Car extends Vehicle {

        public void doCarStuff() {
            System.out.println("Doing car stuff");
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }

    }

    // Concrete
    public static class Boat extends Vehicle {

        public void doBoatStuff() {
            System.out.println("Doing boat stuff");
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }

    }

    // Concrete visitor
    public static class StuffVisitor implements Visitor {

        @Override
        public void visit(Boat boat) {
            boat.doBoatStuff();
        }

        @Override
        public void visit(Car car) {
            car.doCarStuff();
        }
    }

    public static void main(String[] args) {
        // Create our garage
        Vehicle[] garage = {
            new Boat(),
            new Car(),
            new Car(),
            new Boat(),
            new Car()
        };

        // Create our visitor
        Visitor visitor = new StuffVisitor();

        // Visit each item in our garage in turn
        for (Vehicle v : garage) {
            v.accept(visitor);
        }
    }

}

Как вы можете видеть, StuffVisitor позволяет вам вызывать другой код на Boat или Car в зависимости от того, какая реализация visit вызывается. Вы также можете создавать другие реализации Visitor для вызова другого кода с тем же шаблоном .visit().

Также обратите внимание, что с помощью этого метода нет необходимости использовать instanceof или любую проверку класса hacky. Единственным дублированным кодом между классами является метод void accept(Visitor).

Если вы хотите, например, поддерживать 3 типа конкретных подклассов, вы можете просто добавить эту реализацию в интерфейс Visitor.

Ответ 10

Я просто собираю идеи других здесь (и я не парень Java, поэтому это скорее псевдо, чем фактический), но в этом надуманном примере я бы отвлек мой подход к проверке автомобиля в специальном класс, который знает только о машинах и только заботится о машинах при взгляде на гаражи:

abstract class Vehicle { 
    public abstract string getDescription() ;
}

class Transmission {
    public Transmission(bool isAutomatic) {
        this.isAutomatic = isAutomatic;
    }
    private bool isAutomatic;
    public bool getIsAutomatic() { return isAutomatic; }
}

class Car extends Vehicle {
    @Override
    public string getDescription() { 
        return "a car";
    }

    private Transmission transmission;

    public Transmission getTransmission() {
        return transmission;
    }
}

class Boat extends Vehicle {
    @Override
    public string getDescription() {
        return "a boat";
    }
}

public enum InspectionBoolean {
    FALSE, TRUE, UNSUPPORTED
}

public class CarInspector {
    public bool isCar(Vehicle v) {
        return (v instanceof Car);
    }
    public bool isAutomatic(Car car) {
        Transmission t = car.getTransmission();
        return t.getIsAutomatic();
    }
    public bool isAutomatic(Vehicle vehicle) {
        if (!isCar(vehicle)) throw new UnsupportedVehicleException();
        return isAutomatic((Car)vehicle);
    }
    public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
        if (!isCar(garage[bay])) return InspectionBoolean.UNSUPPORTED;
        return isAutomatic(garage[bay]) 
             ? InspectionBoolean.TRUE
             : InspectionBoolean.FALSE;
    }
}

Точка, вы уже решили, что только заботитесь о машинах, когда спрашиваете о передаче автомобиля. Так что просто спросите CarInspector. Благодаря tri-state Enum вы можете теперь знать, является ли он автоматическим или даже если это не автомобиль.

Конечно, вам понадобятся разные VehicleInspectors для каждого интересующего вас автомобиля. И вы только что подтолкнули проблему, с которой VehicleInspector должен создать цепочку.

Поэтому вместо этого вы можете посмотреть интерфейсы.

Абстрактный getTransmission выход в интерфейс (например, HasTransmission). Таким образом, вы можете проверить, есть ли у транспортного средства трансмиссия, или написать трансмиссионный инспектор:

abstract class Vehicle { }

class Transmission {
    public Transmission(bool isAutomatic) {
        this.isAutomatic = isAutomatic;
    }
    private bool isAutomatic;
    public bool getIsAutomatic() { return isAutomatic; }
}

interface HasTransmission { 
    Transmission getTransmission(); 
}

class Car extends Vehicle, HasTransmission {
    private Transmission transmission;

    @Override
    public Transmission getTransmission() {
        return transmission;
    }
}

class Bus extends Vehicle, HasTransmission {
    private Transmission transmission;

    @Override
    public Transmission getTransmission() {
        return transmission;
    }
}

class Boat extends Vehicle { }

enum InspectionBoolean {
    FALSE, TRUE, UNSUPPORTED
}

class TransmissionInspector {
    public bool hasTransmission(Vehicle v) {
        return (v instanceof HasTransmission);
    }
    public bool isAutomatic(HasTransmission h) {
        Transmission t = h.getTransmission();
        return t.getIsAutomatic();
    }
    public bool isAutomatic(Vehicle v) {
        if (!hasTranmission(v)) throw new UnsupportedVehicleException();
        return isAutomatic((HasTransmission)v);
    }
    public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
        if (!hasTranmission(garage[bay])) return InspectionBoolean.UNSUPPORTED;
        return isAutomatic(garage[bay]) 
             ? InspectionBoolean.TRUE
             : InspectionBoolean.FALSE;
    }
}

Теперь вы говорите, что вы только о передаче, независимо от транспортного средства, поэтому можете спросить у TransmissionInspector. Как автобус, так и автомобиль могут быть осмотрены с помощью TransferInspector, но он может только спросить о передаче.

Теперь вы можете решить, что логические значения не все, о чем вы заботитесь. В этот момент вы можете использовать общий поддерживаемый тип, который предоставляет как поддерживаемое состояние, так и значение:

class Supported<T> {
    private bool supported = false;
    private T value;

    public Supported() { }
    public Supported(T value) { 
        this.isSupported = true;
        this.value = value; 
    }

    public bool isSupported() { return supported; }
    public T getValue() { 
        if (!supported) throw new NotSupportedException();
        return value;
    }
}

Теперь ваш инспектор может быть определен как:

class TransmissionInspector {
    public Supported<bool> isAutomatic(Vehicle[] garage, int bay) {
        if (!hasTranmission(garage[bay])) return new Supported<bool>();
        return new Supported<bool>(isAutomatic(garage[bay]));
    }

    public Supported<int> getGearCount(Vehicle[] garage, int bay) {
        if (!hasTranmission(garage[bay])) return new Supported<int>();
        return new Supported<int>(getGearCount(garage[bay]));
    }
}

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

Ответ 11

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

Ответ 12

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

public abstract class Vehicle {
    public final boolean isCar;
    public final boolean isBoat;

    public Vehicle (boolean isCar, boolean isBoat) {
        this.isCar  = isCar;
        this.isBoat = isBoat;
    }
}

Установите поля уровня транспортного средства в классе наследования до соответствующего значения.

public class Car extends Vehicle {
    public Car (...) {
        super(true, false);
        ...
    }
}

public class Boat extends Vehicle {
    public Boat (...) {
        super(false, true);
        ...
    }
}

Внесите, используя поля уровня транспортного средства, чтобы правильно расшифровать тип автомобиля.

boolean carIsAutomatic = false;

if (myGarage[0].isCar) {
    Car car = (Car) myGarage[0];
    car.carMethod();
    carIsAutomatic = car.auto;
}

else if (myGarage[0].isBoat) {
    Boat boat = (Boat) myGarage[0];
    boat.boatMethod();
}

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

Java - это тип безопасного языка, поэтому лучше всего всегда проверять данные перед обработкой данных, которые были выбраны как ваши Boat и Car s.

Ответ 13

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

Я понимаю, что правильное моделирование класса для представления гаража не поможет ответить на вопрос "автомобили в гараже"; просто совет.

Вернитесь к коду. Помимо того, что некоторые из них зависают от ООП, несколько вопросов были бы полезными для создания сцены, чтобы лучше понять проблему, которую вы хотите решить (при условии, что есть один, а не просто "получить зависание" ):

  • Кто или что хочет понять carIsAutomatic?
  • Учитывая carIsAutomatic, кто или что выполнил бы doSomeCarStuff?

Это может быть какой-то инспектор или кто-то, кто знает только, как управлять автомобилями автоматической трансмиссии и т.д., но с точки зрения гаража, все, что он знает, это какое-то транспортное средство, поэтому (в этой модели) это ответственность этого инспектора или водителя сообщить, если это автомобиль или лодка; в этот момент вы можете захотеть создать еще одну группу классов, чтобы представить похожие типы * actor * s в сцене. Зависит от проблемы, которую нужно решить, если вам действительно нужно, вы можете моделировать гараж, чтобы быть супер-умной системой, поэтому он ведет себя как торговый автомат вместо обычного гаража, на котором есть кнопка "Автомобиль", а другая говорит "Лодка", чтобы люди могли нажимать кнопку, чтобы получить автомобиль или лодку по своему усмотрению, что, в свою очередь, делает этот супермагистральный гараж ответственным за то, чтобы сообщить, что (автомобиль или лодка) должны быть представлены его пользователям; чтобы следовать этой импровизации, гараж может потребовать некоторого учета, когда он принимает транспортное средство, кто-то может предоставить информацию и т.д., все эти обязанности выходят за рамки простого основного класса.

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