ООП в примерах. Часть 3. Переопределение методов, уровни абстракции
от aNNiMON
Пункт меню должен рисоваться на экране. Так почему бы нам не добавить метод отрисовки сразу в класс MenuItem и в его классы-потомки? Передадим этому методу все нужные нам параметры и будем рисовать.
Здесь я добавил метод paint с нужными для меня параметрами, а также добавил другой шрифт для текста и сделал все поля protected, чтобы мы могли с лёгкостью обращаться к ним из производных классов.
Теперь перепишем класс ColorMenuItem и IconMenuItem.
Смотрим на метод paint. В одном случае super.paint вызывается, в другом нет. Это нормально. Если нам надо в производном методе выполнить сначала все операторы базового класса, тогда пишем super.имя_метода(параметры), если не надо - не пишем.
В данном случае, при отрисовке пункта меню с иконкой, мы сначала рисуем текст в базовом классе, а потом рисуем иконку. В случае с цветным пунктом меню, мы сразу рисуем текст, не обращаясь к методу базового класса. В некоторых ситуациях это оправдано.
Кстати, вызывать super.метод не обязательно сразу в начале метода. Если мы в IconMenuItem напишем так:
То ничего страшного не произойдёт - сначала будет рисоваться иконка, а потом уже текст.
Теперь посмотрим, что это нам даст в методе отрисовке меню. OopMenu4.java
Большой код в цикле изменился на:
То есть мы перенесли всё необходимое в классы, тем самым освободив класс канваса от лишнего кода.
Это одно из преимуществ использования ООП, нам не надо лепить всё в один класс, мы помещаем реализацию в отдельные классы и вызываем необходимый метод.
Если при этом мы правильно разбили всё на сущности, тогда получится такая вот штука:
объект.действие(параметры)
меню.отрисовывайся(параметры)
спрайт.передвинься(позиция)
автомобиль.притормози(коэффициент)
Как видно, мы теперь оперируем не переменными, методами, а уже как-будто на естественном языке говорим объектам что им делать. То есть теперь наша филолог Настя сможет управлять объектами не зная языков программирования.
Ещё одной важной деталью является сокрытие реализации. В данный момент у нас есть как бы различные слои (называемые уровнями абстракции): пункт меню рисуется в классе Canvas, но мы не знаем деталей реализации этой отрисовки. И нам не обязательно на этом уровне знать о них. Мы просто говорим: ты - рисуйся, ты - двигайся, а ты вот тут пока подожди.
Вот так и программист, когда пишет программу, должен разбивать (декомпозировать) систему на различные уровни.
Возьмём к примеру игру.
На верхнем уровне мы говорим: игра - запустись, игра - остановись.
Чуть ниже: экран заставки - покажись, экран заставки - сменись на экран меню, экран меню - покажись и дождись выбора пункта от пользователя.
Ещё ниже: фон - отрисуйся, меню - отрисуйся, обработчик клавиш - подожди нажатия клавиши и обработай, если было нажатие
Ещё ниже: экран - заполнись чёрным цветом, пункт меню - выведи на экран текст
И в самом низу будет: g.setColor(0), g.fillRect(0, 0, w, h); g.setColor(0xFFFFFF), g.drawString(text, 0, 0, 20);
Программист создаёт классы-сущности и наделяет их свойствами (напр. цвет, размер), затем всю рутину кидает в методы этих классов (напр. отрисовка), и потом уже не задумываясь о деталях реализации, оперирует объектами (напр. меню - отрисуйся, рекорд - сохранись).
Надеюсь, теперь хоть чуточку прояснил полезность использования ООП?
В дальнейшем мы продолжим улучшать систему, оборачивая её во всё новые уровни абстракции.
Готовый проект
Следующая статья →- public class MenuItem {
- protected static final Font ITEM_FONT = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
- protected static final int ITEM_HEIGHT = ITEM_FONT.getHeight() + 5;
- protected String name;
- public MenuItem(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- public void paint(Graphics g, int startY, int itemIndex) {
- g.setFont(ITEM_FONT);
- g.setColor(0xFF095E15);
- g.drawString(name, g.getClipWidth() / 2, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.HCENTER);
- }
- }
Теперь перепишем класс ColorMenuItem и IconMenuItem.
- public class ColorMenuItem extends MenuItem {
- private int color;
- public ColorMenuItem(String name, int color) {
- super(name);
- this.color = color;
- }
- public void paint(Graphics g, int startY, int itemIndex) {
- g.setFont(ITEM_FONT);
- g.setColor(color);
- g.drawString(name, g.getClipWidth() / 2, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.HCENTER);
- }
- }
- public class IconMenuItem extends MenuItem {
- private Image icon;
- public IconMenuItem(String name, String iconName) {
- super(name);
- createIcon(iconName);
- }
- public void paint(Graphics g, int startY, int itemIndex) {
- super.paint(g, startY, itemIndex);
- g.drawImage(icon, g.getClipWidth() / 4, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.RIGHT);
- }
- private void createIcon(String iconName) {
- try {
- icon = Image.createImage("/" + iconName);
- } catch (IOException ex) {
- icon = Image.createImage(18, 18);
- }
- }
- }
Смотрим на метод paint. В одном случае super.paint вызывается, в другом нет. Это нормально. Если нам надо в производном методе выполнить сначала все операторы базового класса, тогда пишем super.имя_метода(параметры), если не надо - не пишем.
В данном случае, при отрисовке пункта меню с иконкой, мы сначала рисуем текст в базовом классе, а потом рисуем иконку. В случае с цветным пунктом меню, мы сразу рисуем текст, не обращаясь к методу базового класса. В некоторых ситуациях это оправдано.
Кстати, вызывать super.метод не обязательно сразу в начале метода. Если мы в IconMenuItem напишем так:
- public void paint(Graphics g, int startY, int itemIndex) {
- g.drawImage(icon, g.getClipWidth() / 4, startY + ITEM_HEIGHT * itemIndex, Graphics.TOP | Graphics.RIGHT);
- super.paint(g, startY, itemIndex);
- }
Теперь посмотрим, что это нам даст в методе отрисовке меню. OopMenu4.java
Большой код в цикле изменился на:
- for (int i = 0; i < items.length; i++) {
- items[i].paint(g, startY, i);
- }
То есть мы перенесли всё необходимое в классы, тем самым освободив класс канваса от лишнего кода.
Это одно из преимуществ использования ООП, нам не надо лепить всё в один класс, мы помещаем реализацию в отдельные классы и вызываем необходимый метод.
Если при этом мы правильно разбили всё на сущности, тогда получится такая вот штука:
объект.действие(параметры)
меню.отрисовывайся(параметры)
спрайт.передвинься(позиция)
автомобиль.притормози(коэффициент)
Как видно, мы теперь оперируем не переменными, методами, а уже как-будто на естественном языке говорим объектам что им делать. То есть теперь наша филолог Настя сможет управлять объектами не зная языков программирования.
Ещё одной важной деталью является сокрытие реализации. В данный момент у нас есть как бы различные слои (называемые уровнями абстракции): пункт меню рисуется в классе Canvas, но мы не знаем деталей реализации этой отрисовки. И нам не обязательно на этом уровне знать о них. Мы просто говорим: ты - рисуйся, ты - двигайся, а ты вот тут пока подожди.
Вот так и программист, когда пишет программу, должен разбивать (декомпозировать) систему на различные уровни.
Возьмём к примеру игру.
На верхнем уровне мы говорим: игра - запустись, игра - остановись.
Чуть ниже: экран заставки - покажись, экран заставки - сменись на экран меню, экран меню - покажись и дождись выбора пункта от пользователя.
Ещё ниже: фон - отрисуйся, меню - отрисуйся, обработчик клавиш - подожди нажатия клавиши и обработай, если было нажатие
Ещё ниже: экран - заполнись чёрным цветом, пункт меню - выведи на экран текст
И в самом низу будет: g.setColor(0), g.fillRect(0, 0, w, h); g.setColor(0xFFFFFF), g.drawString(text, 0, 0, 20);
Программист создаёт классы-сущности и наделяет их свойствами (напр. цвет, размер), затем всю рутину кидает в методы этих классов (напр. отрисовка), и потом уже не задумываясь о деталях реализации, оперирует объектами (напр. меню - отрисуйся, рекорд - сохранись).
Надеюсь, теперь хоть чуточку прояснил полезность использования ООП?
В дальнейшем мы продолжим улучшать систему, оборачивая её во всё новые уровни абстракции.
Готовый проект
Часть 4. Интерфейсы, повторное использование