Зарегистрируйтесь, чтобы продолжить обучение

Полиморфизм Java: Основы ООП

Проблема

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

Одним из примеров такой проблемы является обработка различных типов фигур в геометрических расчетах

Создадим два класса, представляющие геометрические фигуры:

public class Rectangle {
    private int sideA;
    private int sideB;

    public Rectangle(int sideA, int sideB) {
        this.sideA = sideA;
        this.sideB = sideB;
    }

    public double getArea() {
        System.out.println("get Rectangle area");
        return sideA * sideB;
    }
}
public class Square {

    private int side;

    public Square(int side) {
        this.side = side;
    }

    public double getArea() {
        System.out.println("get square area");
        return side * side;
    }
}

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

public class Example1 {
    public static void main(String[] args) {
        List < Square > squares = List.of(
            new Square(2),
            new Square(3)
        );
        List < Rectangle > rectangles = List.of(
            new Rectangle(1, 2),
            new Rectangle(1, 3)
        );

        System.out.println(getTotalArea(squares, rectangles)); //=> 18
    }

    public static double getTotalArea(List<Square> squares, List<Rectangle> rectangles) {
        double result = 0;
        for (Square square : squares) {
            result += square.getArea();
        }
        for (Rectangle rectangle : rectangles) {
            result += rectangle.getArea();
        }

        return result;
    }
}

Проблема этого кода заключается в том, что его сложно масштабировать. Если нам нужно добавить новый тип фигуры, например, круг, то нам придется изменить метод getTotalArea()

Сделаем это, добавим еще одну фигуру — круг

public class Circle {

    private int r;

    public Circle(int r) {
        this.r = r;
    }
    public double getArea() {
        return Math.PI * r * r;
    }
}

Расширим метод так, чтобы он мог работать еще и с кругами

public class Example1 {
    public static void main(String[] args) {
        List < Square > squares = List.of(
            new Square(2),
            new Square(3)
        );
        List < Rectangle > rectangles = List.of(
            new Rectangle(1, 2),
            new Rectangle(1, 3)
        );

        List < Rectangle > rectangles = List.of(
            new Circle(1)
        );

        System.out.println(getTotalArea(squares, rectangles, circles)); //=> 21.1415...
    }

    public static double getTotalArea(
        List<Square> squares,
        List<Rectangle> rectangles,
        List<Circles> circles
    ) {
        double result = 0;
        for (Square square : squares) {
            result += square.getArea();
        }
        for (Rectangle rectangle : rectangles) {
            result += rectangle.getArea();
        }
        for (Circle circle : circles) {
            result += circle.getArea();
        }

        return result;
    }
}

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

Решение

Сделаем интерфейс, который будут реализовывать геометрические фигуры

public interface Shape {
    double getArea();
}

Откорректируем классы, чтобы они реализовывали этот интерфейс

public class Rectangle implements Shape {
    private int sideA;
    private int sideB;

    public Rectangle(int sideA, int sideB) {
        this.sideA = sideA;
        this.sideB = sideB;
    }

    public double getArea() {
        System.out.println("get Rectangle area");
        return sideA * sideB;
    }
}
public class Square implements Shape {

    private int side;

    public Square(int side) {
        this.side = side;
    }

    public double getArea() {
        System.out.println("get square area");
        return side * side;
    }
}
public class Circle implements Shape  {

    private int r;

    public Circle(int r) {
        this.r = r;
    }
    public double getArea() {
        System.out.println("get Circle area");
        return Math.PI * r * r;
    }
}

Изменим метод для подсчета площади

public class Example1 {
    public static void main(String[] args) {
        List <Square> squares = List.of(
            new Square(2),
            new Square(3),
            new Rectangle(1, 2),
            new Rectangle(1, 3),
            new Circle(1)
        );

        System.out.println(getTotalArea(shapes)); //=> 21.1415...
    }

    public static double getTotalAreal(List<Shape> shapes) {
        double result = 0;
        for (Shape shape: shapes) {
            result += shape.getArea();
        }
        return result;
    }
}

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

Игры разума

public class Calc {

    public static String[] generateQuestion() {
        return new String[0];
    }

    public static String getDescription() {
        return "Введите результат выражения";
    }
}

public class IsEven {

    public static String[] generateQuestion() {
        return new String[0];
    }

    public static String getDescription() {
        return "Является ли число четным";
    }
}

public class IsPrime {

    public static String[] generateQuestion() {
        return new String[0];
    }

    public static String getDescription() {
        return "Является ли число простым";
    }
}

class Example2 {

    public final static String IS_EVEN_NAME = "IS_EVEN";
    public final static String CALC_NAME = "CALC";
    public final static String IS_PRINE_NANE = "IS_PRIME";

    public static void main(String[] args) {
        System.out.println(getGameDescription(IS_EVEN_NAME));
    }

    public static String getGameDescription(String name) {
        return switch (name) {
            case IS_EVEN_NAME -> IsEven.getDescription();
            case CALC_NAME -> Calc.getDescription();
            case IS_PRIME_NAME -> IsPrime.getDescription();
            default -> throw new RuntimeException();
        };
    }

    public static String[] generateQuestion(String name) {
        return switch (name) {
            case IS_EVEN_NAME -> IsEven.generateQuestion();
            case CALC_NAME -> Calc.generateQuestion();
            case IS_PRIME_NAME -> IsPrime.generateQuestion();
            default -> throw new RuntimeException();
        };
    }
}

Проблема этого кода заключается в том, что для добавления новой игры необходимо каждый раз изменять класс Example2 и добавлять новый случай в методах getGameDescription() и generateQuestion(). Это может привести к дублированию кода и усложнению класса, что затрудняет его поддержку и расширение.

Чтобы решить эту проблему, можно создать интерфейс для игр, который будет иметь методы getDescription() и generateQuestion(). Тогда каждый класс игры будет реализовывать этот интерфейс

Создадим интерфейс:

public interface Game {
    String getDescription();
    String[] generateQuestion();
}

Доработаем классы игр, чтобы они реализовывали интерфейс

public class Calc implements Game {

    public String[] generateQuestion() {
        return new String[0];
    }

    public String getDescription() {
        return "Введите результат выражения";
    }
}

public class IsEven implements Game {

    public String[] generateQuestion() {
        return new String[0];
    }

    public String getDescription() {
        return "Является ли число четным";
    }
}

public class IsPrime implements Game {

    public String[] generateQuestion() {
        return new String[0];
    }

    public String getDescription() {
        return "Является ли число простым";
    }
}

Теперь мы можем упростить код в классе Example2

class Example2 {

    public final static String IS_EVEN_NAME = "IS_EVEN";
    public final static String CALC_NAME = "CALC";
    public final static String IS_PRINE_NANE = "IS_PRIME";

    public static Map<String, Game> games = Map.of(
        IS_EVEN_NAME, new IsEven(),
        CALC_NAME, new Calc(),
        IS_PRINE_NANE, new IsPrime()
    );

    public static void main(String[] args) {
        System.out.println(getGameDescription(IS_EVEN_NAME));
    }

    public static String getGameDescription(String name) {
        return games.get(name).getDescription();
    }

    public static String[] generateQuestion(String name) {
        return games.get(name).generateQuestion();
    }
}

Дополнительные материалы

  1. Полиморфизм подтипов

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 27 февраля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»