VieTopik
Tiếng HànTiếng Anh.NET (C#)Java
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
OOP và thiết kế (Java)Interface và composition
Bài 7/12
5 phút

Composition

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Đơn hàng có hai kiểu giao (thường, nhanh) và hai kiểu gói (thường, gói quà). Dùng kế thừa thì phải có FastOrder, GiftOrder, FastGiftOrder… Mỗi lựa chọn mới lại nhân số class lên. Composition giải quyết việc này bằng cách ghép các phần lại với nhau.

Khái niệm

🧱 Composition: class chứa object của class khác làm field và giao việc cho object đó, thay vì kế thừa.

Đơn hàng không phải là một cách giao hàng, mà có một cách giao hàng, như Customer có Address ở bài Kế thừa.

Ví dụ

// Main.java
void main() {
    var order = new Order(200000);
    order.setShipping(new StandardShipping());
    System.out.println(order.total());   // 230000
}

// Shipping.java
interface Shipping {
    long fee(long subtotal);
}

// StandardShipping.java
class StandardShipping implements Shipping {
    @Override
    public long fee(long subtotal) {
        return 30000;
    }
}

// FastShipping.java
class FastShipping implements Shipping {
    @Override
    public long fee(long subtotal) {
        return 60000;
    }
}

// Order.java
class Order {
    private long subtotal;
    private Shipping shipping = new StandardShipping();

    public Order(long subtotal) {
        this.subtotal = subtotal;
    }

    public void setShipping(Shipping shipping) {
        this.shipping = shipping;
    }

    public long total() {
        return subtotal + shipping.fee(subtotal);
    }
}
  • Order có một Shipping, và giao việc tính phí ship cho nó.
  • Muốn đổi cách giao chỉ cần gán object khác qua setShipping. Không cần tạo class con của Order.
  • Thêm kiểu giao mới chỉ cần thêm một class implement Shipping.
Order subtotal shipping total() <<interface>> Shipping fee() StandardShipping FastShipping
Order có một Shipping, gán object nào cũng được

Thử ngay

Giữ các file của ví dụ, thay main trong Main.java bằng:

// Main.java
void main() {
    var order = new Order(200000);
    System.out.println(order.total());

    order.setShipping(new FastShipping());
    System.out.println(order.total());
}

Đoán trước khi chạy: cùng một object order, đổi shipping rồi gọi lại total(). Hai dòng in ra có khác nhau không?

Xem kết quả
230000
260000

Khác nhau. Object order vẫn là object cũ, chỉ phần giao hàng bên trong được thay. Kế thừa không làm được việc này: một object đã tạo từ FastOrder thì không thể đổi thành StandardOrder.

Lỗi hay gặp

Dùng kế thừa cho mọi tổ hợp lựa chọn. Số class bằng tích số các lựa chọn.

// SAI — 2 kiểu giao × 2 kiểu gói = 4 class con
// BaseOrder.java
class BaseOrder { }
// StandardOrder.java
class StandardOrder extends BaseOrder { }
// FastOrder.java
class FastOrder extends BaseOrder { }
// StandardGiftOrder.java
class StandardGiftOrder extends StandardOrder { }
// FastGiftOrder.java
class FastGiftOrder extends FastOrder { }
// ĐÚNG — một class, ghép các phần lại
// Order.java
class Order {
    private Shipping shipping = new StandardShipping();
    private boolean giftWrap;
}

Thêm kiểu giao thứ ba thì cách kế thừa cần 6 class con. Cách composition chỉ thêm một class implement Shipping.

Tóm tắt

  • Composition: class có object khác làm field và giao việc cho nó.
  • Kế thừa là "là một", composition là "có một".
  • Composition đổi được hành vi ngay lúc chạy bằng cách gán object khác.
  • Khi phân vân giữa hai cách, ưu tiên composition.

Tự kiểm tra

0/3 câu
Câu 1

Class Car cần dùng code của class Engine. Cách nào đúng?

Câu 2

Order có field Discount discount và method setDiscount. Muốn áp dụng mã giảm giá khác cho một đơn đã tạo, cần làm gì?

Câu 3

Có 3 kiểu thanh toán và 3 kiểu giao hàng. Dùng kế thừa để tạo mọi tổ hợp thì cần khoảng bao nhiêu class đơn hàng?

Interface hay abstract classSRP — Một class, một trách nhiệm

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt