Больше переменных или объёмнее конструктор?
Пишу тестовое сейчас на понимание ООП в Java и столкнулся с таким диссонансом:
public abstract class Shape {
protected String color;
protected double area;
Shape() {
color = null;
area = 0;
}
Shape (String color, double area) {
this.color = color;
this.area = area;
}
public String getColor() {
return color;
}
public abstract double getArea();
public void draw() {
System.out.println(this);
}
public String toString() {
return "Shape: " + this.getClass().getSimpleName() + " color: " + color;
}
}
class Square extends Shape {
private double side;
Square(double side) {
super(Colors.YELLOW.name(), side * side);
this.side = side;
}
public double getSide() {
return side;
}
@Override
public double getArea() {
return area;
}
}
Переменная area вроде как излишня. Её можно убрать, в конструкторы меньше инфы передавать и вычисления делать в геттере getArea().
Вот так:
public abstract class Shape {
protected String color;
Shape() {
color = null;
}
Shape (String color) {
this.color = color;
}
public String getColor() {
return color;
}
public abstract double getArea();
public void draw() {
System.out.println(this);
}
public String toString() {
return "Shape: " + this.getClass().getSimpleName() + " color: " + color;
}
}
class Square extends Shape {
private double side;
Square(double side) {
super(Colors.YELLOW.name());
this.side = side;
}
public double getSide() {
return side;
}
@Override
public double getArea() {
return side * side;
}
}
С одной стороны мне кажется, что это будет трудно читаемо и памяти потребуется куда больше (представим цикл, который с каждой итерацией будет не смотреть значение в переменной, а постоянно по новой его вычислять), а с другой, что так неправильно и появится куча лишних ненужных переменных, а конструктор будет перегружен их инициализацией.
Ответы (2 шт):
С одной стороны мне кажется, что это будет трудно читаемо
Читабельность ничуть не пострадает, а вот вероятность совершить ошибку при передаче неправильного значения исчезает
и памяти потребуется куда больше
Как раз наоборот - памяти нужно меньше. Т.к. меньше статической информации. Один double это восемь байт. Если у вас 1000 объектов, то это экономия 8000 байт.
(представим цикл, который с каждой итерацией будет не смотреть значение в переменной, а постоянно по новой его вычислять),
А вот время получения значения, действительно увеличится. И тут нужно смотреть, что в приоритете. Если у одного объекта метод getArea() вызывается один-два раза за время жизни объекта, то оставьте его как есть с динамическим вычислением. Если 100 раз в цикле, то вызовите его один раз за пределами цикла и сохраните в переменную. А если 1000 раз в разных местах программы, то тогда нужно вводить отдельное поле класса и таки передать его в конструктор базового класса:
public abstract class Shape {
private double area;
protected String color;
Shape(double area) {
this(null, area);
}
Shape (String color, double area) {
this.color = color;
this.area = area;
}
public String getColor() {
return color;
}
public double getArea() {
return area;
}
public void draw() {
System.out.println(this);
}
public String toString() {
return "Shape: " + this.getClass().getSimpleName() + " color: " + color;
}
}
class Square extends Shape {
private double side;
Square(double side) {
super(Colors.YELLOW.name(), side * side);
this.side = side;
}
public double getSide() {
return side;
}
}
Вопросы производительности и памяти должны возникать тогда, когда у вас есть проблемы с производительностью и памятью. При проектировании классов производительность не является определяющим критерием.
Для начала надо ответить на вопрос - а какую информацию вообще должен содержать базовый класс Shape? Какие у вас есть требования к нему?
Вот вы передаете ему цвет и площадь, а надо ли ему знать об этом? Как этот класс будет использовать эту информацию? Требует ли класс знать цвет и площадь при конструировании обекта или каждый наследник просто должен иметь методы для извлечения цвета и площади?
Допустим, мы явно хотим сказать, что при создании объекта, мы уже должны знать цвет и площадь и они неизменны. Мы это можем выразить вот так
public abstract class Shape {
protected String color;
protected double area;
Shape (String color, double area) {
this.color = color;
this.area = area;
}
public String getColor() {
return color;
}
public double getArea() {
return area;
}
}
Код выше выражает наши требования для всех наследников, нельзя будет создать наследника не указав цвет и область.
Вот теперь вы озадачились вопросом вычисления площади. Например, вы можете хотеть эту площадь лениво вычислять, а цвет объекта вообще может быть будет изменяемым? В таком случае, базовый класс не может требовать этих параметров при создании, нет в этом смысла. Это уже другие требования к классу. Вы можете сказать, ок, мы не знаем как именно наследник будет вычислять или получать цвет и площадь, но он должен предоставить методы для их извлечения.
public abstract class Shape {
public abstract String getColor();
public abstract double getArea();
}
Теперь каждый наследник сам решает, как и когда он получит цвет/площадь.
Таким образом, ваш код явно должен выражать ваши ожидания от класса. Это идет не от соображений читаемости, а от ваших требований и архитектуры. Вы должны определить для своей системы - что есть Shape? Как этот базовый класс должен работать с наследниками? Что должны делать наследники? Какие формы вы будете наследовать? Как эти наследники должны себя вести, какие у них должны быть поля? Что вы знаете о полях и что вы не знаете? Какие у вас есть сценарии использоваия ваших форм? И т.д.