Wyzwanie Java #5: Rozwiązanie

Radosław Szmit 18 maja 2018
 

Pora na rozwiązanie kolejnego wyzwania z drugiej części programowania obiektowego. Tym razem mieliśmy zaprojektować klasy do sysPora na rozwiązanie kolejnego wyzwania z drugiej części programowania obiektowego. Tym razem mieliśmy zaprojektować klasy do systemu wypożyczalni pojazdów.

To zadanie można rozwiązać na wiele sposobów, wszystko zależy od wymagań systemu i pomysłu programisty, dlatego bez obaw jeśli wasze klasy są kompletnie inne niż będą poniżej.

Zaczynamy od modelowania pojazdów. Zakładamy, że mamy samochody osobowe, dostawcze, motocykle i maszyny robocze. Każdy z nich jest pojazdem silnikowym i zakładamy, że nie będzie innych, uproszczając tym model. W takim razie stworzymy cztery klasy odpowiadające tym pojazdom i abstrakcyjną klasę pojazdu Vehicle (pomijamy podział na pliki *.java):

package pl.kodolamacz.vehiclerental.vehicle;

public class Vehicle {

}

public class Car extends Vehicle {
    
}

public class Motorbike extends Vehicle {

}

public class Van extends Vehicle {

}

public class WorkingMachine extends Vehicle {

}

Każdy pojazd ma swój numer rejestracyjny, numer vin, kolor, cenę, spalanie, stan zbiornika paliwa oraz licznik przejechanych kilometrów, dlatego nasza klasa Vehicle powinna zyskać następujące pola:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.Engine;

public class Vehicle {

    private String registrationNumber;
    private String vin;temu wypożyczalni pojazdów.

To zadanie można rozwiązać na wiele sposobów, wszystko zależy od wymagań systemu i pomysłu programisty, dlatego bez obaw jeśli wasze klasy są kompletnie inne niż będą poniżej.

Zaczynamy od modelowania pojazdów. Zakładamy, że mamy samochody osobowe, dostawcze, motocykle i maszyny robocze. Każdy z nich jest pojazdem silnikowym i zakładamy, że nie będzie innych, uproszczając tym model. W takim razie stworzymy cztery klasy odpowiadające tym pojazdom i abstrakcyjną klasę pojazdu *Vehicle* (pomijamy podział na pliki *.java):

~~~java
package pl.kodolamacz.vehiclerental.vehicle;

public class Vehicle {

}
public class Car extends Vehicle {
    
}

public class Motorbike extends Vehicle {

}

public class Van extends Vehicle {

}

public class WorkingMachine extends Vehicle {

}

Każdy pojazd ma swój numer rejestracyjny, numer vin, kolor, cenę, spalanie, stan zbiornika paliwa oraz licznik przejechanych kilometrów, dlatego nasza klasa Vehicle powinna zyskać następujące pola:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.Engine;

public class Vehicle {

    private String registrationNumber;
    private String vin;
    private String color;
    private double price;
    private double combustion;
    private double fuel;
    private double kilometers;

}

W zależności od typu, pojazdy mogą mieć dodatkowe cechy jak np. liczba drzwi w przypadku aut osobowych, której to cechy nie będą mieć motocykle. Dlatego dla przykładu klasa samochodu osobowego może wyglądać tak:

package pl.kodolamacz.vehiclerental.vehicle;

import pl.kodolamacz.vehiclerental.Vehicle;

public class Car extends Vehicle {

   private int numberOfDoors;

}

Pojazdy będą mieć silnik diesla, silnik na benzynę lub elektryczny. By spełnić ten wymóg stworzymy sobie interfejs silnika (dowolnego, to ma być po prostu idea silnika):

package pl.kodolamacz.vehiclerental.engine;

public interface Engine {

    default void drive(){
        // domyślna metoda
    }

}

Interfejsy mają swoje implementacje czyli klasy które spełniają ten interfejs. W naszym wymaganiach mamy trzy wersje silników, dlatego stworzymy trzy klasy:

package pl.kodolamacz.vehiclerental.engine;

public class PetrolEngine implements Engine {

}

public class DieselEngine implements Engine {

}

public class ElectricEngine implements Engine {

}

Gdy mamy już “silniki” możemy uwzględnić to w naszym kodzie dla pojazdu, przez co nasza klasa zmieni się do takiej postaci:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.Engine;

public class Vehicle {

    private String registrationNumber;
    private String vin;
    private String color;
    private double price;
    private double combustion;
    private double fuel;
    private double kilometers;
    private Engine engine;

}

Zauważcie ostatnie pole “engine” typu Engine, to właśnie nasz silnik w samochodzie. Ten sam samochód może mieć różne silniki, stąd tutaj mówimy że jest jakiś silnik, ale dopiero tworząc tą klasę “zainstalujemy” odpowiednią implementację!

Nasze “pojazdy” miały mieć metody jedź oraz tankuj, zmieniające pola licznika kilometrów oraz stanu zbiornika paliwa, dlatego dodajmy je:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.Engine;

public class Vehicle {

