ООП в примерах. Часть 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
  • views 6237