Основы объектно-ориентированного программирования
Рассмотрим задачу на реализацию класса с заданными свойствами.
Задача 10.1. Реализуйте класс R2Vector, позволяющий выполнять над векторами на плоскости следующие операции: сложение, вычитание, умножение на число и вычисление скалярного произведения.
Ее решение, приведенное ниже, в комментариях не нуждается.
Текст программы
class R2Vector { private double x, y; public R2Vector(double x, double y) { this.x = x; this.y = y; } public R2Vector() throws Exception { x = Xterm.inputDouble("x -> "); y = Xterm.inputDouble("y -> "); } public static R2Vector plus(R2Vector a, R2Vector b) { return new R2Vector(a.x+b.x, a.y+b.y); } public static R2Vector minus(R2Vector a, R2Vector b) { return new R2Vector(a.x-b.x, a.y-b.y); } public static R2Vector mult(R2Vector a, double k) { return new R2Vector(k*a.x, k*a.y); } public static double product(R2Vector a, R2Vector b) { return a.x*b.x + a.y*b.y; } }
Одним из достоинств объектно-ориентированного подхода является возможность использования уже существующих типов для порождения новых с автоматическим наследованием уже имеющихся свойств. Язык Java реализует эту возможность с помощью двух механизмов — расширения или наследования классов (ключевое слово extends ) и реализации интерфейсов (ключевое слово implements ).
Примером использования первого из этих механизмов является следующий программный фрагмент.
Пример расширения класса
class Stack { private static final int DEFSIZE = 16; private char[] array; private int head; public Stack() { array = new char[DEFSIZE]; head = 0; } public final void push(char c) { array[head++] = c; } public final char pop() { return array[--head]; } } public class Compf extends Stack { private void processSymbol(char c) { switch (c) { case '(': push(c); break; case ')': pop(); break; case '+': case '-': case '*': case '/': push(c); break; default: break; } } public Compf() { super(); } public final void compile(String str) { for(int i = 0; i < str.length(); i++) processSymbol(str.charAt(i)); } }
Класс Stack (стек символов) в данном случае используется в качестве базового для нового класса Compf (стековый компилятор формул), называемого в этом случае дочерним или выведенным. Все те методы, которые были реализованы в базовом классе (или суперклассе ) Stack могут быть использованы и для объектов типа Compf, что и делается в методе processSymbol. Принято говорить, что между классами Stack и Compf возникает отношение наследования.
В языке Java каждый существующий класс неявно наследует класс Object, что превращает последний в суперкласс любого класса. Сам же класс Object является единственным классом, не имеющим суперкласса. Если изобразить иерархию классов графически, то получится дерево с корнем Object.
При вызове конструктора выведенного класса всегда происходит вызов (явный или неявный) конструктора его непосредственного базового класса. В случае явного вызова это реализуется с помощью оператора super(), который должен быть первым оператором конструктора. В случае отсутствия явного вызова компилятор самостоятельно вставляет вызов конструктора суперкласса без параметров. Таким образом, при создании нового объекта всегда реализуется цепочка вызовов конструкторов всех родительских классов (вплоть до Object ).
Перед завершением рассмотрения примера отметим, что используемые в реальном проекте классы Stack и Compf существенно отличаются от рассмотренных нами сейчас. Это замечание в полной мере применимо и к следующему примеру механизма создания новых типов — использованию интерфейсов.
Пример реализации интерфейса
// Интерфейс, задающий новый тип - фигуру. interface Figure { // Периметр фигуры. public double perimeter(); // Площадь фигуры. public double area(); } // Класс "нульугольник", реализующий интерфейс фигуры. class Void implements Figure { public double perimeter() { return 0.0; } public double area() { return 0.0; } } // Класс "одноугольник", реализующий интерфейс фигуры. class Point implements Figure { private R2Point p; public Point(R2Point p) { this.p = p; } public double perimeter() { return 0.0; } public double area() { return 0.0; } } // Класс "двуугольник", реализующий интерфейс фигуры. class Segment implements Figure { private R2Point p, q; public Segment(R2Point p, R2Point q) { this.p = p; this.q = q; } public double perimeter() { return 2.0 * R2Point.dist(p, q); } public double area() { return 0.0; } } // Класс "многоугольник", реализующий интерфейс фигуры. class Polygon extends Deq implements Figure { private double s, p; public Polygon(R2Point a, R2Point b, R2Point c) { ; // Значительное упрощение. } public double perimeter() { return p; } public double area() { return s; } }