Освоюємо Java/Абстрактні класи

В деяких випадках немає необхідності реалізовувати усі методи класу. Наприклад, якщо відомо, що ці методи в дочірніх класах і так обов'язково будуть заміщатися, то немає сенсу їх реалізовувати в батьківському класі. Також такі класи корисні при груповій роботі над класами, коли один програміст визначає список методів і реалізує основні з них, а реалізацію інших методів покладено на інших програмістів, які можуть створити кілька версій реалізації абстрактного класу. Такі класи, в яких немає реалізації усіх методів називаються абстрактними класами.

При описі абстрактного класу використовується модифікатор abstract. Нереалізовані методи також позначаються як абстрактні методи з використанням модифікатора abstract.

Потомок класу повинен реалізувати усі методи класу, або ж також бути оголошеним абстрактним.

Екземпляр абстрактного класу не можливо створити. Тобто ми не можемо створити об'єкт з використанням ключового слова new.

Опис абстрактного класу ред.

Для того, щоб позначити клас абстрактним, модифікатор abstract вписується в рядку-заголовку класу. Аналогічно даний модифікатор пишеться при оголошенні абстрактного методу.

Розглянемо наступну задачу. Фірма виробник автомобілів, хоче мати програму, яка б розраховувала оптимальну мінімальну вартість автомобіля у конкретній країні. Виготовляються автомобілі у себе в країні і перевозяться у інші країни для продажу. При формуванні ціни, необхідно враховувати собівартість автомобіля, вартість транспортування та легалізація автомобіля в певній країні (мито, ПДВ, та інші збори). Тож звідси уже видно, що необхідно три методи. Розрахунок собівартості може зробити програміст в країні виробника. Інший програміст може розширити базовий клас та реалізувати метод, що обчислює вартість транспортування в кожній країні. І вже в країнах, куди автомобіль ввозиться, місцеві програмісти, що володіють знаннями про всі податки, збори та витрати, можуть зробити метод, що розраховує вартість "легалізації" автомобіля в певній країні.

На основі вищесказаного визначаємо наш базовий абстрактний клас із одним реалізованим методом і двома пустими (без тіла):

package ua.osvjava.classes;
/**
 * Абстрактний клас 
 * Список методів до реалізації
 */
public abstract class CarCost {
    
    /**
     * обчислення собівартості
     * @return - Собівартість автомобіля на заводі
     */  
     public double countPrimeCost() {
         //обчислення собівартості
         return 50000.0;
     }
    
    /**
     * Обчислення вартості перевезення
     * @param Country - країна 
     * @return - вартість перевезення
     */
    public abstract double countTransportationCosts(String Country); 

    /**
     * Обчислення вартості автомобіля в салонах продажів
     * @return - остаточна ціна автомобіля у певній країні
     */
    public abstract double countLocaleCost();                                       
}

В реальному прикладі, програміст змушений би був отримувати вихідні дані із бази даних і опрацьовувати їх. Ми не будемо ускладнювати і вдаватися у деталі. Наш метод countPrimeCost повертає 50000.0. Приймемо умовно, що це і є результат обрахунку собівартості. Перед заголовком методів описано призначення методу у вигляді javadoc коментаря. Якщо ви використовуєте інтегроване середовище розробки, то при зверненні до методу через оператор "." (крапка), буде спливати підказка із наведеними відомостями про метод та його параметри.

Розширення абстрактних класів ред.

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

Тож, обчислюємо транспортні витрати:

package ua.osvjava.classes;

public abstract class CarTransportationCosts extends CarCost {

    @Override
    public double countTransportationCosts(String country) {
        
        
        if (country.compareTo("Ukraine") == 0){
            //обчислення вартості транспортування в Україну
            return 3.000;
        }
        
        if (country.compareTo("England") == 0) {
            //обчислення вартості доставки в Англію
            return 2.000;
        }
        return -1.0;
    }

}

Тож і у вищенаведеному прикладі, клас CarTransportationCosts не реалізовує усі методи, тож він оголошений як abstract.

Нереалізованим залишився метод countLocaleCost. Він буде мати кілька реалізацій в залежності від країни. Реалізацію кожного класу можна доручити різним програмістам. Тож тепер необхідно розширити клас CarTransportationCosts, таким чином успадкуються як метод класу CarCost так і метод визначеного щойно класу CarTransportationCosts. Головне, щоб метод в кожній реалізації повертав те, що потрібно. Реалізація логіки всередині методу залежить від конкретних законів країни та програміста, що реалізовує метод.

package ua.osvjava.classes;
/**
 * Клас для обчислення остаточної ціни автомобіля в Англії
 * 
 * @author someEnglishMan
 */
public class EnglandPrice extends CarTransportationCosts {

public double countLocaleCost(){
    //складні обчислення із врахуванням податків, митних зборів та інших витрат
    double enLocaleCosts=15800.00;
    double price=super.countPrimeCost()+super.countTransportationCosts("Ukraine")+enLocaleCosts;
            
    return price;
    }
}

Інший клас для України:

package ua.osvjava.classes;
/**
 * Клас для обчислення остаточної ціни автомобіля в Україні
 * 
 * @author volodimirg 
 */
