Как стать автором
Обновить
1047.95
OTUS
Цифровые навыки от ведущих экспертов

Sealed классы Java в действии: создание надежных и безопасных приложений

Время на прочтение16 мин
Количество просмотров9.7K
Автор оригинала: A N M Bazlur Rahman

Sealed (закрытые или запечатанные) классы были представлены в Java 15 в качестве способа ограничить иерархию наследования класса или интерфейса.

Sealed класс или интерфейс ограничивает набор классов, которые могут наследовать или реализовывать его, что может помочь предотвратить потенциальные ошибки и сделать код более удобным в сопровождении.

Предположим, вы создаете приложение для онлайн магазина, поддерживающее несколько разных способов оплаты, а именно кредитные карты, PayPal и биткоины. Вы можете определить sealed класс PaymentMethod, который имеет список допустимых подклассов (после ключевого слова permits) под каждый метод оплаты:

public sealed class PaymentMethod permits CreditCard, PayPal, Bitcoin {
   // Члены класса
}

В этом примере PaymentMethod — это sealed класс, который позволяет CreditCard, PayPal и Bitcoin расширять его. Sealed класс может разрешить любому количеству классов расширить его, указав их в списке, разделенном запятыми, после ключевого слова permits.

И это только один пример того, как использование sealed класса может облегчить нашу жизнь.

Итак, давайте разбираться!

Создание иерархии закрытого типа

Sealed классы могут создавать иерархию закрытого типа. Речь идет об ограниченном наборе классов, которые не могут быть расширены или реализованы вне определенного пакета.

Это гарантирует, что могут быть использованы только классы из определенного набора, и предотвращает нежелательные расширения или реализации.

package ca.bazlur
public sealed class Animal permits Cat, Dog {
   // Определение класса
}
public final class Cat extends Animal {
   // Определение класса
}
public final class Dog extends Animal {
   // Определение класса
}

В этом примере Animal является sealed классом, который позволяет расширять его только классам Cat и Dog.

Любая другая попытка расширения Animal приведет к ошибке компиляции.

Создание ограниченного набора реализаций

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

public sealed interface Shape permits Circle, Square {
   double getArea();
}
public final class Circle implements Shape {
   // Определение класса
}
public final class Square implements Shape {
   // Определение класса
}

В этом примере Shape — это sealed интерфейс, который позволяет реализовать его только классам Circle и Square.

Это гарантирует, что создать любую другую реализацию Shape будет невозможно.

Улучшение паттерн матчинга в switch-конструкциях

Sealed классы также можно использовать для улучшения паттерн матчинга в операторах ветвления.

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

public sealed abstract class PaymentMethod permits CreditCard, DebitCard, PayPal {
   // Определение класса
}
public class PaymentProcessor {
   public void processPayment(PaymentMethod paymentMethod, double amount) {
       switch (paymentMethod) {
           case CreditCard cc -> {
               // Обработка платежа кредитной картой
           }
           case DebitCard dc -> {
               // Обработка платежа дебетовой картой
           }
           case PayPal pp -> {
               // Обработка платежа по PayPal
           }
         
       }
   }
}

В этом примере PaymentMethod является sealed классом, который могут расширять классы CreditCard, DebitCard и PayPal.

Метод processPayment в классе PaymentProcessor использует оператор ветвления switch с паттерн матчингом для обработки различных способов оплаты.

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

Реализация конечного автомата

Sealed классы можно использовать для реализации конечного автомата (или стейт машины) — вычислительной модели, которая определяет поведение системы в ответ на ряд входных данных. В конечном автомате каждое состояние представлено sealed классом, а переход между состояниями реализован с помощью методов, возвращающих новое состояние.

public sealed class State permits IdleState, ActiveState, ErrorState {
   public State transition(Input input) {
       // Логика перехода
   }
}
public final class IdleState extends State {
   // Определение класса
}
public final class ActiveState extends State {
   // Определение класса
}
public final class ErrorState extends State {
   // Определение класса
}

В этом примере State является sealed классом, который допускает подклассы IdleState, ActiveState и ErrorState.

Метод transition отвечает за переход между состояниями на основе введенного input.

