Unes dades (o "data") és un un conjunt de valors que estan realacionats i que es gestionen com un conjunt.
Introducció
...
Entorn de treball
Crea la carpeta otter
:
> md otter
> cd otter
Crea el projecte otter
amb Gradle:
gradle init --package otter --project-name otter --java-version 21 --type kotlin-application --dsl kotlin --test-framework kotlintest --no-split-project --no-incubating --overwrite
Construeix el projecte i obre'l amb Idea:
> .\gradlew build
> idea .
Dades
Unes dades són un conjunt de valors que ens interessa tractar i que agrupem en una estructura de dades.
Un exemple és id
, name
i married
que agrupem en l'estructura Person
classDiagram direction LR class Person { id: Long name: String married: Boolean }
Aquesta estructura es pot implementar en Kotlin mitjançant un data class
.
Modifica el fitxer App.kt
package otter
data class Person(
val id: Long,
val name: String,
val married: Boolean,
)
fun main() {
}
Una estructura de dades sempre ha se ser inmutable: totes les propietats de la classe són val
.
A continuació crea algunes "dades":
package otter
data class Person(val id: Long, val name: String, val married: Boolean)
fun main() {
val david = Person(1, "David", false)
val esther = Person(2, "Esther", true)
println(esther)
}
Executa l'aplicació:
> .\gradlew run
Person(id=2, name=Esther, married=true)
Pots veure que un data class
té algunes implementacion molt útils.
Quan fas un "print" d'un objecte, aquest et mostra totes les propietats amb el seu valor:
> gradle run
...
Person(id=2, name=Esther, married=true)
Si comparo dos persones amb les mateixes propietats, el resultat és true
.
Modifica el fitxer AppTest.kt
package otter
import kotlin.test.*
class DataTest {
@Test
fun testPerson() {
assertEquals(Person(1, "David", false), Person(1, "David", false))
}
}
Si executo la tasca test
tot es correcte 👌
.\gradlew test
BUILD SUCCESSFUL in 1s
Com que les dades són inmutables, per modificar unes dades has de crear un objecte nou.
Amb un data class
pots utilizar les dades antigues per crear dades noves indicant només la nova informació:
package otter
data class Person(val id: Long, val name: String, val married: Boolean, )
fun main() {
val esther = Person(2, "Esther", true)
require(esther.married)
val estherDivorced = esther.copy(married = false)
require(esther.name == "Esther")
require(!estherDivorced.married)
}
Amb la funció require
dic a l'aplicació que el que dic és true
o l'aplicació a de petar amb un "Failed requirement" 💣🔥 !!.
Modificar el fitxer App.kt
i equivoca't:
package otter
data class Person(val id: Long, val name: String, val married: Boolean, )
fun main() {
val esther = Person(2, "Esther", true)
require(!esther.married)
}
Donc si ... 🙄
.\gradlew run
> Task :app:run FAILED
Exception in thread "main" java.lang.IllegalArgumentException: Failed requirement.
at otter.AppKt.main(App.kt:8)
at otter.AppKt.main(App.kt)
FAILURE: Build failed with an exception.
...
L'Esther encara no s'ha divorciat 😒
Mermaid
Mermaid ens permet crear el nostre diagrama de classes.
Instal.la el plugin Mermaid:
- Ctrl + Alt + S per obrir el menú "Settings"
- Selecciona Plugins
- Busca "Mermaid" i instal.la
Crea el fitxer REAMDE.md
.
A https://gitlab.com/xtec/kotlin/data/-/blob/main/README.md?ref_type=heads tens un exemple d'un diagrama de classes fet amb Mermaid.
Crea el diagrama de la classe Person
:
classDiagram
direction LR
class Person {
id: Long
name: String
married: Boolean
}
Composició
Un objecte pot estar composat d'altres objectes definits per altres classes.
Per exemple, una persona pot tenir una adreça:
classDiagram direction LR class Address { street: String city: String zipcode: String country: String = "Spain" } class Person { id: Long name: String married: Boolean } Person --> Address
En Kotlin tots són objectes!
Per tant, com que Long
, String
, etc. són objectes, no hi diferència entre els teus objectes i els objectes predefinits per Kotlin.
Modifica el fitxer App.kt
:
package otter
data class Address(
val street: String,
val city: String,
val zipcode: String,
val country: String = "Spain",
)
data class Person(
val id: Long,
val name: String,
val address: Address,
)
fun main() {
}
Crea el test corresponent:
package otter
class AppTest {
@Test
fun test_person() {
val david = Person(1, "david", Address("C/Miquel Angel", "Barcelona", "08028"))
assertEquals(david.address.city, "Barcelona")
}
}
Activitat
1.- Modifica el fitxer README.md
amb aquest nou disseny:
classDiagram direction LR class User { id: Long name: String } class Message { id: Long time: LocalTime text: String } Message --> User : from Message --> "1..*" User : to
class User {
id: Long
name: String
}
class Message {
id: Long
time: LocalTime
text: String
}
Message --> User : from
Message --> "1..*" User : to
2.- Modifica el fitxer App.kt
amb les classes corresponents:
package otter
import java.time.LocalTime
data class User(
val id: Long,
val name: String,
)
data class Message(
val id: Long,
val time: LocalTime,
val text: String,
val from: User,
val to: ArrayList<User>,
)
3.- Modifica el fitxer App.kt
amb el test corresponent
package otter
import java.time.LocalTime
import kotlin.test.*
class AppTest {
@Test
fun testMessage() {
val message = Message(23, LocalTime.now(), User(1, "David"),
arrayListOf(User(2, "Esther")))
assertEquals(message.from.name, "David")
}
}
Propietat derivada
Hi ha molts objectes que tenen propietats que es deriven d'altres propietats.
Per exemple, un rectangle té dos propietats bàsiques width
i length
.
Però també té altres propietats que es deriven d'aquestes propietats bàsiques, com poden ser area
i perimeter
.
package otter
data class Rectangle(val width: Int, val length: Int)
fun main() {
val r = Rectangle(4, 5)
val area = r.width * r.length
require(area == 20)
val perimeter = r.width * 2 + r.length * 2
require(perimeter == 18)
}
Aquestes propietats derivades es poden definir a la declaració de la classe.
Per exemple, podem definir la propietat derivada area
:
package otter
data class Rectangle(val width: Int, val length: Int) {
val area get() = this.width * this.length
}
fun main() {
val r = Rectangle(4, 5)
require(r.area == 20)
val perimeter = r.width * 2 + r.length * 2
require(perimeter == 18)
}
Si fas un print de l'objecte r
pots veure que no apareix la propietat derivada area
:
Rectangle(width=4, length=5)
Activitats
1.- Afegeix la propietat derivada perimeter
a la classe Rectangle
.
package otter
data class Rectangle(val width: Int, val length: Int) {
val area get() = this.width * this.length
val perimeter get() = this.width * 2 + this.length * 2
}
fun main() {
val r = Rectangle(4, 5)
require(r.area == 20)
require(r.perimeter == 18)
}
2.- Defineix una classe Circle
amb les propietats derivades més importants.
package otter
import kotlin.math.*
data class Circle(val radius: Double) {
val area get() = this.radius.pow(2) * PI
val circumference get() = this.diameter * PI
val diameter get() = this.radius * 2
}
fun main() {
val c = Circle(4.0)
require(c.diameter == 8.0)
fun Double.equalsDelta(other: Double) = abs(this - other) < 0.01
require(c.circumference.equalsDelta(25.13))
require(c.area.equalsDelta(50.26))
}
3.- A continuació tens el diagrama de la classe Patient
d'un Hospital:
classDiagram direction LR class Patient { id: Long givenName: String familyName: String /name: String birthDate: Date addmitted: Date /age: Int }
La classe Patient
té els atributs derivats name
i age
:
name
és el nom complet i es deriva degivenName
ifamilyName
.age
és calcula en funció debirthDate
i la data actual.
Modifica els fitxers README.md
, App.kt
i AppTest.kt
:
README.md
class Patient {
id: Long
givenName: String
familyName: String
/name: String
birthDate: Date
addmitted: Date
/age: Int
}
App.kt
package otter
import java.time.LocalDate
class Patient (
val id: Long,
val givenName: String,
val familyName: String,
val birthDate: LocalDate,
val addmitted: LocalDate,
) {
val name get() = "${this.givenName} ${this.familyName}"
val age get() = LocalDate.now().year - this.birthDate.year
}
fun main() {
}
AppTest.kt
package otter
import java.time.LocalDate
import kotlin.test.*
class AppTest {
@Test
fun testPatient() {
val esther = Patient(
2, "Esther", "Parra",
LocalDate.of(1973, 1, 9), LocalDate.of(2024, 7, 6)
)
assertEquals(esther.age, 51)
}
}
5.- A continuació tens el diagrama de classes del prèstec d'un llibre d'una Biblioteca:
classDiagram direction LR class Book { id: Long title: String author: String } class BookCopy { id: Long } BookCopy --> Book class BookBorrow { id: Long date: Date loadPeriod: Long /dueDate: Date /isOverdue: Boolean } BookBorrow --> BookCopy
La classe BookBorrow
té els atributs derivats dueData
i isOverdue
:
-
dueData
és la data de venciment de la devolució del llibre i es calcula en funció de la data del préstec i el període de préstec. -
overdue
indica si el prèstec a vençut i és calcula en funció dedueDate
i la data actual.
Modifica els fitxers README.md
, App.kt
i AppTest.kt
:
README.md
class Book {
id: Long
title: String
author: String
}
class BookCopy {
id: Long
}
BookCopy --> Book
class BookBorrow {
id: Long
date: Date
loadPeriod: Long
/dueDate: Date
/isOverdue: Boolean
}
BookBorrow --> BookCopy
App.kt
package otter
import java.time.LocalDate
class Book(
val id: Long,
val title: String,
val author: String
)
class BookCopy(
val id: Long,
val book: Book
)
class BookBorrow(
val id: Long,
val date: LocalDate,
val loadPeriod: Long,
val copy: BookCopy)
{
val dueDate get() = this.date.plusDays(loadPeriod)
val isOverdue get() = LocalDate.now().isAfter(this.dueDate)
}
fun main() {}
AppTest.kt
package otter
import java.time.LocalDate
import kotlin.test.*
class AppTest {
@Test
fun testBorrow() {
val borrow = BookBorrow(
1, LocalDate.of(2024, 7, 4), 40,
BookCopy(4, Book(3, "Tirant lo Blanc", "Joanot Martorell"))
)
assertEquals(borrow.dueDate, LocalDate.of(2024, 8, 13))
assertTrue(borrow.isOverdue)
}
}
Herència
TODO
Activitat (Client)
1.- Crea aquest diagrama:
classDiagram direction LR class Client { id: Long name: String }
class Client {
id: Long
name: String
}
2.- Crea el model de dades en el fitxer App.kt
:
package otter
data class Client(
val id: Long,
val name: String,
)
fun main() {}
3.- Crea els tests corresponents en el fitxer AppTest.kt
:
package otter
import kotlin.test.*
class DataTest {
@Test
fun Testclient() {
val david = Client(1, "David")
assertEquals(david, Client(1, "David"))
}
}
4.- Modifica el fitxer App.kt
per tal que demani el nom a l'usuari i crei un nou usuari:
package otter
data class Client(
val id: Long,
val name: String,
)
fun main() {
val name = input ???
val client = Client(2,name)
println(client)
}
Activitat (Sky)
Has de dissenyar el model de dades d'una aplicació que controla els vols d'avions dins del Cel Únic Europeu (Single European Sky, SES).
L'aplicació ha de tenir la informació dels diferents vols, on en cada vol consta l'aeroport d'origen i destí, data prevista d'enlairament i aterratge, avió que farà el vol, companyia aerea, etc.
A https://gitlab.com/xtec/kotlin/data tens un exemple d'una aplicació semblant.
TODO Amb tag sol no funciona
1.- Crea el diagrama de dades:
classDiagram direction TB class Flight { << 🐦🔥 >> id: Long 🔑 } class Plane { << ✈ >> id: Long 🔑 name: String seats: Int 💺 } class Airport { << ⛩ >> id: Long 🔑 name: String country: String } class Passenger { << 🙂 >> id: Long 🔑 name: String } Flight --> Plane Flight --> "1..* 🤗 🫣 🙄 😮" Passenger Flight --> Airport : departure 🛫 Flight --> Airport: arrival 🛬
2.- L'usuari ha de poder consultar els vols que tenen origen en un aeroport.
TODO altres