    private String registrationNumber;
    private String vin;
    private String color;
    private double price;
    private double combustion;
    private double fuel;
    private double kilometers;
    private Engine engine;

    public void drive(double kilometers) {
        engine.drive(); // jadąc używamy silnika (metoda jest pusta, tylko pokazowo)
        this.kilometers += kilometers; // jadąc zwiększa się licznik kilometrów
        fuel -= combustion * kilometers; // zmniejsza się ilość paliwa w zależności od spalania
    }

    public void refuel(double fuel) {
        this.fuel += fuel;
    }

}

Jeśli zostawimy parametry jako “private” nikt z zewnątrz nie będzie miał do nich dostępu zarówno do zapisu jak i odczytu. Dlatego należałoby je zrobić jako dostępne (np. publiczne lub pakietowe) albo stworzyć możliwość dostępu do nich za pomocą odpowiednich metod (hermetyzacja). By móc stworzyć taką klasę należałoby dodać wtedy odpowiedni konstruktor lub ewentualnie metody dostępowe, tak zwane “settery”, które pozwoliłyby na ustawianie tych wartości. Z drugiej strony by móc odczytać wartości tych pól trzeba by było stworzyć metody dostępowe zwane “getterami”. Więcej o tym jak powinny wyglądać takie metody dostępowe, zarówno do zapisu pola (setter) jak i odczytu jego wartości (getter), można przeczytać w dokumentacji, jest to standard związany z Javą Enterprise Edition który dodaje tylko jako ciekawostkę dla osób które są już bardziej zaawansowane, w dalszej części ograniczymy się tylko do konstruktora, gdyż nie to było celem zadania.

Dodajmy zatem konstruktor z wszystkimi parametrami. W idei jest to dość proste, wystarczy użyć skrótu Alt + Insert i z listy wybrać opcję Constructor a potem wybrać wszystkie pola i nacisnąć OK. Nasza klasa zacznie wyglądać tak:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.Engine;

public class Vehicle {

    private String registrationNumber;
    private String vin;
    private String color;
    private double price;
    private double combustion;
    private double fuel;
    private double kilometers;
    private Engine engine;

    public Vehicle() {
    }

    public Vehicle(String registrationNumber, String vin, String color, double price, double combustion, double fuel, double kilometers, Engine engine) {
        this.registrationNumber = registrationNumber;
        this.vin = vin;
        this.color = color;
        this.price = price;
        this.combustion = combustion;
        this.fuel = fuel;
        this.kilometers = kilometers;
        this.engine = engine;
    }

    public void drive(double kilometers) {
        engine.drive(); // jadąc używamy silnika (metoda jest pusta, tylko pokazowo)
        this.kilometers += kilometers; // jadąc zwiększa się licznik kilometrów
        fuel -= combustion * kilometers; // zmniejsza się ilość paliwa w zależności od spalania
    }

    public void refuel(double fuel) {
        this.fuel += fuel;
    }

}

Jeśli zostawimy parametry jako “private” nikt z zewnątrz nie będzie miał do nich dostępu zarówno do zapisu jak i odczytu. Dlatego należałoby je zrobić jako dostępne (np. publiczne lub pakietowe) albo stworzyć możliwość dostępu do nich za pomocą odpowiednich metod (hermetyzacja). By móc stworzyć taką klasę należałoby dodać wtedy odpowiedni konstruktor lub ewentualnie metody dostępowe, tak zwane “settery”, które pozwoliłyby na ustawianie tych wartości. Z drugiej strony by móc odczytać wartości tych pól trzeba by było stworzyć metody dostępowe zwane “getterami”. Więcej o tym jak powinny wyglądać takie metody dostępowe, zarówno do zapisu pola (setter) jak i odczytu jego wartości (getter), można przeczytać w dokumentacji, jest to standard związany z Javą Enterprise Edition który dodaje tylko jako ciekawostkę dla osób które są już bardziej zaawansowane, w dalszej części ograniczymy się tylko do konstruktora, gdyż nie to było celem zadania.

Dodajmy zatem konstruktor z wszystkimi parametrami. W idei jest to dość proste, wystarczy użyć skrótu Alt + Insert i z listy wybrać opcję Constructor a potem wybrać wszystkie pola i nacisnąć OK. Nasza klasa zacznie wyglądać tak:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.Engine;

public class Vehicle {

    private String registrationNumber;
    private String vin;
    private String color;
    private double price;
    private double combustion;
    private double fuel;
    private double kilometers;
    private Engine engine;

    public Vehicle() {
    }

    public Vehicle(String registrationNumber, String vin, String color, double price, double combustion, double fuel, double kilometers, Engine engine) {
        this.registrationNumber = registrationNumber;
        this.vin = vin;
        this.color = color;
        this.price = price;
        this.combustion = combustion;
        this.fuel = fuel;
        this.kilometers = kilometers;
        this.engine = engine;
    }
    