public  class UkrainePrice extends CarTransportationCosts {
    public double countLocaleCost(){
        //складні обчислення із врахуванням податків, митних зборів та інших витрат
        double someLocaleCosts=14000.00;
        double price=this.countPrimeCost()+this.countTransportationCosts("Ukraine")+someLocaleCosts;
        
        return price;
    }
}

Вищенаведені класи реалізували останній метод, що залишився не реалізований, тож оголошувати їх абстрактними немає потреби. Тепер, можна створити екземпляри класів UkrainePrice та EnglandPrice і обчислити ціни автомобілів в Україні та у Англії.

Використання реалізацій абстрактного класу ред.

package ua.osvjava.classes;

public class MainClass {
    UkrainePrice ukPrice = null;
    EnglandPrice enPrice = null;

    MainClass() {
        // ПОМИЛКИ! Екземпляри абстрактних класів неможливо створити
        // CarCost cs=new CarCost();
        // CarTransportationCosts pt=new CarTransportationCosts();

        // тож працюємо з неабстрактними класами, в яких усі методи
        // абстрактного класу CarCost реалізовані

        ukPrice = new UkrainePrice();
        enPrice = new EnglandPrice();
        System.out.println("Ціна автомобіля в Україні складає " 
                            + ukPrice.countLocaleCost());
        System.out.println("Ціна автомобіля в Англії складає " 
                             + enPrice.countLocaleCost());

    }

    public static void main(String[] args) {
        new MainClass();
    }
}

Результат виконання:

Ціна автомобіля в Україні складає 64003.0
Ціна автомобіля в Англії складає 65803.0
 
UML діаграма класів


Структуру нашого прикладення наведено на UML діаграмі. Абстрактні класи та методи в UML позначаються курсивом. Як бачимо тепер ми використовуємо кілька реалізацій нашого абстрактного класу. Їх може бути і десять, і більше. В залежності від країни ми створюємо екземпляр того класу, який нам потрібний. Крім того завдяки тому, що у всіх реалізаціях класу є одні і ті ж методи, будь-якому програмісту знайомому хоча б з однією реалізацією абстрактного класу, не складатиме труднощів використовувати інші класи даної ієрархії.

Звичайно, що проміжний клас CarTransportationCosts можна було б і не створювати, а зразу ж реалізувати метод в базовому класі CarCost або ж у UkrainePrice та EnglandPrice. Можна було б також здійснити успадкування класу UkrainePrice, безпосередньо від CarCost та реалізувати в UkrainPrice всі необхідні методи без посередництва класу CarTransportationCosts, а клас EnglandPrice реалізувати так як тут реалізовано в прикладі. Все залежить від того, який шлях реалізації абстрактних класів вибрали ті чи інші розробники.

В java існує чимало ієрархій класів, в основі яких лежать саме абстрактні класи.

Об'єктні змінні абстрактних класів ред.

Хоча не можливо створити екземляр абстрактного класу, проте об'єктну змінну можна оголосити і використовувати її для посилання на екземпляри класів нащадків.

У нашому прикладі, можна викликати методи таким чином:

    CarCost cc=new UkrainePrice();
    System.out.println("Ціна автомобіля в Україні складає " + cc.countLocaleCost());

Тут використовується основна властивість поліморфізму (батьківський клас може підміняти дочірній клас). Тож об'єктна змінна може посилатися на об'єкти нащадків. Хоч методи і не реалізовані, проте оголошені в абстрактному класі. Відповідно, через об'єктну змінну абстрактного класу ми можемо використовувати реалізації методів (по суті вступає в дію заміщення методів).

Зробимо нашу програму дещо більш інтерактивною із використанням об'єктної змінної типу CarCost. Дозволимо користувачу самому ввести назву країни. Далі програма виведе ціну автомобіля в країні, яка його цікавить.

package ua.osvjava.classes;

import java.io.Console;
import java.util.Scanner;

public class MainClass2 {
    
    Scanner in;
    CarCost cc;
    
    public MainClass2() {
        
        in = new Scanner(System.in);
        System.out.print("Введіть назву країни: ");
        String str=in.nextLine(); // читання рядка тексту з клавіатури
        
        //В залежності від країни створюється відповідний об'єкт,
        //якщо неправильна назва, вивести повідомлення про це і завершити виконання методу
        if (str.equalsIgnoreCase("Ukraine")||str.equalsIgnoreCase("Україна")){
            cc=new UkrainePrice();
        }
        else if (str.equalsIgnoreCase("England")||str.equalsIgnoreCase("Англія")){
            cc=new EnglandPrice();
        }else {
            System.out.println("Інформація про введену країну відсутня!");
            return; 
        }
        System.out.println("Ціна автомобіля ("+str+"): " + cc.countLocaleCost());
    }

    public static void main(String[] args) {
        MainClass2 class1 = new MainClass2();
    } 
}

Результат виконання:

Введіть назву країни: Україна
Ціна автомобіля (Україна): 64003.0

Ви можете допрацювати програму, наприклад, щоб вона надавала інформацію про ціну зазначеної користувачем марки авто у зазначеній користувачем країні.

Примітки ред.

Об'єкти і класи · Вкладені, внутрішні класи