Использование sealed классов гарантирует, что конечный автомат структурирован должным образом и может быть расширен только ограниченным набором классов.

Создание ограниченного набора исключений

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

public sealed class DatabaseException extends Exception permits ConnectionException, QueryException {
   // Определение класса
}
public final class ConnectionException extends DatabaseException {
   // Определение класса
}
public final class QueryException extends DatabaseException {
   // Определение класса
}

В этом примере DatabaseException является sealed классом, который допускает подклассы ConnectionException и QueryException.

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

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

Sealed классы также могут управлять доступом к конструкторам, что позволяет реализовать определенный набор инвариантов для класса.

public sealed class Person {
   private final String name;
   private final int age;
   private Person(String name, int age) {
       this.name = name;
       this.age = age;
   }
   public static final class Child extends Person {
       public Child(String name, int age) {
           super(name, age);
           if (age >= 18) {
               throw new IllegalArgumentException("Children must be under 18 years old.");
           }
       }
   }
   public static final class Adult extends Person {
       public Adult(String name, int age) {
           super(name, age);
           if (age < 18) {
               throw new IllegalArgumentException("Adults must be 18 years old or older.");
           }
       }
   }
}

В этом примере Person — это sealed класс с двумя подклассами: Child и Adult.

Конструкторы для классов Child и Adult указаны как public, а конструктор для Person указан как private, в результате чего создание инстансов Person возможно только через его подклассы.

Это позволяет Person обеспечить соблюдение инварианта о том, что дети должны быть моложе 18 лет, а взрослым должно быть 18 лет или больше.

Повышение безопасности кода

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

public sealed class SecureCode permits TrustedCode {
   // Определение класса
}
// Надежный код
public final class TrustedCode extends SecureCode {
   // Определение класса
}
// Ненадежный код
public final class UntrustedCode extends SecureCode {
   // Определение класса
}

В этом примере SecureCode является sealed классом, который допускает только подкласс TrustedCode.

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

Полиморфизм с исчерпывающим паттерн матчингом

Sealed классы также можно использовать для реализации полиморфизма с исчерпывающим паттерн матчингом.

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

public sealed class Shape permits Circle, Square {
   // Определение класса
}
public final class Circle extends Shape {
   // Определение класса
}
public final class Square extends Shape {
   // Определение класса
}
public void drawShape(Shape shape) {
   switch (shape) {
       case Circle c -> c.drawCircle();
       case Square s -> s.drawSquare();
   }
}

В этом примере Shape — это sealed класс, который позволяет классам Circle и Square расширять его.

Метод drawShape использует паттерн матчинг для отрисовки фигуры, гарантируя, что все возможные подтипы Shape охватываются оператором switch.

Повышение читабельности кода

Sealed классы также можно использовать для улучшения читабельности кода за счет четкого определения набора возможных подтипов.

Ограничивая набор возможных подтипов, разработчикам легче анализировать код и понимать его поведение.

public sealed class Fruit permits Apple, Banana, Orange {
   // Определение класса
}
public final class Apple extends Fruit {
   // Определение класса
}
public final class Banana extends Fruit {
   // Определение класса
}
public final class Orange extends Fruit {
   // Определение класса
}

В этом примере Fruit — это sealed класс, который допускает подклассы Apple, Banana и Orange.

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

Соблюдение контрактов API

Sealed классы также можно использовать для обеспечения соблюдения контрактов API, которые представляют собой набор ожиданий потребителей API в отношении его поведения.

Используя sealed классы, поставщики API могут гарантировать, что набор возможных подтипов четко определен и задокументирован, что повышает удобство использования и поддерживаемость API.

public sealed class Vehicle permits Car, Truck, Motorcycle {
   // Определение класса
}
public final class Car extends Vehicle {
   // Определение класса
}
public final class Truck extends Vehicle {
   // Определение класса
}
public final class Motorcycle extends Vehicle {
   // Определение класса
}

В этом примере Vehicle — это sealed класс, который позволяет расширять его классам Car, Truck и Motorcycle.

Используя sealed класс для определения набора возможных типов транспортных средств, поставщики API могут гарантировать, что контракт API четко определен и может быть соблюден.

Предотвращение нежелательных расширений подтипов

Наконец, sealed классы также можно использовать для предотвращения нежелательных расширений подтипов.

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

public sealed class PaymentMethod {
   // Определение класса
}
public final class CreditCard extends PaymentMethod {
   // Определение класса
}
public final class DebitCard extends PaymentMethod {
   // Определение класса
}
public class StolenCard extends PaymentMethod {
   // Определение класса
}

В этом примере PaymentMethod является sealed классом, который не позволяет никаким подтипам расширять его.

Это предотвращает создание класса StolenCard, который не соответствует предполагаемому поведению класса PaymentMethod.

Повышение типобезопасности коллекций

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

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

public sealed interface Animal permits Dog, Cat, Bird {
   // Определение интерфейса
}
public final class Dog implements Animal {
   // Определение класса
}
public final class Cat implements Animal {
   // Определение класса
}
public final class Bird implements Animal {
   // Определение класса
}

В этом примере Animal — это sealed интерфейс, который позволяет реализовать его классам Dog, Cat и Bird.

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

List<!--? extends Animal--> animals = List.of(new Dog(), new Cat(), new Bird());

В этом примере animals — это список, содержащий элементы, которые расширяют интерфейс Animal.

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

Содействие развитию API

Sealed классы также могут способствовать развитию API, то есть обновлению API путем добавления или удаления фич.

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

public sealed class Animal permits Dog, Cat {
   // Определение класса
}
public final class Dog extends Animal {
   // Определение класса
}
public final class Cat extends Animal {
   // Определение класса
}
public final class Bird extends Animal {
   // Определение класса
}

В этом примере Animal — это sealed класс, который позволяет Dog и Cat расширять его.

Поскольку Animal является sealed классом, добавление нового подтипа Bird будет критическим изменением и потребует изменения версии API.

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


Вот еще несколько более конкретных и реальных примеров того, как sealed классы можно использовать в Java-разработке:

Представление различных типов сообщений

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

Sealed классы могут представлять различные типы сообщений и гарантировать, что каждый тип структурирован должным образом и типобезопасен.

public sealed interface Message permits RequestMessage, ResponseMessage {
   // Определение интерфейса
}
public final class RequestMessage implements Message {
   // Определение класса
}
public final class ResponseMessage implements Message {
   // Определение класса
}

В этом примере Message — это sealed интерфейс, который позволяет классам RequestMessage и ResponseMessage реализовать его.

Используя sealed классы, разработчики могут гарантировать, что каждый тип сообщения является четко определенным и типобезопасным, что может помочь предотвратить ошибки и повысить удобство сопровождения кода.

Определение набора доменных объектов

В предметно‑ориентированном проектировании (DDD) доменные объекты представляют концепции и сущности предметной области.

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

public sealed interface OrderItem permits ProductItem, ServiceItem {
   // Определение интерфейса
}
public final class ProductItem implements OrderItem {
   // Определение класса
}
public final class ServiceItem implements OrderItem {
   // Определение класса
}

В этом примере OrderItem — это sealed интерфейс, который позволяет классам ProductItem и ServiceItem реализовать его.

Используя sealed классы, разработчики могут гарантировать, что каждый доменный объект четко определен и имеет ограниченный набор возможных подтипов, что может помочь предотвратить появление ошибок и сделать код более удобным в сопровождении.

Представление различных типов пользователей

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

public sealed class User permits Customer, Employee, Admin {
   // Определение класса
}
public final class Customer extends User {
   // Определение класса
}
public final class Employee extends User {
   // Определение класса
}
public final class Admin extends User {
   // Определение класса
}

В этом примере User — это sealed класс, который позволяет допускает подклассы Customer, Employee и Admin.

Используя sealed классы, разработчики могут гарантировать, что каждый пользовательский тип четко определен и типобезопасен, что может помочь предотвратить ошибки и сделать код более удобным в сопровождении.

Определение ограниченного набора типов ошибок

Во многих системах ошибки сигнализируют о том, что во время выполнения программы что‑то пошло не так.

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

