Trình tạo sơ đồ lớp Mermaid
Sơ đồ lớp cho thấy các kiểu và quan hệ giữa chúng: cái gì chứa cái gì, cái gì kế thừa từ cái gì, cái gì phụ thuộc vào cái gì. Nó hợp khi cốt lõi là hình dạng của mã nguồn — một mô hình miền, một giao diện mở rộng, một cây kế thừa. Nếu bạn muốn cho thấy điều gì xảy ra lúc chạy chứ không phải các kiểu khớp với nhau ra sao, hãy dùng sơ đồ tuần tự.
Mô hình miền của việc thanh toán
Ba loại quan hệ trong cùng một sơ đồ: hợp thành cho những phần không sống sót khi cái toàn thể mất đi, kế thừa cho cây phương thức thanh toán, và một liên kết thường có kèm bội số. Ý nghĩa chủ yếu nằm ở các mũi tên; những hình chữ nhật lớp gần như chỉ là phần đi kèm.
classDiagram
class DonHang {
+String maDon
+TrangThaiDon trangThai
+SoTien tongCong()
+void themDong(SanPham sp, int soLuong)
}
class DongDonHang {
+SanPham sanPham
+int soLuong
+SoTien thanhTien()
}
class PhuongThucThanhToan {
<<abstract>>
+xacThuc(SoTien soTien) bool
}
class TheNganHang {
+String bonSoCuoi
+xacThuc(SoTien soTien) bool
}
class ChuyenKhoan {
+String soTaiKhoan
+xacThuc(SoTien soTien) bool
}
class ThanhToanKhiNhan {
+SoTien phiThuHo
+xacThuc(SoTien soTien) bool
}
DonHang "1" *-- "1..*" DongDonHang : chứa
DonHang --> PhuongThucThanhToan : thanh toán bằng
PhuongThucThanhToan <|-- TheNganHang
PhuongThucThanhToan <|-- ChuyenKhoan
PhuongThucThanhToan <|-- ThanhToanKhiNhanVí dụ có giải thích
1. Một lớp đơn lẻ
`+` là công khai, `-` là riêng tư, `#` là được bảo vệ. Thành viên có dấu ngoặc đơn được vẽ thành phương thức; không có ngoặc thì đó là trường dữ liệu.
classDiagram
class NguoiDung {
+String email
-String bamMatKhau
+bool kiemTra(String ungVien)
}2. Kế thừa và giao diện
`<|--` là kế thừa, đọc là «cái bên phải mở rộng cái bên trái». Chú thích `<<interface>>` là một cái nhãn chứ không phải hành vi, nhưng chính nó quyết định sơ đồ có dễ đọc hay không.
classDiagram
class KhoDuLieu {
<<interface>>
+tim(String id) ThucThe
+luu(ThucThe t) void
}
class KhoPostgres {
-KetNoi ketNoi
+tim(String id) ThucThe
+luu(ThucThe t) void
}
class KhoTrongBoNho {
-Map luuTru
+tim(String id) ThucThe
+luu(ThucThe t) void
}
KhoDuLieu <|.. KhoPostgres
KhoDuLieu <|.. KhoTrongBoNho3. Hợp thành so với tập hợp
Khác biệt nằm ở vòng đời. Hình thoi đặc (`*--`) nghĩa là phần con mất theo cái toàn thể: xóa hóa đơn thì các dòng của nó biến mất. Hình thoi rỗng (`o--`) nghĩa là phần con tồn tại độc lập.
classDiagram
class HoaDon {
+String soHoaDon
}
class DongHoaDon {
+String dienGiai
}
class KhachHang {
+String tenCongTy
}
HoaDon "1" *-- "1..*" DongHoaDon : gồm có
KhachHang "1" o-- "0..*" HoaDon : đã nhận4. Kiểu tổng quát
Dấu ngã cho tham số kiểu: `KhoDuLieu~NguoiDung~`. Lồng nhau cũng chạy được, đôi khi cần đến và hiếm khi là ý hay. Dấu tiếng Việt hoạt động bình thường bên trong một kiểu tổng quát.
classDiagram
class KhoDuLieu~T~ {
+tim(String id) T
+tatCa() List~T~
}
class BoNhoDem~K, V~ {
+lay(K khoa) V
+dat(K khoa, V giaTri) void
}
class KhoNguoiDung {
+timTheoEmail(String email) NguoiDung
}
KhoDuLieu~NguoiDung~ <|-- KhoNguoiDung5. Ghi chú và hướng
`direction LR` xếp sơ đồ từ trái sang phải, điều thường hợp với cây kế thừa hơn là cách xếp mặc định. Ghi chú là chỗ đúng cho ràng buộc không nhét vừa vào hình chữ nhật của lớp.
classDiagram
direction LR
class KhoSuKien {
+ghiThem(SuKien sk) void
+phatLai(String luong) List~SuKien~
}
class AnhChup {
+int phienBan
+byte[] noiDung
}
KhoSuKien --> AnhChup : ghi mỗi 100 sự kiện
note for KhoSuKien "Chỉ ghi thêm. Sự kiện không bao giờ bị sửa hay xóa."Tóm tắt cú pháp sơ đồ lớp
Các mũi tên quan hệ đáng để thuộc lòng: chính chúng phân biệt một sơ đồ lớp với một bức vẽ gồm hình chữ nhật và đường thẳng, và ta đọc chúng từ phải sang trái, điều gây nhầm lẫn khá lâu.
| Cú pháp | Ý nghĩa |
|---|---|
| classDiagram | Mở sơ đồ. Phân biệt hoa thường. |
| class Tên { ... } | Lớp cùng các thành viên. Dấu ngoặc nhọn đóng nằm trên dòng riêng. |
| +thành viên | Công khai. |
| -thành viên | Riêng tư. |
| #thành viên | Được bảo vệ. |
| +phươngThức(Kiểu đốiSố) KiểuTrảVề | Phương thức — chính dấu ngoặc đơn làm nên điều đó. |
| <<interface>> / <<abstract>> | Khuôn mẫu, viết ở dòng đầu tiên bên trong lớp. |
| A <|-- B | Kế thừa: B mở rộng A. |
| A <|.. B | Hiện thực: B cài đặt giao diện A. |
| A *-- B | Hợp thành: B không sống sót sau A. |
| A o-- B | Tập hợp: B có thể tồn tại không cần A. |
| A --> B | Liên kết có hướng. |
| A ..> B | Phụ thuộc — A dùng B nhưng không giữ B. |
| A "1" --> "0..*" B : nhãn | Bội số ở cả hai đầu kèm nhãn quan hệ. |
| class Kho~T~ | Tham số kiểu tổng quát. |
| note for A "văn bản" | Ghi chú gắn vào một lớp. |
| direction LR | Đổi hướng sắp xếp. |
Các lỗi làm hỏng sơ đồ lớp
Dựng lại trên Mermaid 11.12.2. Sơ đồ lớp thuộc nhóm dễ dãi trong sáu loại, nên một nửa số lỗi này vẫn vẽ ra bình thường và trả cho bạn bức hình sai.
Bạn thấy gì
Parse error, kết thúc bằng: got 'EOF_IN_STRUCT'
Vì sao
Thân lớp mở bằng `{` mà không bao giờ đóng. Lần này tên token thật sự có ích: nó nói rằng tệp đã hết trong khi ta vẫn còn ở bên trong một lớp.
Cách sửa
Đóng dấu ngoặc nhọn trên một dòng riêng.
classDiagram
class DonHang {
+String maDonclassDiagram
class DonHang {
+String maDon
}Bạn thấy gì
Parse error, kết thúc bằng: got 'ANNOTATION_END'
Vì sao
Dùng mũi tên của sơ đồ tuần tự trong sơ đồ lớp. `->>` ở đây không mang nghĩa gì, và bộ phân tích đi vào nó đủ sâu để trả về một tên token dễ gây hiểu lầm.
Cách sửa
Dùng các quan hệ của sơ đồ lớp: `-->` cho liên kết, `<|--` cho kế thừa, `*--` cho hợp thành.
classDiagram
DonHang ->> KhachHangclassDiagram
DonHang --> KhachHang : thuộc vềBạn thấy gì
No diagram type detected matching given configuration
Vì sao
Viết sai hoa thường ở từ khóa. `classdiagram` không phải `classDiagram`.
Cách sửa
Chữ D viết hoa.
classdiagram
class DonHangclassDiagram
class DonHangBạn thấy gì
Mũi tên chỉ ngược với ý bạn muốn
Vì sao
Mũi tên quan hệ đọc ngược từ đầu mũi trở lại. `A <|-- B` nghĩa là B kế thừa từ A, không phải ngược lại. Viết ngược thì vẫn vẽ ra — chỉ có điều bây giờ nó khẳng định lớp cơ sở mở rộng chính lớp con của nó.
Cách sửa
Hãy đọc là «đầu xa mở rộng đầu có mũi». Đặt lớp cha ở bên trái `<|--`.
classDiagram
TheNganHang <|-- PhuongThucThanhToanclassDiagram
PhuongThucThanhToan <|-- TheNganHangBạn thấy gì
Xuất hiện một trường dữ liệu ở chỗ bạn tưởng là phương thức
Vì sao
Dấu ngoặc đơn là thứ duy nhất phân biệt phương thức với trường dữ liệu. `+luu` là một trường tên luu; `+luu()` là một phương thức. Cả hai dạng đều hợp lệ nên không có gì cảnh báo.
Cách sửa
Thêm dấu ngoặc đơn, rồi thêm kiểu trả về nếu bạn muốn thấy nó.
classDiagram
class Kho {
+luu
+tim
}classDiagram
class Kho {
+luu(ThucThe t) void
+tim(String id) ThucThe
}Bạn thấy gì
Hợp thành và tập hợp thoạt nhìn giống hệt nhau mà nói điều ngược nhau
Vì sao
`*--` và `o--` lệch nhau đúng một ký tự, và chúng mã hóa một khác biệt ý nghĩa có thật: phần con có sống sót được sau cái toàn thể hay không. Dùng nhầm sẽ cho ra sơ đồ đúng về hình thức và sai về miền nghiệp vụ của bạn.
Cách sửa
Hình thoi đặc `*--` khi xóa cha thì con cũng mất. Hình thoi rỗng `o--` khi con vẫn còn.
classDiagram
DonHang o-- DongDonHang : chứaclassDiagram
DonHang *-- DongDonHang : chứaGhi chú về việc vẽ
Đo trên Mermaid 11.12.2, đúng phiên bản trang này dùng.
Nó cao lên nhanh hơn mọi loại sơ đồ khác ở đây
Đo trên các lớp mỗi lớp hai thành viên, nối với nhau bằng kế thừa: ba lớp cho viewBox khoảng 176×548, bốn mươi lớp cho 180×7726, tức chừng 194 pixel chiều cao mỗi lớp — mức tăng dốc nhất trong sáu loại trên trang này. Một sơ đồ bốn mươi lớp vượt bảy nghìn pixel chiều cao và vô dụng nếu để nguyên một tấm. `direction LR` có giúp, nhưng qua khoảng mười lăm lớp thì cách trung thực là chia sơ đồ theo ranh giới miền.
Tên lớp phải viết liền, còn nhãn thì không
Đây là chỗ tiếng Việt phải chọn cách viết. Tên lớp là định danh, nên `Đơn hàng` không dùng được, phải viết liền thành `DonHang` hoặc `ĐơnHàng` — và đã kiểm chứng là cả hai đều chạy, dấu tiếng Việt không hề gây trở ngại: `ĐơnHàng` vẽ ra đúng với định danh `classId-ĐơnHàng`. Chỉ khoảng trắng mới làm hỏng. Bù lại, nhãn quan hệ sau dấu hai chấm là văn bản tự do, nên `: thanh toán bằng` viết bình thường với đầy đủ khoảng trắng.
Số lượng thành viên gần như không ảnh hưởng đến chiều rộng
Chiều rộng do chữ ký dài nhất của một thành viên quyết định, chứ không phải do có bao nhiêu thành viên. Một lớp hai mươi trường ngắn không rộng hơn lớp có ba trường. Nói cách khác: hãy hào phóng với thành viên và dè sẻn với lớp, đúng ngược lại với bản năng. Trong tiếng Việt viết liền, một tên phương thức mô tả kỹ như `xacThucGiaoDichNganHang` một mình quyết định bề rộng cả lớp.
Kiểu tổng quát dùng dấu ngã và điều đó có hệ quả
`KhoDuLieu~T~` viết như vậy vì dấu ngoặc nhọn sẽ đụng với HTML trong nhãn. Từ đó cũng suy ra rằng một dấu ngã thật sự nằm trong tên lớp hay tên thành viên sẽ bị đọc thành điểm mở đầu của một tham số kiểu. Hiếm gặp, nhưng khi xảy ra thì gây bối rối không ít.
Nhãn là HTML, nên việc xuất PNG phải vẽ lại
Nhãn của lớp được vẽ bên trong một `<foreignObject>` trong SVG, và trình duyệt từ chối chuyển thứ đó thành ảnh raster trên canvas. Chức năng xuất PNG của trang này trước đây thất bại lặng lẽ và trả về tệp SVG; giờ nó vẽ lại sơ đồ bằng nhãn văn bản SVG thuần trước. Tệp PNG ra đúng và đủ kích thước, với kiểu chữ khác đôi chút so với màn hình.
Khi nào nên dùng loại sơ đồ khác
Nếu bạn đang mô tả một cơ sở dữ liệu chứ không phải một hệ thống kiểu, hãy dùng sơ đồ ER. Sự phân biệt này quan trọng: sơ đồ lớp mô hình hóa hành vi và kế thừa, những thứ bảng không có, còn sơ đồ ER mô hình hóa khóa và bội số một cách tử tế, những thứ sơ đồ lớp chỉ lướt qua.
Nếu sơ đồ chủ yếu gồm các hình chữ nhật nối bằng `-->` và không có thành viên nào, thì bạn đang vẽ kiến trúc chứ không phải sơ đồ lớp. Một lưu đồ có nhóm con sẽ đẹp hơn và khẳng định ít hơn.
Còn nếu danh sách lớp được sinh ra từ mã nguồn, hãy tự hỏi liệu sơ đồ có nên được sinh ra từ đó luôn không. Một sơ đồ lớp duy trì thủ công cho một cơ sở mã thay đổi hằng tuần sẽ sai sự thật chỉ sau một tháng, và một sơ đồ sai thì tốn kém hơn là không có sơ đồ nào.
Các loại sơ đồ khác
Viết bởi Dominik Malsch · Cập nhật lần cuối: