ООП в примерах. Часть 3. Переопределение методов, уровни абстракции

от
Совершенный код   ооп, java me, java

Первая часть
Вторая часть. Наследование.

Пункт меню должен рисоваться на экране. Так почему бы нам не добавить метод отрисовки сразу в класс MenuItem и в его классы-потомки? Передадим этому методу все нужные нам параметры и будем рисовать.
  1. public class MenuItem {
  2.  
  3.     protected static final Font ITEM_FONT = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
  4.     protected static final int ITEM_HEIGHT = ITEM_FONT.getHeight() + 5;
  5.  
  6.     protected String name;
  7.  
  8.     public MenuItem(String name) {
  9.         this.name = name;
  10.     }
  11.  
  12.     public String getName() {
  13.         return name;
  14.     }
  15.  
  16.     public void paint(Graphics g, int startY, int itemIndex) {
  17.         g.setFont(ITEM_FONT);
  18.         g.setColor(0xFF095E15);
  19.         g.drawString(name, g.getClipWidth() / 2, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.HCENTER);
  20.     }
  21. }
Здесь я добавил метод paint с нужными для меня параметрами, а также добавил другой шрифт для текста и сделал все поля protected, чтобы мы могли с лёгкостью обращаться к ним из производных классов.

Теперь перепишем класс ColorMenuItem и IconMenuItem.
  1. public class ColorMenuItem extends MenuItem {
  2.  
  3.     private int color;
  4.  
  5.     public ColorMenuItem(String name, int color) {
  6.         super(name);
  7.         this.color = color;
  8.     }
  9.  
  10.     public void paint(Graphics g, int startY, int itemIndex) {
  11.         g.setFont(ITEM_FONT);
  12.         g.setColor(color);
  13.         g.drawString(name, g.getClipWidth() / 2, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.HCENTER);
  14.     }
  15. }
  16.  
  17. public class IconMenuItem extends MenuItem {
  18.  
  19.     private Image icon;
  20.  
  21.     public IconMenuItem(String name, String iconName) {
  22.         super(name);
  23.         createIcon(iconName);
  24.     }
  25.  
  26.     public void paint(Graphics g, int startY, int itemIndex) {
  27.         super.paint(g, startY, itemIndex);
  28.         g.drawImage(icon, g.getClipWidth() / 4, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.RIGHT);
  29.     }
  30.  
  31.     private void createIcon(String iconName) {
  32.         try {
  33.             icon = Image.createImage("/" + iconName);
  34.         } catch (IOException ex) {
  35.             icon = Image.createImage(18, 18);
  36.         }
  37.     }
  38. }

Смотрим на метод paint. В одном случае super.paint вызывается, в другом нет. Это нормально. Если нам надо в производном методе выполнить сначала все операторы базового класса, тогда пишем super.имя_метода(параметры), если не надо - не пишем.
В данном случае, при отрисовке пункта меню с иконкой, мы сначала рисуем текст в базовом классе, а потом рисуем иконку. В случае с цветным пунктом меню, мы сразу рисуем текст, не обращаясь к методу базового класса. В некоторых ситуациях это оправдано.
Кстати, вызывать super.метод не обязательно сразу в начале метода. Если мы в IconMenuItem напишем так:
  1. public void paint(Graphics g, int startY, int itemIndex) {
  2.     g.drawImage(icon, g.getClipWidth() / 4, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.RIGHT);
  3.     super.paint(g, startY, itemIndex);
  4. }
То ничего страшного не произойдёт - сначала будет рисоваться иконка, а потом уже текст.

Теперь посмотрим, что это нам даст в методе отрисовке меню. OopMenu4.java
Большой код в цикле изменился на:
  1. for (int i = 0; i < items.length; i++) {
  2.     items[i].paint(g, startY, i);
  3. }

То есть мы перенесли всё необходимое в классы, тем самым освободив класс канваса от лишнего кода.
Это одно из преимуществ использования ООП, нам не надо лепить всё в один класс, мы помещаем реализацию в отдельные классы и вызываем необходимый метод.
Если при этом мы правильно разбили всё на сущности, тогда получится такая вот штука:
объект.действие(параметры)
меню.отрисовывайся(параметры)
спрайт.передвинься(позиция)
автомобиль.притормози(коэффициент)

Как видно, мы теперь оперируем не переменными, методами, а уже как-будто на естественном языке говорим объектам что им делать. То есть теперь наша филолог Настя сможет управлять объектами не зная языков программирования.

Ещё одной важной деталью является сокрытие реализации. В данный момент у нас есть как бы различные слои (называемые уровнями абстракции): пункт меню рисуется в классе Canvas, но мы не знаем деталей реализации этой отрисовки. И нам не обязательно на этом уровне знать о них. Мы просто говорим: ты - рисуйся, ты - двигайся, а ты вот тут пока подожди.
Вот так и программист, когда пишет программу, должен разбивать (декомпозировать) систему на различные уровни.
Возьмём к примеру игру.

На верхнем уровне мы говорим: игра - запустись, игра - остановись.
Чуть ниже: экран заставки - покажись, экран заставки - сменись на экран меню, экран меню - покажись и дождись выбора пункта от пользователя.
Ещё ниже: фон - отрисуйся, меню - отрисуйся, обработчик клавиш - подожди нажатия клавиши и обработай, если было нажатие
Ещё ниже: экран - заполнись чёрным цветом, пункт меню - выведи на экран текст
И в самом низу будет: g.setColor(0), g.fillRect(0, 0, w, h); g.setColor(0xFFFFFF), g.drawString(text, 0, 0, 20);

Программист создаёт классы-сущности и наделяет их свойствами (напр. цвет, размер), затем всю рутину кидает в методы этих классов (напр. отрисовка), и потом уже не задумываясь о деталях реализации, оперирует объектами (напр. меню - отрисуйся, рекорд - сохранись).

Надеюсь, теперь хоть чуточку прояснил полезность использования ООП?
В дальнейшем мы продолжим улучшать систему, оборачивая её во всё новые уровни абстракции.

Готовый проект
+8   9   1
3051