Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/kotlin/ru/otus/cars/Car.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ interface Car : CarInput {
*/
val carOutput: CarOutput

/**
* Горловина бака: единственный способ заправить машину снаружи
*/
val tankMouth: TankMouth

/**
* Получить оборудование
*/
Expand Down
10 changes: 5 additions & 5 deletions src/main/kotlin/ru/otus/cars/CarFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ object Togliatti : CarFactory {
return vaz
}

override fun buildCar(builder: CarBuilder, plates: Car.Plates): Car {
return when (builder) {
is Vaz2107.Companion -> return buildVaz2107(plates)
is Vaz2108.Companion -> return buildVaz2108(plates)
// when по sealed-иерархии CarBuilder: else не нужен, значение возвращается напрямую
override fun buildCar(builder: CarBuilder, plates: Car.Plates): Car =
when (builder) {
is Vaz2107.Companion -> buildVaz2107(plates)
is Vaz2108.Companion -> buildVaz2108(plates)
}
}
}
7 changes: 6 additions & 1 deletion src/main/kotlin/ru/otus/cars/CarOutput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ interface CarOutput {
* Скажи текущую скорость
*/
fun getCurrentSpeed(): Int
}

/**
* Скажи, сколько топлива в баке
*/
fun getFuelContents(): Int
}
43 changes: 43 additions & 0 deletions src/main/kotlin/ru/otus/cars/GasStation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.otus.cars

/**
* Заправка. Одна на всех - поэтому object.
*/
object GasStation {
/**
* Стандартная порция топлива
*/
const val DEFAULT_LITERS = 40

/**
* Заправить машину [liters] литрами.
* Метод залива выбирается по горловине, взрыв бака обрабатывается.
*/
fun refuel(car: Car, liters: Int = DEFAULT_LITERS) {
val mouth = car.tankMouth
mouth.open()
try {
// when по sealed-иерархии: else не нужен
when (mouth) {
is PetrolMouth -> {
println("Наливаем $liters л бензина...")
mouth.fuelPetrol(liters)
}
is LpgMouth -> {
println("Качаем $liters л газа...")
mouth.fuelLpg(liters)
}
}
} catch (e: TankExplodedException) {
println("ЧП на заправке: ${e.message}")
} finally {
// Крышку закрываем в любом случае - даже если бак рванул
mouth.close()
}
}
}

/**
* Заправить сразу всю коллекцию машин
*/
fun Iterable<Car>.refuelAll(liters: Int = GasStation.DEFAULT_LITERS) = forEach { car -> GasStation.refuel(car, liters) }
71 changes: 71 additions & 0 deletions src/main/kotlin/ru/otus/cars/Tank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package ru.otus.cars

/**
* Бак машины. Наружу торчит только этот интерфейс —
* реализация спрятана внутри файла.
*/
interface Tank {
/**
* Горловина, установленная на баке.
* Она определяет, что именно в бак заливают.
*/
val mouth: TankMouth

/**
* Сколько топлива сейчас в баке
*/
fun getContents(): Int

/**
* Принять топливо. Вызывается горловиной, а не водителем.
*/
fun receiveFuel(liters: Int)

/**
* Специалист по топливным системам: только он умеет собрать бак с горловиной.
*/
companion object Assembler {
/**
* Приварить бензиновую горловину к любому баку
*/
fun petrolMouthFor(tank: Tank): TankMouth = PetrolMouth(tank)

/**
* Приварить газовую горловину к любому баку
*/
fun lpgMouthFor(tank: Tank): TankMouth = LpgMouth(tank)

/**
* Бак под бензин
*/
fun assemblePetrolSystem(): Tank = FuelTank().apply { mouth = petrolMouthFor(this) }

/**
* Бак под сжиженный газ
*/
fun assembleLpgSystem(): Tank = FuelTank().apply { mouth = lpgMouthFor(this) }
}
}

/**
* Реализация бака.
* private на уровне файла: за пределами Tank.kt этого класса просто не существует.
*/
private class FuelTank : Tank {
// Текущий запас топлива
private var contents: Int = 0

// Горловину ставит сборщик сразу после создания бака
override lateinit var mouth: TankMouth

override fun getContents(): Int = contents

override fun receiveFuel(liters: Int) {
contents += liters
}
}

/**
* Бак не выдержал заправки
*/
class TankExplodedException(message: String) : RuntimeException(message)
35 changes: 35 additions & 0 deletions src/main/kotlin/ru/otus/cars/TankMouth.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ru.otus.cars

/**
* Горловина бака: через неё машина заправляется.
* Sealed: набор видов горловин закрыт и известен компилятору.
*/
sealed class TankMouth(private val tank: Tank) {
private var isOpen = false

fun open() { isOpen = true }

fun close() { isOpen = false }

/**
* Общая механика залива: доступна только наследникам.
*/
protected fun pour(liters: Int) {
check(isOpen) { "Горловина закрыта" }
tank.receiveFuel(liters)
}
}

/**
* Бензиновая горловина: принимает только бензин.
*/
class PetrolMouth(tank: Tank) : TankMouth(tank) {
fun fuelPetrol(liters: Int) = pour(liters)
}

/**
* Газовая горловина: принимает только сжиженный газ.
*/
class LpgMouth(tank: Tank) : TankMouth(tank) {
fun fuelLpg(liters: Int) = pour(liters)
}
25 changes: 24 additions & 1 deletion src/main/kotlin/ru/otus/cars/Taz.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ object Taz: Car {
override val carOutput: CarOutput
get() = throw NotImplementedError("Приборов нет")

/**
* Горловина есть, а бак лучше не трогать
*/
override val tankMouth: TankMouth
get() = RustyTank.mouth

/**
* Получить оборудование
*/
Expand All @@ -36,4 +42,21 @@ object Taz: Car {
override fun wheelToLeft(degrees: Int) {
throw NotImplementedError("Руля нет")
}
}

// Выводим состояние машины
override fun toString(): String = "Taz(color=$color, fuel=${RustyTank.getContents()})"

/**
* Ржавый бак ТАЗа. Вложенный private object: снаружи о нём никто не знает.
*/
private object RustyTank : Tank {
// Горловину и ТАЗу ставит специалист
override val mouth: TankMouth = Tank.petrolMouthFor(this)

override fun getContents(): Int = 0

override fun receiveFuel(liters: Int) {
throw TankExplodedException("Бак ТАЗа взорвался при заправке!")
}
}
}
21 changes: 20 additions & 1 deletion src/main/kotlin/ru/otus/cars/Vaz2107.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class Vaz2107 private constructor(color: String) : VazPlatform(color) {
override fun build(plates: Car.Plates): Vaz2107 = Vaz2107("Зеленый").apply {
this.engine = getRandomEngine()
this.plates = plates
// Специалист ставит на семёрку газовую систему
this.tank = Tank.assembleLpgSystem()
}

/**
Expand Down Expand Up @@ -57,9 +59,22 @@ class Vaz2107 private constructor(color: String) : VazPlatform(color) {
override lateinit var plates: Car.Plates
private set

/**
* Топливная система. Её ставит сборщик, наружу бак не отдаём.
* @see [build]
*/
private lateinit var tank: Tank

/**
* Заправить машину можно только через горловину
*/
override val tankMouth: TankMouth
get() = tank.mouth

// Выводим состояние машины
override fun toString(): String {
return "Vaz2107(plates=$plates, wheelAngle=$wheelAngle, currentSpeed=$currentSpeed)"
return "Vaz2107(plates=$plates, wheelAngle=$wheelAngle, currentSpeed=$currentSpeed, " +
"fuel=${tank.getContents()})"
}

/**
Expand All @@ -74,5 +89,9 @@ class Vaz2107 private constructor(color: String) : VazPlatform(color) {
override fun getCurrentSpeed(): Int {
return this@Vaz2107.currentSpeed
}

override fun getFuelContents(): Int {
return this@Vaz2107.tank.getContents()
}
}
}
21 changes: 20 additions & 1 deletion src/main/kotlin/ru/otus/cars/Vaz2108.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class Vaz2108 private constructor(color: String) : VazPlatform(color) {
override fun build(plates: Car.Plates): Vaz2108 = Vaz2108("Красный").apply {
this.engine = getRandomEngine()
this.plates = plates
// Специалист ставит на восьмёрку бензиновую систему
this.tank = Tank.assemblePetrolSystem()
}

fun alignWheels(vaz2108: Vaz2108) {
Expand Down Expand Up @@ -61,9 +63,22 @@ class Vaz2108 private constructor(color: String) : VazPlatform(color) {
override lateinit var plates: Car.Plates
private set

/**
* Топливная система. Её ставит сборщик, наружу бак не отдаём.
* @see [build]
*/
private lateinit var tank: Tank

/**
* Заправить машину можно только через горловину
*/
override val tankMouth: TankMouth
get() = tank.mouth

// Выводим состояние машины
override fun toString(): String {
return "Vaz2108(plates=$plates, wheelAngle=$wheelAngle, currentSpeed=$currentSpeed)"
return "Vaz2108(plates=$plates, wheelAngle=$wheelAngle, currentSpeed=$currentSpeed, " +
"fuel=${tank.getContents()})"
}

/**
Expand All @@ -78,5 +93,9 @@ class Vaz2108 private constructor(color: String) : VazPlatform(color) {
override fun getCurrentSpeed(): Int {
return this@Vaz2108.currentSpeed
}

override fun getFuelContents(): Int {
return this@Vaz2108.tank.getContents()
}
}
}
30 changes: 29 additions & 1 deletion src/main/kotlin/ru/otus/cars/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ fun main() {
techChecks()
println("\n===> Taz...")
println(Taz.color)
println("\n===> refuel cars...")
refuelCars()
}

fun driveCars() {
Expand Down Expand Up @@ -90,4 +92,30 @@ fun repairEngine(car: VazPlatform) {
is VazEngine.LADA_2107 -> println("Чистка карбюратора у двигателя объемом ${car.engine.volume} куб.см у машины $car")
is VazEngine.SAMARA_2108 -> println("Угол зажигания у двигателя объемом ${car.engine.volume} куб.см у машины $car")
}
}
}

fun refuelCars() {
val cars = listOf(
Vaz2107.build(Car.Plates("123", 77)),
Vaz2108.build(Car.Plates("321", 78)),
Taz
)

println("Баки до заправки:")
cars.forEach { printFuelLevel(it) }

cars.refuelAll()

println("Баки после заправки:")
cars.forEach { printFuelLevel(it) }
}

/**
* Водитель смотрит уровень топлива на приборке - через CarOutput.
* У ТАЗа приборов нет, поэтому обращение к ним может не удаться.
*/
fun printFuelLevel(car: Car) {
val level = runCatching { car.carOutput.getFuelContents() }
.fold({ "$it л" }, { "приборов нет" })
println("$car -> на приборке: $level")
}