Home Open closed principle Kotlin
Post
Cancel

Open closed principle Kotlin

Принцип открытости-закрытости на Kotlin

Принцип открытости-закрытости - принцип объектно-ориентированного программирования, устанавливающий следующее положение: «программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для изменения»; это означает, что такие сущности могут позволять менять свое поведение без изменения их исходного кода.

В качестве примера возьмем контроллер, который пользуется внешним сервисом и получая json генерирует из него класс. Пусть это будет - https://jsonplaceholder.typicode.com/

У нас есть модель (дата-класс) - PostModel, которая позволяет хранить в нем полученные данные.

1
2
3
4
5
6
data class PostModel(
    val userId: Int,
    val id: Int,
    val title: String,
    val body: String,
)

Достает данные для нашего приложения класс JsonPlaceholderService (метод getPost) из вышеуказанного сервиса (при помощи библиотеки RestAssured) по адресу - https://jsonplaceholder.typicode.com/posts/$postId, где $postId идентификатор нашего поста.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class JsonPlaceholderService {
    
    val baseUrl: String = "https://jsonplaceholder.typicode.com/"
    
    fun getPost(postId: Int): PostModel {
        return RestAssured.given()
            .contentType(ContentType.JSON)
            .`when`()
            .get("${baseUrl}posts/$postId")
            .then()
            .extract().response().body.`as`(PostModel::class.java)
    }
    
}

…а контроллер получая их, обрабатывает и выводит на печать.

1
2
3
4
5
6
7
class JsonController(private val service: JsonPlaceholderService) {
    
    fun printPost(postId: Int = 1) {
        println(service.getPost(postId))
    }
    
}

Неплохо, но вот настало время для интеграционного тестирования, и нам нужно отключить внешний сервис, или же получать данные из другого места. Для этого потребуется поменять текущую реализацию JsonPlaceholderService. Но! В чистой архитектуре Дядя Боб не рекомендовал такой подход, поскольку нарушается Принцип открытости-закрытости — старый код не должен меняться. К тому же, вдруг начальству вновь понадобится {JSON} Placeholder Service?

Как решить проблему и не трогать старый код? При помощи интерфейса Карл! Создадим interface JsonRestService, который будет содержать свойство baseUrl для ссылки на внешний источник, и метод getPost()

1
2
3
4
5
6
interface JsonRestService {
    
    val baseUrl: String
    fun getPost(postId: Int): PostModel
    
}

Теперь изменим JsonPlaceholderService - унаследуемся от JsonRestService и переопределим метод и свойство из интерфейса, а также добавим еще один класс - DummyJsonServiceImpl, который будет “стучаться” на localhost и возвращать класс PostModel с тестовыми данными.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class JsonPlaceholderServiceImpl: JsonRestService {
    
    override val baseUrl: String = "https://jsonplaceholder.typicode.com/"
    
    override fun getPost(postId: Int): PostModel {
        return RestAssured.given()
            .contentType(ContentType.JSON)
            .`when`()
            .get("${baseUrl}posts/$postId")
            .then()
            .extract().response().body.`as`(PostModel::class.java)
    }
    
}

class DummyJsonServiceImpl: JsonRestService {

    override val baseUrl: String = "localhost:8080"
    
    override fun getPost(postId: Int): PostModel {
        return PostModel(
            userId = Random.nextInt(0, 10),
            id = postId,
            title = "Mock Title",
            body = "The some text from mock class"
        )
    }
    
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main(){
    
    val dataController = JsonController( JsonPlaceholderServiceImpl() )
    dataController.printPost(1)

    val testController = JsonController( DummyJsonServiceImpl() )
    testController.printPost(2)
    
}

class JsonController(private val service: JsonRestService) {
    
    fun printPost(postId: Int = 1) {
        println(service.getPost(postId))
    }
    
}

Далее, если захотим добавить ещё одну реализацию (внешний сервис), нам не придётся менять существующий код. Таким образом, достигается главная цель Принципа Открытости-Закрытости — сделать систему легко-расширяемой и обезопасить её от влияния изменений.

This post is licensed under CC BY 4.0 by the author.
Trending Tags