Абстрактни класове в Java

1. Общ преглед

Има много случаи при изпълнение на договор, при които искаме да отложим някои части от изпълнението, за да бъде завършено по-късно. Лесно можем да постигнем това в Java чрез абстрактни класове.

В този урок ще научим основите на абстрактните класове в Java и в какви случаи те могат да бъдат полезни .

2. Основни понятия за абстрактни класове

Преди да се впуснем кога да използваме абстрактен клас, нека разгледаме най-подходящите им характеристики :

  • Дефинираме абстрактен клас с абстрактния модификатор, предшестващ ключовата дума class
  • Абстрактен клас може да бъде подкласиран, но не може да бъде създаден
  • Ако клас дефинира един или повече абстрактни методи, тогава самият клас трябва да бъде обявен за абстрактен
  • Абстрактният клас може да декларира както абстрактни, така и конкретни методи
  • Подкласът, получен от абстрактния клас, трябва или да реализира всички абстрактни методи на базовия клас, или да бъде самият абстракт

За да разберем по-добре тези концепции, ще създадем прост пример.

Нека нашият основен абстрактен клас дефинира абстрактния API на настолна игра:

public abstract class BoardGame { //... field declarations, constructors public abstract void play(); //... concrete methods }

След това можем да създадем подклас, който реализира метода на възпроизвеждане :

public class Checkers extends BoardGame { public void play() { //... implementation } }

3. Кога да се използват абстрактни класове

Сега, нека да анализират няколко типични ситуации, в които трябва да се предпочитат абстрактни класове над интерфейси и конкретни класове:

  • Искаме да капсулираме някои общи функции на едно място (повторно използване на кода), които ще се споделят от множество свързани подкласове
  • Трябва частично да дефинираме API, който нашите подкласове могат лесно да разширят и усъвършенстват
  • Подкласовете трябва да наследят един или повече често срещани методи или полета със защитени модификатори за достъп

Нека имаме предвид, че всички тези сценарии са добри примери за пълно придържане към принципа Open / Closed, основано на наследство.

Освен това, тъй като използването на абстрактни класове имплицитно се занимава с базови типове и подтипове, ние също се възползваме от полиморфизма.

Обърнете внимание, че повторното използване на кода е много убедителна причина да се използват абстрактни класове, стига да се запази връзката „is-a“ в йерархията на класовете.

И Java 8 добавя още една бръчка с методи по подразбиране, които понякога могат да заемат мястото на необходимостта от създаване на абстрактен клас изобщо.

4. Примерна йерархия на четци на файлове

За да разберем по-ясно функционалността, която абстрактните класове внасят в таблицата, нека разгледаме друг пример.

4.1. Определяне на основен абстрактен клас

Така че, ако искахме да имаме няколко типа четци на файлове, бихме могли да създадем абстрактен клас, който да капсулира общото за четенето на файлове:

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); }

Имайте предвид, че сме направили filePath защитен, така че подкласовете да имат достъп до него, ако е необходимо. По-важното е, че оставихме нещо неотменено: как всъщност да се анализира ред текст от съдържанието на файла.

Планът ни е прост: докато нашите конкретни класове нямат специален начин да съхраняват пътя на файла или да преминават през файла, всеки от тях ще има специален начин да трансформира всеки ред.

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

4.2. Дефиниране на подкласове

Естествената реализация вероятно е тази, която преобразува съдържанието на файла в малки букви:

public class LowercaseFileReader extends BaseFileReader { public LowercaseFileReader(Path filePath) { super(filePath); } @Override public String mapFileLine(String line) { return line.toLowerCase(); } }

Или друг може да е такъв, който преобразува съдържанието на файл в главни букви:

public class UppercaseFileReader extends BaseFileReader { public UppercaseFileReader(Path filePath) { super(filePath); } @Override public String mapFileLine(String line) { return line.toUpperCase(); } }

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

4.3. Използване на подклас

И накрая, използването на клас, който наследява от абстрактния, не се различава от всеки друг конкретен клас:

@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); }

За по-голяма простота целевият файл се намира под папката src / main / resources / files . Следователно, ние използвахме loader на клас приложения за получаване на пътя на примерния файл. Чувствайте се свободни да разгледате нашия урок за зареждане на класове в Java.

5. Заключение

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

Както обикновено, всички примерни кодове, показани в този урок, са достъпни в GitHub.