Эффективный обзор Java (Серия из 78 частей)
Похоже, что многие предыдущие главы Эффективная Java разделяли некоторые опасения по поводу использования всех возможностей, которые предлагает нам объектно-ориентированное программирование. Действительно, существуют подводные камни, связанные с чрезмерным использованием некоторых функций этих шаблонов, поэтому их следует использовать с осторожностью. Однако сегодня мы рассмотрим тему, в которой объектно-ориентированное программирование может прийти на помощь.
Давайте начнем с рассмотрения следующего класса.
class Figure { enum Shape {CIRCLE, RECTANGLE} final Shape shape; double length; double width; double radius; Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } Figure(double length, double width) { shape = Shape.RECTANGLE; this.length = length; this.width = width; } double area() { switch(shape) { case RECTANGLE: return length * width; break; case CIRCLE: return Math.PI * (radius * radius); break; default: throw new AssertionError(shape); } } }
Как вы можете видеть в классе выше, это единственный класс, в котором есть перечисление
, в котором используется флаг, указывающий, является ли класс Кругом
или Прямоугольник
. Предположительно, причина (хотя и чрезвычайно тонкая) заключалась в том, что у них обоих есть функция области. Помимо сохранения этой одной строки, все остальное в этом классе еще хуже. Давайте перечислим некоторые из проблем.
- Существует много шаблонных шаблонов.
- Везде, где мы имеем дело со значениями и тем, как их нужно по-разному обрабатывать между помеченными типами, потребуется какой-то утомительный шаблон. Это приведет к значительному снижению читабельности.
- Объем памяти увеличивается, так как все экземпляры класса будут обременены ненужными значениями.
- Класс нельзя сделать неизменяемым, так как поля нельзя сделать
окончательными
если только конструкторы не инициализируют нерелевантные значения. - Мы также теряем некоторые преимущества нашего компилятора, поскольку класс с тегами не использует все поля вместе.
Так каков же ответ? К счастью, объектно-ориентированное программирование дает ответ с помощью иерархий классов. Как мы можем превратить помеченные классы в иерархию классов? Первым шагом является создание общего абстрактного
класса, в котором хранятся общие переменные и абстрактные объявления общих методов. В нашем классе Figure
есть только один общий метод, метод area
. Затем мы должны создать класс, который расширяется от корневого абстрактного класса для каждого из типов, имеющих тег. Давайте посмотрим, как наш Рисунок
можно было бы изменить, чтобы следовать этому методу:
abstract class Figure { abstract double area(); } class Rectangle extends Figure { final double width; final double length; Rectangle(double width, double length) { this.width = width; this.length = length; } @Override double area() { return width * length; } } class Circle extends Figure { final double radius; Circle(double radius) { this.radius = radius; } @Override double area() { return Math.PI * (radius * radius); } }
Несмотря на то, что в этом примере мы получаем больше классов, каждый класс намного проще, понятнее и эффективнее. Теперь давайте подумаем, нужно ли нам добавлять новый тип:
class Square extends Rectangle { Square(double side) { super(side, side); } }
Так же просто добавить новый тип. Представьте, как это выглядело бы в примере с помеченным классом. В то время как это очень чисто, помеченный класс стал бы только более сложным и менее простым.
Как вы можете видеть, на самом деле нет никаких преимуществ использования помеченных классов вместо правильной иерархии классов. Если вы подумываете об использовании помеченного класса, пересмотрите свое решение и подумайте, как вместо этого можно использовать иерархию классов. Это приведет к более эффективному, простому в обслуживании и менее подверженному ошибкам коду.
Эффективный обзор Java (Серия из 78 частей)
Оригинал: “https://dev.to/kylec32/effective-java-prefer-class-hierarchies-to-tagged-classes-59l9”