Skip to content

Latest commit

 

History

History
988 lines (782 loc) · 46.1 KB

File metadata and controls

988 lines (782 loc) · 46.1 KB

Занятие 7. Классы и объекты (III)

Инкапсуляция

Инкапсуляция — это принцип ООП, который заключается в объединении данных и методов, работающих с этими данными, в единый объект, и сокрытии внутренней реализации от внешнего доступа.

Согласно этому принципу в классе необходимо выделить открытую часть и закрытую часть.

Пакеты

Пакеты — это механизм для организации классов в логические группы, что помогает избежать конфликтов имен и управлять доступом.

Объявление пакета:

// Самая первая инструкция (в начале файла .java)
package com.company.project.module;

public class MyClass {
    // код класса
}

Физически пакет является иерархией каталогов, а имя пакета соответствует структуре каталогов:

com/
└── company/
    └── project/
        └── module/
            └── MyClass.java

Обычно, имя пакета начинается с доменного имени той организации, в которой разрабатывается советующий исходный код, записанного в обратном порядке. Такое именование пакетов позволяет избежать конфликты пространств имен.

Стандартная схема именования пакета:

com.[company].[project].[module].[submodule]

Пакеты могут иметь общую начальную часть в структуре каталогов.

Например, имеем следующие классы:

package ru.isu.math.graphicaleditor;

public class GraphicalEditorApp {
    // код класса
}
package ru.isu.math.graphicaleditor.graphics;

public abstract class Shape {
   // код класса
}
package ru.isu.math.graphicaleditor.graphics;

public class Rectangle extends Shape {
   // код класса
}

В этом случае имеем два разных пакета:

ru.isu.math.graphicaleditor
ru.isu.math.graphicaleditor.graphics

Соответствующая структура каталогов будет выглядеть следующим образом:

ru/
└── isu/
    └── math/
        └── graphicaleditor/
            ├── GraphicalEditorApp.java
            └── graphics/
                ├── Shape.java
                └── Rectangle.java

Внутри одного пакета нет необходимости импортировать одни классы этого пакета в другие классы этого же пакета. Если пакеты разные, то чтобы использовать классы одного пакета в классах другого пакета, их необходимо импортировать.

package ru.isu.math.graphicaleditor;

// Импорт всех классов из вложенного пакета  
import ru.isu.math.graphicaleditor.graphics.*;

public class GraphicalEditorApp {
   // код класса
}

Управление доступом

Уровни доступа

Есть четыре уровня доступа к полям, методам и конструкторам:

Где доступно? Закрытый (private) Пакетный (по умолчанию) Защищенный (protected) Открытый (public)
Класс да да да да
Пакет нет да да да
Пакет и подклассы нет нет да да
Любой класс нет нет нет да

Есть три модификатора доступа:

  • private — закрытый уровень доступа;
  • protected — защищенный уровень доступа;
  • public — открытый уровень доступа.

Пакетный уровень применяется по умолчанию, поэтому не имеет модификатора.

Управление доступом к классам

К классам верхнего уровня возможно применить только два уровня доступа: открытый или пакетный (по умолчанию).

По умолчанию класс доступен только в своем пакете.

package ru.isu.math.graphicaleditor.graphics;

// Этот класс доступен только в пакете 
// ru.isu.math.graphicaleditor.graphics
abstract class Shape {
    // Код класса
}

Публичный класс может быть доступен в любых других классах.

package ru.isu.math.graphicaleditor.graphics;

// Этот класс доступен только везде, где он импортирован
public class Rectangle extends Shape {
    // Код класса
}

Публичный класс может быть импортирован в классы других пакетов.

package ru.isu.math.graphicaleditor;

// Импорт всех публичных классов заданного пакета
impoprt ru.isu.math.graphicaleditor.graphics.*;

public class GraphicalEditorApp {
    // код класса
    public static void main(String[] args) {
        // Используем класс Rectangle
        Rectangle rect = new Rectangle(); 
        
        // Попытка обратиться к классу Shape
        // Shape[] shapes = {rect} // ОШИБКА!
    }
}

Управление доступом к членам класса

Закрытый доступ:

Закрытые поля и методы объявляются с модификатором private.

package com.company.project.module;