public sealed class Error permits NetworkError, DatabaseError, SecurityError {
   // Определение класса
}
public final class NetworkError extends Error {
   // Определение класса
}
public final class DatabaseError extends Error {
   // Определение класса
}
public final class SecurityError extends Error {
   // Определение класса
}

В этом примере Error — это sealed класс, который позволяет классам NetworkError, DatabaseError и SecurityError расширять его.

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

Определение ограниченного набора HTTP-методов

Во многих веб‑приложениях взаимодействие с веб‑ресурсами происходит посредством HTTP‑методов.

Sealed классы могут определять ограниченный набор HTTP‑методов и гарантировать, что каждый метод четко определен и имеет ограниченный набор возможных подтипов.

public sealed class HttpMethod permits GetMethod, PostMethod, PutMethod {
   // Определение класса
}
public final class GetMethod extends HttpMethod {
   // Определение класса
}
public final class PostMethod extends HttpMethod {
   // Определение класса
}
public final class PutMethod extends HttpMethod {
   // Определение класса
}

В этом примере HttpMethod — это sealed класс, который позволяет классам GetMethod, PostMethod и PutMethod расширять его.

Используя sealed классы для определения ограниченного набора HTTP‑методов, разработчики могут гарантировать, что каждый метод четко определен и имеет ограниченный набор возможных подтипов.

Это может помочь сделать код более удобным в сопровождении и более простым для понимания.

Определение ограниченного набора параметров конфигурации

Во многих системах для управления поведением программы используются специальные параметры конфигурации.

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

public sealed class ConfigurationParameter permits DebugMode, LoggingLevel {
   // Определение класса
}
public final class DebugMode extends ConfigurationParameter {
   // Определение класса
}
public final class LoggingLevel extends ConfigurationParameter {
   // Определение класса
}

В этом примере ConfigurationParameter является sealed классом, который позволяет классам DebugMode и LoggingLevel расширять его.

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

Это может помочь сделать код более удобным в сопровождении и более простым для понимания.

Определение ограниченного набора стратегий доступа к базе данных

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

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

public sealed class DatabaseAccessStrategy permits JdbcStrategy, JpaStrategy, HibernateStrategy {
   // Определение класса
}
public final class JdbcStrategy extends DatabaseAccessStrategy {
   // Определение класса
}
public final class JpaStrategy extends DatabaseAccessStrategy {
   // Определение класса
}
public final class HibernateStrategy extends DatabaseAccessStrategy {
   // Определение класса
}

В этом примере DatabaseAccessStrategy — это sealed класс, который допускает подклассы JdbcStrategy, JpaStrategy и HibernateStrategy.

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

Определение ограниченного набора методов аутентификации

Во многих системах для проверки личности пользователей используется аутентификация.

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

public sealed class AuthenticationMethod permits PasswordMethod, TokenMethod, BiometricMethod {
   // Определение класса
}
public final class PasswordMethod extends AuthenticationMethod {
   // Определение класса
}
public final class TokenMethod extends AuthenticationMethod {
   // Определение класса
}
public final class BiometricMethod extends AuthenticationMethod {
   // Определение класса
}

В этом примере AuthenticationMethod является sealed классом, который позволяет PasswordMethod, TokenMethod и BiometricMethod расширять его.

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

Это может помочь сделать код более удобным в сопровождении и более простым для понимания.

Заключение

В заключение, sealed классы Java — это мощная фича, которая может помочь вам создать более надежный и удобный в сопровождении код, благодаря ограничению иерархии наследования ваших классов и интерфейсов.

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

Освоив sealed классы Java, вы сможете вывести свои навыки программирования на новый уровень, создавая более качественное программное обеспечение.


Приглашаем всех желающих на открытое занятие «Введение в Java Collections: списки и O(n)». На занятии вы познакомитесь со списками в Java и изучите особенности их использования, получите ответ на самый популярный вопрос на собеседованиях по Java. В результате занятия будете знать основные особенности списков в java и скорости их работы. Записаться можно по ссылке.

Теги:
Хабы:
Всего голосов 10: ↑8 и ↓2+6
Комментарии19

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS