Classes abstratas em Java
1. Visão geral
Existem muitos casos ao implementar um contrato em que queremos adiar algumas partes da implementação para serem concluídas posteriormente. Podemos facilmente fazer isso em Java através de classes abstratas.
In this tutorial, we’ll learn the basics of abstract classes in Java, and in what cases they can be helpful.
2. Principais conceitos para classes abstratas
Antes de mergulhar em quando usar uma classe abstrata,let’s look at their most relevant characteristics:
-
Definimos uma classe abstrata com o modificadorabstract precedendo a palavra-chaveclass
-
Uma classe abstrata pode ter uma subclasse, mas não pode ser instanciada
-
Se uma classe define um ou mais métodosabstract, então a própria classe deve ser declaradaabstract
-
Uma classe abstrata pode declarar métodos abstratos e concretos
-
Uma subclasse derivada de uma classe abstrata deve implementar todos os métodos abstratos da classe base ou ser ela própria abstrata
Para entender melhor esses conceitos, criaremos um exemplo simples.
Vamos fazer com que nossa classe abstrata base defina a API abstrata de um jogo de tabuleiro:
public abstract class BoardGame {
//... field declarations, constructors
public abstract void play();
//... concrete methods
}
Então, podemos criar uma subclasse que implementa o métodoplay :
public class Checkers extends BoardGame {
public void play() {
//... implementation
}
}
3. Quando usar classes abstratas
Agora,let’s analyze a few typical scenarios where we should prefer abstract classes over interfacese classes concretas:
-
Queremos encapsular algumas funcionalidades comuns em um único local (reutilização de código) que várias subclasses relacionadas compartilharão
-
Precisamos definir parcialmente uma API que nossas subclasses possam estender e refinar facilmente
-
As subclasses precisam herdar um ou mais métodos ou campos comuns com modificadores de acesso protegido
Vamos ter em mente que todos esses cenários são bons exemplos de aderência completa com base em herança paraOpen/Closed principle.
Além disso, uma vez que o uso de classes abstratas lida implicitamente com tipos e subtipos básicos, também estamos tirando proveito dePolymorphism.
Observe que a reutilização de código é um motivo muito convincente para usar classes abstratas, desde que o relacionamento "is-a" dentro da hierarquia de classes seja preservado.
EJava 8 adds another wrinkle with default methods, que às vezes pode substituir a necessidade de criar uma classe abstrata.
4. Um exemplo de hierarquia de leitores de arquivos
Para entender mais claramente a funcionalidade que as classes abstratas trazem para a mesa, vejamos outro exemplo.
4.1. Definindo uma classe abstrata de base
Então, se quisermos ter vários tipos de leitores de arquivo, podemos criar uma classe abstrata que encapsula o que é comum na leitura de arquivo:
public abstract class BaseFileReader {
protected Path filePath;
protected BaseFileReader(Path filePath) {
this.filePath = filePath;
}
public Path getFilePath() {
return filePath;
}
public List readFile() throws IOException {
return Files.lines(filePath)
.map(this::mapFileLine).collect(Collectors.toList());
}
protected abstract String mapFileLine(String line);
}
Observe que fizemosfilePath protected para que as subclasses possam acessá-lo, se necessário. Mais importante,we’ve left something undone: how to actually parse a line of text do conteúdo do arquivo.
Nosso plano é simples: embora nossas classes concretas não tenham, cada uma, uma maneira especial de armazenar o caminho do arquivo ou percorrer o arquivo, cada uma delas terá uma maneira especial de transformar cada linha.
À primeira vista,BaseFileReader pode parecer desnecessário. No entanto, é a base de um design limpo e facilmente extensível. A partir dele,we can easily implement different versions of a file reader that can focus on their unique business logic.
4.2. Definindo subclasses
Uma implementação natural é provavelmente aquela que converte o conteúdo de um arquivo em minúsculas:
public class LowercaseFileReader extends BaseFileReader {
public LowercaseFileReader(Path filePath) {
super(filePath);
}
@Override
public String mapFileLine(String line) {
return line.toLowerCase();
}
}
Ou outro pode ser aquele que converte o conteúdo de um arquivo em maiúsculas:
public class UppercaseFileReader extends BaseFileReader {
public UppercaseFileReader(Path filePath) {
super(filePath);
}
@Override
public String mapFileLine(String line) {
return line.toUpperCase();
}
}
Como podemos ver neste exemplo simples,each subclass can focus on its unique behavior sem a necessidade de especificar outros aspectos da leitura do arquivo.
4.3. Usando uma subclasse
Finalmente, usar uma classe que herda de uma abstrata não é diferente de qualquer outra classe concreta:
@Test
public void givenLowercaseFileReaderInstance_whenCalledreadFile_thenCorrect() throws Exception {
URL location = getClass().getClassLoader().getResource("files/test.txt")
Path path = Paths.get(location.toURI());
BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
assertThat(lowercaseFileReader.readFile()).isInstanceOf(List.class);
}
Para simplificar, o arquivo de destino está localizado na pastasrc/main/resources/files. Portanto, usamos um carregador de classes de aplicativos para obter o caminho do arquivo de exemplo. Sinta-se à vontade para verificarour tutorial on class loaders in Java.
5. Conclusão
Neste artigo rápido,we learned the basics of abstract classes in Java, and when to use them for achieving abstraction and encapsulating common implementation in one single place.
Como de costume, todos os exemplos de código mostrados neste tutorial estão disponíveisover on GitHub.