public class BaseClass {
    // Закрытое поле доступно только внутри данного класса
    private String secret;
    
    // Закрытый метод доступен только внутри данного класса
    private void printSecret() {
        System.out.println(secret);
    }
    
    // Другие поля и методы
}

Из любого другого класса доступ к закрытому полю и методу напрямую невозможен.

package com.company.project;

import com.company.project.module.BaseClass;

public class MainClass {
    public static void main(String[] args) {
        BaseClass bc = new BaseClass();
        // Попытка обратиться к закрытому полю другого класса
        String s = bc.secret; // ОШИБКА!
        // Попытка обратиться к закрытому методу другого класса
        String s = bc.printSecret(); // ОШИБКА!
    }
}

Защищенный доступ

Защищенные поля и методы объявляются с модификатором prоtected.

package com.company.project.module;

public class BaseClass {
    // Защищенное поле
    prоtected String secret;
       
    // Другие поля и методы
}

Подкласс имеет доступ к защищенным полям и методам, объявленным в суперклассе.

package com.company.project.othermodule;

import com.company.project.module.BaseClass;

public class DerivedClass extends BaseClass {   
    // Защищенный метод
    prоtected void printSecret() {
        System.out.println(secret);
    }
    
    // Другие поля и методы
}

Но за пределами пакета защищенные поля и методы уже не доступны.

package com.company.project; // Другой пакет

import com.company.project.module.BaseClass;
import com.company.project.othermodule.DerivedClass;

public class MainClass {
    public static void main(String[] args) {
        BaseClass bc = new BaseClass();
        // Попытка обратиться к защищенному полю другого класса
        String s = bc.secret; // ОШИБКА!

        DerivedClass dc = new DerivedClass();
        // Попытка обратиться к защищенному методу другого класса
        String s = dc.printSecret(); // ОШИБКА!
    }
}

Пакетный доступ

Пакетные поля и методы объявляются без модификатора (по умолчанию).

package com.company.project.module;

public class BaseClass {
    // Пакетное поле
    String secret;
       
    // Другие поля и методы
}

Подкласс имеет доступ к защищенным полям и методам, объявленным в суперклассе.

package com.company.project.module; // Тот же самый пакет

public class OtherClass {   
    // Пакетный метод
    void printSecret() {
        System.out.println(secret);
    }
    
    // Другие поля и методы
}

Но за пределами пакета пакетные поля и методы уже не доступны.

package com.company.project; // Другой пакет

public class MainClass {
    public static void main(String[] args) {
        BaseClass bc = new BaseClass();
        // Попытка обратиться к пакетному полю другого класса
        String s = bc.secret; // ОШИБКА!

        OtherClass oc = new OtherClass();
        // Попытка обратиться к пакетному методу другого класса
        String s = oc.printSecret(); // ОШИБКА!
    }
}

Открытый доступ

Публичные поля и методы объявляются с модификатором public.

package com.company.project.module;

public class BaseClass {
    // Публичное поле
    public String unsecretedId;
    
    // Публичный метод
    public void printUnsecreted() {
        System.out.println(unsecretedId);
    }
   
    // Другие поля и методы
}

Публичные поля и методы доступны везде где доступен сам класс, в котором они объявлены.

package com.company.project;

import com.company.project.module.BaseClass;

public class MainClass {
    public static void main(String[] args) {
        BaseClass bc = new BaseClass();
        // Можно обратиться к публичному полю другого класса
        String s = bc.unsecretedId;
        // Можно обратиться к публичному методу другого класса
        bc.printUnsecreted();
    }
}

Организация доступа к закрытым полям

Как правило, любое нестатическое поле объявляется закрытым (private) и сопровождается парой открытых (public) методов доступа (аксессоры): геттером (для чтения значения) и сеттером (для редактирования значения).

Добавление методов доступа в класс Shape:

import java.awt.*;

public abstract class Shape {
    // Закрытые поля
    private int x, y; // Координаты базовой точки    
    private Color color; // Цвет

    // Методы доступа к закрытым полям (аксессоры)
		
    // Геттер для поля x
    public int getX() {
        return x;
    }
    
    // Сеттер для поля x
    public void setX(int x) {
        if (x >= 0 && x <= 1000)
            this.x = x;
    }
    
    // Геттер для поля y
    public int getY() {
        return y;
    }

