Mẫu Decorator
Thuộc phần Lập trình hướng đối tượng trong hành trình Kotlin của Coddy. Bài học 47 trên 57.
Mẫu decorator bổ sung hành vi cho một đối tượng bằng cách bọc nó trong một đối tượng khác có cùng giao diện. Decorator chuyển tiếp lời gọi đến đối tượng mà nó bọc và thêm phần riêng của mình, vì vậy bạn có thể xếp chồng các decorator:
interface Coffee {
fun cost(): Int
fun description(): String
}
class Espresso : Coffee {
override fun cost() = 200
override fun description() = "espresso"
}
class Milk(private val inner: Coffee) : Coffee {
override fun cost() = inner.cost() + 50
override fun description() = inner.description() + ", milk"
}
class Syrup(private val inner: Coffee) : Coffee {
override fun cost() = inner.cost() + 30
override fun description() = inner.description() + ", syrup"
}Bên trong main:
val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")Đầu ra:
espresso, milk, milk, syrup: 330Khi decorator thay đổi kết quả, thứ tự của chúng rất quan trọng: mỗi decorator xử lý kết quả mà đối tượng bên trong nó trả về:
interface Text {
fun render(): String
}
class Word(private val w: String) : Text {
override fun render() = w
}
class Bracket(private val inner: Text) : Text {
override fun render() = "[" + inner.render() + "]"
}
class Twice(private val inner: Text) : Text {
override fun render() = inner.render() + inner.render()
}Bên trong main:
println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())Đầu ra:
[hi][hi]
[hihi]Với by, một lớp trang trí chỉ cần viết các thành viên mà nó thay đổi, còn lại sẽ tự động chuyển tiếp:
interface Source {
fun read(): String
fun name(): String
}
class FileSource(private val content: String) : Source {
override fun read() = content
override fun name() = "notes.txt"
}
class Censored(private val inner: Source, private val word: String) : Source by inner {
override fun read() = inner.read().replace(word, "***")
}Bên trong main:
val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")Đầu ra:
notes.txt: the *** planCác decorator được kết hợp khi chương trình chạy, chẳng hạn dựa trên lựa chọn của khách hàng, còn lớp con cố định một cách kết hợp khi viết code. Nếu dùng lớp con để biểu diễn ba món thêm, bạn cần một lớp cho mỗi cách kết hợp; còn với decorator, fold xếp chồng chúng từ một danh sách:
interface Coffee {
fun cost(): Int
}
class Espresso : Coffee {
override fun cost() = 200
}
class Extra(private val inner: Coffee, private val price: Int) : Coffee {
override fun cost() = inner.cost() + price
}Bên trong main:
val prices = mapOf("milk" to 50, "syrup" to 30, "cream" to 70)
val choices = listOf("milk", "cream", "milk")
val coffee = choices.fold<String, Coffee>(Espresso()) { c, extra -> Extra(c, prices.getValue(extra)) }
println(coffee.cost())Đầu ra:
370Thử thách
DễPizza được tạo bằng các bộ trang trí (decorator) của giao diện được cung cấp Pizza (với price() và toppings()). Hãy viết Base (800, không có topping) và các bộ trang trí Cheese (+150, thêm cheese), Olives (+100, thêm olives) và DoubleUp, bộ này nhân đôi giá của mọi thứ bên trong nó và lặp lại topping hai lần. Sau đó, hãy viết build(names), hàm này bắt đầu từ Base và lần lượt bọc nó bằng một bộ trang trí cho mỗi tên (cheese, olives hoặc double).
Code được cung cấp đọc các dòng chứa tên được phân tách bằng dấu phẩy hoặc plain, tạo từng chiếc pizza và in ra 1050: cheese, olives hoặc 800: plain.
Hãy viết code trong Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt và Pizza.kt. Main.kt chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.
Tự làm thử
fun main() {
// Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên
val input = generateSequence(::readLine).toList()
for (line in input) {
val names = if (line == "plain") listOf() else line.split(",")
val pizza = build(names)
val toppings = pizza.toppings()
println("${pizza.price()}: " + if (toppings.isEmpty()) "plain" else toppings.joinToString(", "))
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Lớp và đối tượng
Làm việc với tệpĐịnh nghĩa lớpHàm khởi tạo chínhKhối init và kiểm tra tính hợp lệHàm khởi tạo phụÔn tập: thẻ thư viện4Kế thừa
Lớp mở và lớp conGhi đè thành viênsuper và hàm khởi tạoLớp trừu tượngÔn tập: Danh bạ nhân viên2Thuộc tính và tính đóng gói
Trình truy cập tùy chỉnhBộ sửa đổi phạm vi truy cậpSetter riêng tưlazy và lateinitÔn tập: tài khoản ngân hàng5Giao diện
Khai báo giao diệnPhương thức mặc địnhNhiều giao diệnGiao diện như kiểu dữ liệuÔn tập: Phương thức thanh toánTự luyện tập: Playground Kotlin