Orientação a objetos: Classes Seladas
04/02/2021
André Baltieri

Na orientação a objetos, uma classe selada não pode ser extendida e tomar esta decisão design pode ter diferentes impactos no consumo do seu código. Neste artigo vamos entender melhor quando utilizá-las.

Sobre o Flunt

Flunt é um pacote que criei para aplicar validações e o padrão Domain Notification. Com o tempo ele se tornou popular e tem até versões em Java/JS, o que é bem legal.

Modificador Sealed

No C# e em diversas outras linguagens orientadas à objetos nós temos um modificador chamado sealed que previne a classe de ser extendida.

Por tempos eu fiquei me perguntando onde eu usaria este modificador, qual seria um caso real para ele, e em 2017 acabei utilizando no Flunt.

Para tornar uma classe selada (Impedir que a mesma seja extendida), basta utilizar o seguinte exemplo.

public sealed class Notification 
{
    ...
}

Desta forma, se tentarmos criar uma nova classe que derive de Notification nos depararemos com o seguinte erro.

public class CustomNotification : Notification
{
    ...
}

ERRO

'Program.CustomNotification': cannot derive from sealed type 'Program.Notification'

Motivações

Tudo bem, você já entendeu o que é o sealed e como utilizar, mas agora vem a motivação para o seu uso.

Nas primeiras versões do Flunt eu não selei a classe Notification o que permitiu que outras classes fossem criadas sobre ela, e durante uma passagem por um dos meus clientes, notei que ele possuiam customizações destas notificações.

Não vou me recordar 100% agora, mas era algo como isto.

// Classe do Flunt
public class Notification 
{
    ...
}

// Derivações (No cliente)
public class ErrorNotification 
{
    ...
}

public class WarningNotification 
{
    ...
}

public class SuccessNotification 
{
    ...
}

Cada uma destas derivações tinha suas próprias customizações, seus métodos e suas propriedades. Como temos a possibilidade de upcast na OOP, estas classes também podiam substituir sua classe pai, a Notification.

Mas qual o problema nisso? Qual problema em criar customizações?

Algumas coisas não foram feitas para serem customizadas ou você não quer permitir que elas sejam feitas de formas diferentes.

A ideia por trás das notificações é ter uma padronização nas APIs por exemplo, onde todo erro 400 (BadRequest) retorna uma lista de notificações, algo parecido com isto.

{
    "status": 400,
    "message": "Falha na requisição",
    "data": null,
    "notifications": [
        { "Name": "Nome inválido" },
        { "Document": "CPF inválido" },
    ]
}

Desta forma, qualquer aplicação que consuma a API pode se preparar para o mesmo padrão, isto facilita a vida do Frontend que pode ter uma tipagem sobre o retorno de qualquer método da API, em suma, qualquer requisição a API resulta neste modelo.

Focando apenas no nó notifications, a classe Notification do Flunt deveria garantir que seu resultado fosse sempre neste padrão, ou seja, quem consome a API sempre pode esperar uma lista de notificações contendo chave/valor.

Porém, ao customizar (Extender) as notificações, o resultado podia variar de acordo com o tipo da notificação.

{
    "status": 400,
    "message": "Falha na requisição",
    "data": null,
    "notifications": [
        { "Name": "Nome inválido" },
        { "Document": "CPF inválido" },
    ]
}
{
    "status": 200,
    "message": "Cadastro realizado",
    "data": null,
    "notifications": [
        { "id": 1, "type": "success", "value":"Cadastro OK" }
    ]
}
{
    "status": 200,
    "message": "Cadastro realizado",
    "data": null,
    "notifications": [
        { "type": "success", "key":"XYZ", "value":"XPTO" }
    ]
}

Isto geraria no mínimo um switch no Frontend para saber o tipo de notificação e então tratá-la de forma diferente. Ainda vale lembrar que o Frontend não está limitado a Web, tem Flutter, Xamarin e várias outras tecnologias/linguagens.

Deste primeiro cenário veio a motivação para selar a classe Notification e padronizar todas as notificações dos sistemas que usam o Flunt.

Outro exemplo

Como comentei no começo deste artigo, esta foi uma decisão que tomei em 2017, mas o que me motivou a escrever este artigo foi outro cenário que passei hoje.

Em alguns pontos críticos de leitura das Apps/Apis do balta eu utilizo o Dapper para acesso à dados, inclusive tem um curso sobre isto aqui.

Porém, o Dapper é um micro-orm que extende o IDbConnection apenas, o que significa que estamos literalmente por conta própria para tudo, inclusive para gerenciar a conexão com o banco de dados.

Desta forma, eu sempre crio um classe para gerenciar a conexão e garantir que não vou deixar conexão aberta no meu SQL Server.

Fazer isto não é uma tarefa difícil, basta implementar a interface IDisposable e chamar o método Connection.Dispose para encerrar a conexão.

public sealed class DbSession : IDisposable
{
    private Guid _id;
    public IDbConnection Connection { get; }
    public IDbTransaction Transaction { get; set; }

    public DbSession()
    {
        _id = Guid.NewGuid();
        Connection = new SqlConnection(Settings.ConnectionString);
        Connection.Open();
    }

    public void Dispose() => Connection?.Dispose();
}

Mas e aí, você notou algo de diferente nesta classe? Sim, ela está selada e você já deve imaginar o motivo!

Pois é, se tem uma classe que não quero que alguém extenda é esta, ela é uma das mais importantes e sensíveis da aplicação, e sem o sealed nela, poderiam ter várias versões da mesma.

Ainda pior, com a possibilidade de upcast poderiamos facilmente substituir a conexão padrão por uma customizada que talvez esquecesse de fechar a conexão com o banco de dados.

Imagina o problemão que não arrumaríamos?

Conclusão

As vezes, na correria do dia-a-dia tendemos a fazer o mais fácil, e em diversos cenários, optaríamos por criar uma nova versão de uma classe do que dar manutenção e melhorá-la.

O modificador sealed age neste ponto, ele te ajuda a travar as classes que você não quer que sejam extendidas e forçar as coisas a seguirem um padrão.

Mas calma, não é para sair selando todas as classes do sistema só porque aprendeu algo novo hoje!

Conheça o autor

André Baltieri

Microsoft MVP

Me dedico ao desenvolvimento de software desde 2003, sendo minha maior especialidade o Desenvolvimento Web. Durante esta jornada pude trabalhar presencialmente aqui no Brasil e Estados Unidos, atender remotamente times da ?ndia, Inglaterra e Holanda, receber 8x Microsoft MVP e realizar diversas consultorias em empresas e projetos de todos os tamanhos.

An unhandled error has occurred. Reload 🗙