    // Сеттер для поля y
    public void setY(int y) {
        if (y >= 0 && y <= 1000)
            this.y = y;
    }

    // Геттер для поля color
    public Color getColor() {
        return color;
    }
 
    // Сеттер для поля color
    public void setColor(Color color) {
        if (color != null)
            this.color = color;
    }

    // Конструктор
    Shape(int x, int y, Color color) {
        setX(x); // Задать значение поля x через сеттер
        setY(y); // Задать значение поля y через сеттер
        setColor(color); // Задать значение поля color через сеттер
    }

    // Метод для перемещения фигуры
    void move(int dx, int dy) {
        int newX = getX(); // Получить значение поля x через геттер
        int newY = getY(); // Получить значение поля x через геттер
        
        newX += dx; // Вычислить новое значение x-координаты
        newY += dy; // Вычислить новое значение y-координаты

        setX(newX); // Задать значение поля x через сеттер
        setY(newY); // Задать значение поля y через сеттер
    }

    // Другие поля и методы

    // Нарисовать фигуру в графическом контексте
    abstract void draw(Graphics2D g);
}

В подклассах доступ к закрытым членам суперкласса осуществляется через аксессоры.

public class Rectangle extends Shape {
    // Поля, методы и конструкторы
     
    public static Rectangle copy(Rectangle rect) {
        Rectangle copy = new Rectangle();
        
        // Инструкции для копирования координат базовой точки
        int x = rect.getX(); // Получить значение поля x через геттер
        int y = rect.getY(); // Получить значение поля y через геттер     
        copy.setX(x); // Присвоить значение полю x через сеттер
        copy.setY(y); // Присвоить значение полю y через сеттер
        
        // Другие инструкции для копирования ширины, высоты, угла поворота и пр.
        
        return copy;
    }
}

Во всех других классах доступ к закрытым членам класса осуществляется через аксессоры.

public class MainClass {
    public static void main(String[] args) {
        Rectangle rect = new Rectangle();
        
        // Инструкции
        
        int rectX = rect.getX(); // Получить значение поля x через геттер
        int rectY = rect.getY(); // Получить значение поля y через геттер
        
        rect.setX(10); // Присвоить значение полю x через сеттер
        rect.setY(10); // Присвоить значение полю y через сеттер
        
        // Другие инструкции
    }
}

Класс Object

Класс Object является корневым классом для всех остальных классов. Каждый класс прямо или косвенно наследуется от класса Object.

// Все эти объявления эквивалентны
class MyClass { }
class MyClass extends Object { }

Класс Object имеет несколько полезных методов, которые следует переопределять в его подклассах.

Метод toString

Метод toString возвращает строковое представление объекта.

public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Переопределение метода toString
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

Cтроковое представление объекта часто используют в процессах отладки приложения и журналирования состояния объектов.

Person person = new Person("John", 25);
System.out.println(person); // Вывод: Person{name='John', age=25}

Без переопределения метода toString строковое состояние объекта задается именем его класса и адресом объектной ссылки.

Person person = new Person("John", 25);
System.out.println(person); // Вывод: Person@15db9742

Методы equals и hashCode

Метод equals сравнивает объекты на равенство. Метод hashCode возвращает хэш-код объекта.

public class Person {
    private String name;
    private int age;
    private String email;
    
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    // Переопределение equals вручную
    @Override
    public boolean equals(Object obj) {
        // Проверка на ссылочное равенство
        if (this == obj) {
            return true;
        }
        
        // Проверка на null
        if (obj == null) {
            return false;
        }
        
        // Проверка на совпадение классов
        if (getClass() != obj.getClass()) {
            return false;
        }
        
        // Приведение типа
        Person other = (Person) obj;
        
        // Сравнение полей
        
        // Сравнение age (примитивный тип)
        if (age != other.age) {
            return false;
        }
        
        // Сравнение name (ссылочный тип)
        if (name == null) {
            if (other.name != null) {
                return false;
            }
        } else if (!name.equals(other.name)) {
            return false;
        }
        
        // Сравнение email (ссылочный тип)
        if (email == null) {
            if (other.email != null) {
                return false;
            }
        } else if (!email.equals(other.email)) {
            return false;
        }
        
        return true;
    }
    
    // Переопределение метода hashCode вручную
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        
        return result;
    }
}

Можно переопределить методы equals и hashCode с помощью утилиты Objects:

import java.util.Objects;

public class Person {
    private String name;
    private int age;
    private String email;
    
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    // Переопределение equals с Objects.equals()
    @Override
    public boolean equals(Object obj) {
        // 1. Проверка на ссылочное равенство
        if (this == obj) return true;
        
        // 2. Проверка на null и совпадение классов
        if (obj == null || getClass() != obj.getClass()) return false;
        
        // 3. Приведение типа
        Person person = (Person) obj;
        
        // 4. Сравнение полей с Objects.equals() для ссылочных типов
        return age == person.age &&
               Objects.equals(name, person.name) &&
               Objects.equals(email, person.email);
    }
    
    // Переопределение hashCode с Objects.hash()
    @Override
    public int hashCode() {
        return Objects.hash(name, age, email);
    }
}

Если метод equals возвращает true при сравнении двух объектов, то метод hashCode() должен возвращать одинаковые значения для этих объектов, и, наоборот, если метод hashCode возвращает разные значения для двух объектов, то метод equals должен возвращать false при их сравнении.

public class MainClass {
    public static void main(String[] args) {
        // Создаем два РАВНЫХ объекта
        Person person1 = new Person("Иван", 25, "ivan@math.isu.ru");
        Person person2 = new Person("Иван", 25, "ivan@math.isu.ru");
        
        System.out.println("person1: " + person1);
        System.out.println("person2: " + person2);
        
        // Проверяем контракт
        boolean equalsResult = person1.equals(person2);
        int hashCode1 = person1.hashCode();
        int hashCode2 = person2.hashCode();
        boolean hashCodesEqual = (hashCode1 == hashCode2);
        
        System.out.println("\nПроверка контракта:");
        System.out.println("person1.equals(person2): " + equalsResult);
        System.out.println("person1.hashCode(): " + hashCode1);
        System.out.println("person2.hashCode(): " + hashCode2);
        System.out.println("hashCode1 == hashCode2: " + hashCodesEqual);
        
        System.out.println("\n✓ Контракт выполняется: equals=true → hashCode равны");
    }
}

Вложенные классы

Вложенные классы — это классы, объявленные внутри другого класса. Они помогают логически сгруппировать классы, улучшить инкапсуляцию и сделать код более читаемым.

Статические вложенные классы

public class OuterClass {
    private static String outerStaticField = "Static field";
    private String outerInstanceField = "Instance field";
    
    static class StaticNestedClass {
        public void print() {
            System.out.println(outerStaticField); // Доступ только к статическим полям
            // System.out.println(outerInstanceField); // Ошибка компиляции!
        }
    }
}

// Использование
OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
nested.print();

Внутренние классы

public class OuterClass {
    private String outerField = "Outer field";
    
    class InnerClass {
        public void print() {
            System.out.println(outerField); // Доступ к полям внешнего класса
        }
        
        public void modifyOuterField() {
            outerField = "Modified from inner class";
        }
    }
    
    public InnerClass createInner() {
        return new InnerClass();
    }
}

// Использование
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
// или
OuterClass.InnerClass inner2 = outer.createInner();

Создание экземпляров вложенных классов

// Статический вложенный класс
OuterClass.StaticNestedClass staticNested = new OuterClass.StaticNestedClass();

// Внутренний класс
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();

Управление доступом к вложенным классам

Вложенный класс может иметь любой из уровень доступа:

  • Закрытый (объявление с модификатором доступа private)
  • Защищенный (объявление с модификатором доступа protected)
  • Пакетный (объявление без модификатора доступа)
  • Публичный (объявление с модификатором доступа public)

Шаблон проектирования Builder

Строитель (англ. Builder) — это порождающий шаблон проектирования, который позволяет создавать сложные объекты пошагово.

Строитель для абстрактного класса Shape

import java.awt.*;

public abstract class Shape {
    private int x, y;
    private Color color;

    // Геттеры и сеттеры

    // Защищенный конструктор для использования строителем
    protected Shape(ShapeBuilder builder) {
        setX(builder.x);
        setY(builder.y);
        setColor(builder.color);
    }

    // Базовый строитель
    public static abstract class ShapeBuilder {
        protected int x = 0;
        protected int y = 0;
        protected Color color = Color.BLACK;

        // Методы для установки параметров
        public ShapeBuilder x(int x) {
            this.x = x;
            return this;
        }

        public ShapeBuilder y(int y) {
            this.y = y;
            return this;
        }

        public ShapeBuilder color(Color color) {
            this.color = color;
            return this;
        }

        public ShapeBuilder position(int x, int y) {
            this.x = x;
            this.y = y;
            return this;
        }

        // Абстрактный метод для построения конкретной фигуры
        public abstract Shape build();
    }

    // Другие методы
    
    // Переопределение методов toString, equals, hashCode 
}

Builder для класса Rectangle

import java.awt.*;

public class Rectangle extends Shape {
    private int width;
    private int height;
    private double rotation;

    // Геттеры и сеттеры

    // Закрытый конструктор для использование строителем
    private Rectangle(RectangleBuilder builder) {
        super(builder);
        this.width = builder.width;
        this.height = builder.height;
        this.rotation = builder.rotation;
    }

    // Публичный конструктор по умолчанию
    public Rectangle() {
        super(new RectangleBuilder().x(0).y(0).color(Color.BLACK));
        this.width = 10;
        this.height = 10;
        this.rotation = 0;
    }

    // Строитель
    public static class RectangleBuilder extends ShapeBuilder {
        private int width = 10;
        private int height = 10;
        private double rotation = 0;

        public RectangleBuilder width(int width) {
            this.width = width;
            return this;
        }

        public RectangleBuilder height(int height) {
            this.height = height;
            return this;
        }

        public RectangleBuilder size(int width, int height) {
            this.width = width;
            this.height = height;
            return this;
        }

        public RectangleBuilder rotation(double rotation) {
            this.rotation = rotation;
            return this;
        }

        // Переопределене методов базового строителя
        @Override
        public RectangleBuilder x(int x) {
            super.x(x);
            return this;
        }

        @Override
        public RectangleBuilder y(int y) {
            super.y(y);
            return this;
        }

        @Override
        public RectangleBuilder color(Color color) {
            super.color(color);
            return this;
        }

        @Override
        public Rectangle build() {
            return new Rectangle(this);
        }
    }

    // Статический метод для создания строителя
    public static RectangleBuilder builder() {
        return new RectangleBuilder();
    }

    // Другие методы 

    // Переопределение методов toString, equals, hashCode 

Создание объектов пошагово с помощью строителя.

public class MainClass {
    public static void main(String[] args) {
        // Создание прямоугольника через строитель
        Rectangle rect = Rectangle.builder()
                .x(10)
                .y(20)
                .color(Color.RED)
                .width(100)
                .height(50)
                .rotation(0.0)
                .build();
        
        System.out.println(rect);
    }
}

Задания

7-1 — 1 балл

  • Разработать абстрактный класс Shape в пакете ru.isu.math.graphics для представления графических фигур.
  • В классе Shape должны быть объявлены ЗАКРЫТЫЕ нестатические поля (координаты левого верхнего угла ограничивающей рамки, ширина и высота ограничивающей рамки, цвет контура, цвет заливки, угол поворота).
  • В классе Shape должны быть реализованы методы доступа (геттеры и сеттеры) ко всем ЗАКРЫТЫМ нестатическим полям.
  • В классе Shape должны быть объявлены завершенные статические поля: допустимые (минимальные и максимальные) значения координат левого верхнего угла ограничивающей рамки, ширины и высоты ограничивающей рамки, угла поворота.
  • В классе Shape должно контролироваться присваивание значений нестатическим полям в СЕТТЕРАХ, а именно значения координат левого верхнего угла ограничивающей рамки, ширины и высоты ограничивающей рамки, угла поворота должны принадлежать диапазонам допустимых значений, определенных соответствующими глобальными константами.
  • В классе Shape должны быть объявлены нестатические завершенные методы (перемещения, вращения, масштабирования).
  • В классе Shape должны быть объявлены абстрактные методы (вычисления площади, отрисовки на графическом контексте)
  • В классе Shape должны быть конструкторы с аргументами, которые задают значения нестатических полей через СЕТТЕРЫ.
  • Разработать класс Rectangle в пакете ru.isu.math.graphics для представления прямоугольников: класс должен быть унаследован от класса «Shape».
  • В классе Rectangle должны быть объявлены и реализованы методы, переопределяющие абстрактные методы, унаследованные от класса Shape.
  • Конструкторы подкласса Rectangle должны содержать явные вызовы конструктора суперкласса Shape.
  • В классе Rectangle должны быть переопределены методы toString, equals и hashCode, унаследованные от класса Object.
  • Разработать отдельный Main-класс в пакете ru.isu.math.graphics с main-методом, в котором необходимо создать несколько объектов класса Rectangle с помощью доступных конструкторов и через доступное поведение (перемещение, поворот, масштабирование) объектов класса Rectangle изменить их состояние. Вычислить площади созданных прямоугольников. Rectangle изменить их состояние. Вычислить площади созданных прямоугольников.
  • Продемонстрировать строковое представление объекта класса Rectangle, возвращаемое методом toString.
  • Продемонстрировать выполнение контракта методов equals и hashCode на примере сравнения двух эквивалентных по состоянию объектов класса Rectangle.
  • Продемонстрировать работу отладчика в IDE. Установить точку останова в вашем коде. Запустить отладчик. Показать какие значения имеют локальные переменные в момент останова. Сделать один или несколько шагов так, продемонстрировать изменение значений локальных переменных при пошаговом выполнении кода. Установить курсор в некоторой строке вашего кода и выполнить код до этой строки; показать какие значения имеют локальные переменные в этот момент. Возобновить выполнение программы в обычном режиме.

7-2 — 1 балл

  • Разработать класс Ellipse в пакете ru.isu.math.graphics для представления эллипсов: класс должен быть унаследован от класса Shape.
  • В классе Ellipse должны быть реализованы методы, переопределяющие абстрактные методы, унаследованные от класса Shape.
  • В классе Ellipse должны быть переопределены методы toString, equals и hashCode, унаследованные от класса Object.
  • Конструкторы подкласса Ellipse должны содержать явные вызовы конструктора суперкласса Shape.
  • Разработать класс Triangle для представления треугольников: класс должен быть унаследован от класса Shape; в классе должно быть переопределение абстрактных методов из класса Shape.
  • В классе Triangle должны быть реализованы методы, переопределяющие абстрактные методы, унаследованные от класса Shape.
  • В классе Triangle должны быть переопределены методы toString, equals и hashCode, унаследованные от класса Object.
  • Конструкторы подкласса Triangle должны содержать явные вызовы конструктора суперкласса Shape.
  • Разработать отдельный Main-класс в пакете ru.isu.math.graphics с main-методом, в котором необходимо создать несколько объектов классов Rectangle, Ellipse, Triangle и нарисовать соответствующие им прямоугольники, эллипсы, треугольники в файле изображения формата PNG.
  • Добавить в класс Rectangle статический метод для конвертирования прямоугольников в эллипсы.
  • Добавить в класс Ellipse статический метод для конвертирования эллипсов в прямоугольники.
  • Продемонстрировать строковые представления объектов классов Ellipse и Triangle, возвращаемые соответствующими переопределениями метода toString.
  • Продемонстрировать выполнение контракта методов equals и hashCode на примере сравнения двух эквивалентных по состоянию объектов классов Ellipse и Triangle.
  • Разработать отдельный Main-класс в пакете ru.isu.math.graphics с main-методом, в котором необходимо создать несколько объектов класса Rectangle, объектов класса Ellipse и объектов класса Triangle. Добавить созданные объекты в массив типа Shape[]. Перебрав данный массив, нарисовать соответствующие им прямоугольники, эллипсы и треугольники в файле изображения формата PNG; при этом сделать серию изображений с масштабированием, перемещением и вращением фигур (каждое изображения серии сохранить в отдельный файл формата PNG).

7-3 — 2 балла

