Programowanie średniozaawansowane #1: klasa abstrakcyjna

Znasz już podstawy programowania strukturalnego oraz podstawowe algorytmy. Teraz czas przejść do bardziej skomplikowanych paradygmatów programowania obiektowego. Abstrakcja jest jednym z paradygmatów programowania obiektowego. Można ją wytłumaczyć jak stosowanie równań matematycznych w fizyce. Matematyczne obliczenia to pewnego rodzaju abstrakcja, która później przy podstawieniu odpowiednich wartości wylicza odpowiednie właściwości, które obserwujesz w przyrodzie. Tak samo jest z klasami abstrakcyjnymi. Są to zwykłe klasy (posiadają prawie wszystkie ich własności), ale umożliwiają też pisanie sygnatur metod, które później będziesz musieć zaimplementować w klasie potomnej. Spójrz na przykład poniżej.

public abstract class Player {
    public abstract void play();
 
    public abstract void score();
 
    public void run() {
        System.out.println("Running..");
    }
}

W tej mało skomplikowanej klasie użyto słowa kluczowego abstract. Oznacza ono, że klasa może posiadać metody, które nie są w niej zdefiniowane. Zauważ, że z trzech metod, jedna z nich to typowa metoda, która nie zwraca żadnej wartości, ale dwie kolejne nie posiadają treści (mówi się wtedy o sygnaturze metody). Możesz to sobie wytłumaczyć początkowym przykładem z matematyką używaną w fizyce. Pamiętasz na pewno słynny wzór Einsteina E=mc2. Jeśli nie pamiętasz, podpowiem Ci, że jest to wzór na energię, którą można wyrazić jako iloczyn masy i prędkości światła do kwadratu. Odnosząc go do mojego przykładu E to taka nasza sygnatura. Natomiast mc2 to jego jedna implementacja. Wzorów na energię w fizyce masz dużo więcej (np. na energię kinetyczną).

public class FootballPlayer extends Player{

  @Override
  public void play() {
    System.out.println("Playing football..");
  }

  @Override
  public void score() {
    System.out.println("Scoring goals..");
  }
}

W kolejnym kroku użyłem klasy Player do rozszerzenia klasy FootballPlayer. W praktyce odziedziczy ona metodę running(), a także jest zmuszona do posiada implementacji metod, których sygnatury również zostały zdeklarowane w Player. W tym przypadku obiekt klasy FootballPlayer otrzyma unikatowe względem zwykłego obiektu klasy Player właściwości grania w piłkę i zdobywa bramki (chyba to dość jasne, ze nie każdy gracz będzie posiadał taką umiejętność). Dla porównania zobacz, na implementację poniżej.

public class BasketballPlayer extends Player{

  @Override
  public void play() {
    System.out.println("Playing basketball..");
  }

  @Override
  public void score() {
    System.out.println("Scoring points..");
  }
  
  public void layUp() {
    System.out.println("Scoring points from lay-up..");		
  }
}

Widzisz tu klasę BasketballPlayer, która rozszerzając klasę Player obowiązkowo posiada swoje implementacje metod play() i score(), ale ponad to ma dodatkową metodę layUp() (dwutakt)Pomimo, że koszykarz posiada funkcje gracza, to nic nie stoi na przeszkodzie, aby dodać mu umiejętności tylko charakterystyczne dla niego.

Po co w takim razie są klasy abstrakcyjne? Świetnie sprawdzają się jako klasy ‘korzenie’ w drzewie dziedziczenia, ponieważ wymuszają na programiście, który je użyje, do napisania własnych metod, które powinny być charakterystyczne dla klasy danego typu. Programista planujący system w tym przypadku dostarcza kolejnemu programiście zestaw gotowych narzędzi, ale w zależności do czego one będą służyć, pozostawia mu otwartą furtkę do uszczegółowienia zachowania nowej klasy.

Napisz teraz swoją metodę main, aby zobaczyć efekt pracy koszykarza i piłkarza.

public class MainPlayer {

  public static void main(String[] args) {
    System.out.println("Football player.");
    FootballPlayer footballPlayer = new FootballPlayer();
    footballPlayer.run();
    footballPlayer.play();
    footballPlayer.score();
    System.out.println("Basketball player.");
    BasketballPlayer basketballPlayer = new BasketballPlayer();
    basketballPlayer.run();
    basketballPlayer.play();
    basketballPlayer.score();
    basketballPlayer.layUp();
  }
}

Teraz spójrz na efekt wywołania klas FootballPlayer i BasketballPlayer w konsoli.

Football player.
Running..
Playing football..
Scoring goals..
Basketball player.
Running..
Playing basketball..
Scoring points..
Scoring points from lay-up..

Obie klasy zachowały metodę run() w pierwotnym stanie, jak przypadku normalnego dziedziczenia. Obie klasy posiadały swoje implementacje metod score() i play(). Klasa BasketballPlayer dodatkowo posiadała swoją klasę layUp().

Zadasz teraz pytanie, czemu nie przetestowałem klasę Player. Otóż ponieważ Player jest klasą abstrakcyjną, nie możesz stworzyć jej instancji (czyli Player player = new Player(); nie zadziała). Słowem abstract przed nazwą klasy sugerujesz, że to ma być klasa rodzic, która będzie później rozszerzana przez potomków i nie nadaje się ona do samoistnego funkcjonowania. Kolejne pytanie, które możesz zadać jest, czy może istnieć klasa abstrakcyjna bez metod abstrakcyjnych. Otóż może, po prostu w ten sposób blokujesz kolejnemu programiście użycie jej bezpośrednio w postaci jej instancji w innej metodzie (możesz jej użyć tylko przez dziedziczenie). Ostatnie pytanie, które warto zadać, to czym jest adnotacja @Override? Na to pytanie, odpowiem Ci, w kolejnym poście przy okazji lekcji związanej z polimorfizmem. 🙂

Dodaj komentarz