    public void drive(double kilometers) {
        engine.drive(); // jadąc używamy silnika (metoda jest pusta, tylko pokazowo)
        this.kilometers += kilometers; // jadąc zwiększa się licznik kilometrów
        fuel -= combustion * kilometers; // zmniejsza się ilość paliwa w zależności od spalania
    }

    public void refuel(double fuel) {
        this.fuel += fuel;
    }

}

Naszym pól w klasie jest całkiem sporo, przez co konstruktor wydaje się być skomplikowany, ale tak naprawdę jest bardzo prosty. W przyszłości moglibyśmy także pokusić się o zmniejszenie liczby tych parametrów korzystając z oddzielnych klas które je nam pogrupują lub kolekcji typu Map która przechowa je w jednym polu. Dodatkowo dodaliśmy konstruktor domyślny by nasz wcześniejszy kod się skompilował.

W klasie samochodu mieliśmy dodatkowe pole, dlatego tutaj też dodamy konstruktor plus użyjemy konstruktora z klasy Vehicle żeby ustawić pozostałe parametry:

package pl.kodolamacz.vehiclerental.vehicle;

import pl.kodolamacz.vehiclerental.Vehicle;
import pl.kodolamacz.vehiclerental.engine.Engine;

public class Car extends Vehicle {

   private int numberOfDoors;

    public Car(String registrationNumber, String vin, String color, double price, double combustion, double fuel, double kilometers, Engine engine, int numberOfDoors) {
        super(registrationNumber, vin, color, price, combustion, fuel, kilometers, engine); // tutaj konstruktor z klasy Vehicle, musi być jako pierwszy!
        this.numberOfDoors = numberOfDoors;
    }
    
}

Nasz program jest już prawie skończony, teraz wystarczy spróbować napisać kawałek kodu który użyje tych klas, na przykład tak:

package pl.kodolamacz.vehiclerental;

import pl.kodolamacz.vehiclerental.engine.PetrolEngine;
import pl.kodolamacz.vehiclerental.vehicle.Car;

public class VehicalRentalMain {

    public static void main(String[] args) {
        Car car = new Car("WZ 94567", "1M8GDM9A_KP042788", "Red", 40000, 6, 75, 0, new PetrolEngine(), 5);
        car.refuel(50); // tankujemy 50 litrów paliwa
        car.drive(100); // jedziemy 100 kilometrów
    }

}

Oczywiście program jest bardzo prosty, chodziło nam jedynie tutaj o próbę stworzenia odpowiedniego modelu klas a nie gotowego systemu wypożyczalni pojazdów. Modelowanie jest zagadnieniem tak naprawdę niezwykle ważnym a zarazem wcale nie takim prostym. Dlatego by dowiedzieć się więcej o prawidłowym tworzeniu klas i ich modelowaniu polecam poczytanie więcej o

Na rynku jest dostępna ogromna ilość książek poświęcona powyższym tematom.


Jeśli macie więcej pytań lub problemów, piszcie do nas śmiało na Facebooku, chętnie pomożemy. :) Do zobaczenia w poniedziałek na kolejnym wyzwaniu!


Wszystkie wspisy z serii #javowewyzwanie:

Wprowadzenie

Wyzwanie 1 - Hello world!

Wyzwanie 2 - Podstawowe instrukcje

Wyzwanie 3 - Programowanie obiektowe

Wyzwanie 4 - Algorytmy i struktury danych w języku Java

Wyzwanie 5 - Interfejsy i dziedziczenie

Wyzwanie 6 - Operacje wejścia - wyjścia

Wyzwanie 7 - Programowanie funkcyjne


Materiały dodatkowe do wyzwania:

Wprowadzenie do świata języka Java

Czego się uczyć by zostać programistą?

Java od środka, czyli jak to wszystko działa?

Wprowadzenie do Apache Maven, czyli jak tworzy się projekty w świecie Java

Wprowadzenie do testowania aplikacji w środowisku Java

6 książek, które powinien przeczytać każdy programista Java

Radosław Szmit

Opiekun bootcampu Full-stack w Kodołamacz.pl. Inżynier oprogramowania, specjalista Big Data, trener IT. Absolwent Politechniki Warszawskiej aktualnie pracujący nad rozprawą doktorską z zakresu Big Data i NLP. Twórca polskiej wyszukiwarki internetowej NEKST stworzonej przez Instytut Podstaw Informatyki Polskiej Akademii Nauk oraz Otwartego Systemu Antyplagiatowego realizowanego przez Międzyuniwersyteckie Centrum Informatyzacji. Zawodowo konsultant IT specjalizujący się w rozwiązaniach Java Enterprise Edition, Big Data oraz Business Intelligence, trener IT w firmie Sages.
Komentarze
Ostatnie posty
Data Science News #204
Data Science News #203
Data Science News #202
Data Science News #201