  • Разработать приложение с интерфейсом командной строки (CLI) для создания и управления простыми графическими фигурами (прямоугольники, эллипсы, треугольники) с возможностью экспорта результата в растровое изображение.
  • Дополнить классы Shape, Rectangle, Ellipse и Triangle СТРОИТЕЛЯМИ для пошагового создания их объектов.
  • Программа должна поддерживать ПОШАГОВОЕ создание трех типов фигур: прямоугольники, эллипсы и треугольники с помощью СТРОИТЕЛЕЙ. (Фигуры должны быть представлены объектами соответственно классов Rectangle, Ellipse и Triangle, описанных в двух предыдущих заданиях.)
  • Программа должна позволять перемещать выбранной фигуры на заданное смещение по осям X и Y.
  • Программа должна позволять вращать выбранную фигуру на заданный угол в градусах.
  • Программа должна позволять увеличивать или уменьшать размер существующей фигуры в заданное количество раз.
  • Программа должна сохранять все созданные фигуры в файл изображения в формате PNG.
  • Сопроводить исходный код Javadoc-комментариями и сгенерировать HTML-документацию с помощью команды javadoc.
  • Собрать проект в виде запускаемого JAR-файла и продемонстрировать его работу.

Поддерживаемые команды:

  • Создание фигур
    • create rectangle <x> <y> <width> <height> <color>
    • create ellipse <x> <y> <width> <height> <color>
    • create rectangle <x> <y> <side> <color>
    • create ellipse <x> <y> <side> <color>
    • Пример: create rectangle 50 50 100 #FF0000 — создает прямоугольник с вершиной в (50, 50), шириной 100 и высотой 100, заполненный красным цветом.
    • После создания фигура получает уникальный числовой идентификатор (ID). Этот ID используется для управления фигурой в последующих командах.
  • Перемещение фигур
    • move <id> <dx> <dy>
    • Пример: move 1 10 -5 — перемещает фигуру с ID=1 на 10 пикселей вправо и на 5 пикселей вверх.
  • Вращение фигур
    • rotate <id> <angle>
    • Пример: rotate 1 90 — вращает фигуру с ID=1 на 90 градусов.
  • Масштабирование фигур
    • scale <id> <factor>
    • Пример: scale 1 1.5 — увеличивает фигуру с ID=1 в 1.5 раза.
    • Пример: scale 2 0.5 — уменьшает фигуру с ID=2 в 2 раза.
  • Экспорт в изображение
    • export <filename.png>
    • Пример: export my_drawing.png — сохраняет изображение в файл my_drawing.png в текущей директории.
  • Прочие команды
    • list — выводит в консоль список всех фигур с их ID, типами и параметрами.
    • exit — завершает работу приложения.

Вопросы

  1. Как представлен пакет физически?
  2. Как пакет определяет пространство имен?
  3. Какое полное имя имеет ваш класс?
  4. Почему в имени пакета используется доменное имя, записанное в обратном порядке?
  5. Какие уровни доступа вы использовали при реализации ваших классов?
  6. Выделите модификаторы доступа в вашем коде?
  7. Какой уровень доступа имеет то или иное поле?
  8. Какой уровень доступа имеет тот или иной метод?
  9. Какой уровень доступа имеет тот или иной конструктор?
  10. Какие методы доступа вы реализовали?
  11. Выделите объявления аксессоров в вашем коде?
  12. Какая пара геттера и сеттера отвечает за доступ к тому или иному закрытому полю?
  13. Как можно реализовать доступ READ-ONLY к полям?
  14. Как вы реализовали валидацию значений в сеттерах?
  15. Объясните применяемый вами уровень доступа, как он ограничивает доступ, где будет доступен класс, поле, метод или конструктор, к которому применяется этот уровень доступа?
  16. Применяете ли вы уровень доступа по умолчанию?
  17. Наследуют ли ваши классы методы класса Object?
  18. Какие методы класса Object вы переопределили?
  19. Что возвращает метод toString?
  20. Что возвращает метод equals?
  21. Что возвращает метод hashCode?
  22. Объясните контракт пары методов equals-hashCode?
  23. Используется ли вашем коде полиморфизм?
  24. Используется ли вашем коде полиморфизм?
  25. Используется ли вашем коде инкапсуляция?
  26. Есть ли в вашем коде вложенные классы?
  27. Есть ли в вашем коде внутренние классы?
  28. Используете ли вы строителей для пошагового создания объектов?
  29. Создаете ли вы объекты пошагово с помощью строителей?
  30. Как вы ограничили доступ к вашим вложенным классам?

Дополнительные ресурсы