Проблема
При разработке программного обеспечения часто возникает необходимость создания кода, который может обрабатывать различные типы данных и объектов. Однако, если код не спроектирован с учетом масштабируемости, он может стать громоздким и трудным для поддержки
Одним из примеров такой проблемы является обработка различных типов фигур в геометрических расчетах
Создадим два класса, представляющие геометрические фигуры:
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();
}
}
Дополнительные материалы

Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.