Chuyển tới nội dung chính

📝 Đơn Gọi Món - Structs

Chào mừng đến với bài học về Structs - cách tạo đơn gọi món và quản lý thông tin trong Nhà Hàng Code!

🎯 Món Ăn Hôm Nay

Tưởng tượng bạn là nhân viên nhà hàng nhận đơn từ khách:

  • Tên khách: Nguyễn Văn A
  • Bàn số: 5
  • Món gọi: Phở Bò, Gỏi Cuốn
  • Giá tiền: 80,000 VND

Tất cả thông tin này gom vào 1 tờ đơn → Đó chính là Struct!

🥘 Structs Là Gì?

Struct = Đơn gọi món (gom nhiều thông tin liên quan)

// Đơn gọi món
type DonGoiMon struct {
TenKhach string
SoBan int
DanhSachMon []string
TongTien int
}

// Tạo đơn mới
don := DonGoiMon{
TenKhach: "Nguyễn Văn A",
SoBan: 5,
DanhSachMon: []string{"Phở Bò", "Gỏi Cuốn"},
TongTien: 80000,
}

🍜 Ẩn Dụ Nhà Hàng:

  • Struct = Tờ đơn gọi món có nhiều ô thông tin
  • Fields = Các ô thông tin: tên món, giá, bàn số, khách hàng
  • Instance = Đơn cụ thể từ một bàn khách

👨‍🍳 Câu Chuyện Trong Bếp

Sáng nay, nhà hàng của chị Lan nhận được nhiều đơn gọi món:

Tình huống 1: Tạo đơn gọi món

type DonGoiMon struct {
MaDon int
TenKhach string
SoBan int
MonAn []string
TongTien int
DaThanhToan bool
}

// Khách bàn 3 gọi món
donBan3 := DonGoiMon{
MaDon: 1,
TenKhach: "Trần Thị B",
SoBan: 3,
MonAn: []string{"Phở Bò", "Bún Chả"},
TongTien: 95000,
DaThanhToan: false,
}

fmt.Printf("📋 Đơn #%d - Bàn %d\n", donBan3.MaDon, donBan3.SoBan)
fmt.Printf("👤 Khách: %s\n", donBan3.TenKhach)

Tình huống 2: Thêm hành động cho đơn hàng (Methods)

// Methods = Hành động với đơn gọi món
func (d *DonGoiMon) ThanhToan() {
d.DaThanhToan = true
fmt.Printf("✅ Đơn #%d đã thanh toán: %d VND\n", d.MaDon, d.TongTien)
}

func (d DonGoiMon) InHoaDon() {
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf("HÓA ĐƠN #%d\n", d.MaDon)
fmt.Printf("Bàn: %d - %s\n", d.SoBan, d.TenKhach)
for i, mon := range d.MonAn {
fmt.Printf("%d. %s\n", i+1, mon)
}
fmt.Printf("TỔNG: %d VND\n", d.TongTien)
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━")
}

// Sử dụng
donBan3.InHoaDon()
donBan3.ThanhToan()

🎉 Kết quả: Struct giúp quản lý thông tin đơn hàng có tổ chức và dễ xử lý!

📝 Công Thức Nấu (Code Examples)

Ví Dụ 1: Struct Cơ Bản - Thông Tin Món Ăn

package main

import "fmt"

type MonAn struct {
Ten string
Gia int
ThoiGianNau int // phút
LoaiMon string
}

func main() {
// Tạo món ăn
pho := MonAn{
Ten: "Phở Bò",
Gia: 50000,
ThoiGianNau: 10,
LoaiMon: "Món chính",
}

bunCha := MonAn{
Ten: "Bún Chả",
Gia: 45000,
ThoiGianNau: 15,
LoaiMon: "Món chính",
}

fmt.Println("🍜 THÔNG TIN MÓN ĂN:")
fmt.Printf("%-15s: %d VND (%d phút)\n", pho.Ten, pho.Gia, pho.ThoiGianNau)
fmt.Printf("%-15s: %d VND (%d phút)\n", bunCha.Ten, bunCha.Gia, bunCha.ThoiGianNau)
}

Ví Dụ 2: Struct Lồng Nhau - Thông Tin Khách Hàng

type DiaChi struct {
Duong string
Phuong string
ThanhPho string
}

type KhachHang struct {
Ten string
SoDT string
DiaChi DiaChi
LoaiKhach string // VIP, Thường, Mới
}

func main() {
khach := KhachHang{
Ten: "Lê Văn C",
SoDT: "0912345678",
DiaChi: DiaChi{
Duong: "123 Phố Huế",
Phuong: "Hai Bà Trưng",
ThanhPho: "Hà Nội",
},
LoaiKhach: "VIP",
}

fmt.Println("👤 THÔNG TIN KHÁCH HÀNG:")
fmt.Printf("Tên: %s (%s)\n", khach.Ten, khach.LoaiKhach)
fmt.Printf("SĐT: %s\n", khach.SoDT)
fmt.Printf("Địa chỉ: %s, %s, %s\n",
khach.DiaChi.Duong,
khach.DiaChi.Phuong,
khach.DiaChi.ThanhPho)
}

Ví Dụ 3: Struct với Methods - Hệ Thống Đặt Bàn

type DatBan struct {
MaDat int
TenKhach string
SoBan int
SoNguoi int
GioDat string
DaXacNhan bool
}

// Method xác nhận đặt bàn
func (d *DatBan) XacNhan() {
d.DaXacNhan = true
fmt.Printf("✅ Đã xác nhận đặt bàn #%d cho %s\n", d.MaDat, d.TenKhach)
}

// Method hiển thị thông tin
func (d DatBan) ThongTin() string {
trangThai := "Chờ xác nhận"
if d.DaXacNhan {
trangThai = "Đã xác nhận"
}
return fmt.Sprintf("Đặt bàn #%d: %s - Bàn %d (%d người) - %s - %s",
d.MaDat, d.TenKhach, d.SoBan, d.SoNguoi, d.GioDat, trangThai)
}

func main() {
datBan := DatBan{
MaDat: 101,
TenKhach: "Phạm Thị D",
SoBan: 8,
SoNguoi: 4,
GioDat: "19:00",
DaXacNhan: false,
}

fmt.Println(datBan.ThongTin())
datBan.XacNhan()
fmt.Println(datBan.ThongTin())
}

Ví Dụ 4: Struct với Constructor - Tạo Hóa Đơn

type HoaDon struct {
MaHD int
DanhSachMon map[string]int // món: số lượng
TongTien int
GiamGia int
}

// Constructor - Tạo hóa đơn mới
func TaoHoaDon(maHD int) *HoaDon {
return &HoaDon{
MaHD: maHD,
DanhSachMon: make(map[string]int),
TongTien: 0,
GiamGia: 0,
}
}

// Method thêm món
func (h *HoaDon) ThemMon(ten string, soLuong int, gia int) {
h.DanhSachMon[ten] = soLuong
h.TongTien += soLuong * gia
}

// Method áp dụng giảm giá
func (h *HoaDon) ApDungGiamGia(phanTram int) {
h.GiamGia = h.TongTien * phanTram / 100
}

// Method tính tiền thanh toán
func (h HoaDon) ThanhToan() int {
return h.TongTien - h.GiamGia
}

func main() {
hd := TaoHoaDon(2001)
hd.ThemMon("Phở Bò", 2, 50000)
hd.ThemMon("Gỏi Cuốn", 3, 30000)
hd.ApDungGiamGia(10) // Giảm 10%

fmt.Printf("🧾 HÓA ĐƠN #%d\n", hd.MaHD)
for mon, sl := range hd.DanhSachMon {
fmt.Printf("- %s x%d\n", mon, sl)
}
fmt.Printf("Tổng: %d VND\n", hd.TongTien)
fmt.Printf("Giảm: %d VND\n", hd.GiamGia)
fmt.Printf("Thanh toán: %d VND\n", hd.ThanhToan())
}

Ví Dụ 5: Struct với JSON Tags - API Thực Đơn

import (
"encoding/json"
"fmt"
)

type ThucDon struct {
ID int `json:"id"`
TenMon string `json:"ten_mon"`
Gia int `json:"gia"`
MoTa string `json:"mo_ta"`
NguyenLieu []string `json:"nguyen_lieu"`
HinhAnh string `json:"hinh_anh,omitempty"`
}

func main() {
mon := ThucDon{
ID: 1,
TenMon: "Phở Bò",
Gia: 50000,
MoTa: "Phở bò truyền thống Hà Nội",
NguyenLieu: []string{"Thịt bò", "Bánh phở", "Nước dùng"},
}

// Chuyển sang JSON
jsonData, _ := json.MarshalIndent(mon, "", " ")
fmt.Println("📄 JSON:")
fmt.Println(string(jsonData))

// Chuyển từ JSON
jsonStr := `{"id":2,"ten_mon":"Bún Chả","gia":45000}`
var mon2 ThucDon
json.Unmarshal([]byte(jsonStr), &mon2)
fmt.Printf("\n🍜 Món: %s - %d VND\n", mon2.TenMon, mon2.Gia)
}

🔥 Thực Hành Trong Bếp

Bài Tập 1: Quản Lý Nhân Viên

package main

import "fmt"

type NhanVien struct {
Ma int
Ten string
ChucVu string
Luong int
NamKinhNghiem int
}

func (nv NhanVien) TinhThuong() int {
thuong := 0
if nv.NamKinhNghiem >= 5 {
thuong = nv.Luong * 20 / 100
} else if nv.NamKinhNghiem >= 2 {
thuong = nv.Luong * 10 / 100
}
return thuong
}

func main() {
nv := NhanVien{
Ma: 101,
Ten: "Nguyễn Văn E",
ChucVu: "Đầu bếp chính",
Luong: 15000000,
NamKinhNghiem: 6,
}

thuong := nv.TinhThuong()
fmt.Printf("👨‍🍳 %s - %s\n", nv.Ten, nv.ChucVu)
fmt.Printf("💰 Lương: %d VND\n", nv.Luong)
fmt.Printf("🎁 Thưởng (%d năm): %d VND\n", nv.NamKinhNghiem, thuong)
fmt.Printf("💵 Tổng thu nhập: %d VND\n", nv.Luong+thuong)
}

Bài Tập 2: Hệ Thống Combo

type Combo struct {
Ten string
CacMon []string
GiaGoc int
GiamGia int
}

func (c Combo) GiaBan() int {
return c.GiaGoc - c.GiamGia
}

func (c Combo) HienThi() {
fmt.Printf("🎁 COMBO: %s\n", c.Ten)
fmt.Println("Bao gồm:")
for i, mon := range c.CacMon {
fmt.Printf(" %d. %s\n", i+1, mon)
}
fmt.Printf("Giá gốc: %d VND\n", c.GiaGoc)
fmt.Printf("Giảm: %d VND\n", c.GiamGia)
fmt.Printf("💰 Chỉ còn: %d VND\n", c.GiaBan())
}

func main() {
combo := Combo{
Ten: "Combo Gia Đình",
CacMon: []string{"Phở Bò x2", "Bún Chả x2", "Gỏi Cuốn x4", "Trà Đá x4"},
GiaGoc: 220000,
GiamGia: 30000,
}

combo.HienThi()
}

Bài Tập 3: Quản Lý Kho Nguyên Liệu

type NguyenLieu struct {
Ten string
DonVi string
SoLuong float64
GiaMotDonVi int
}

func (nl *NguyenLieu) Nhap(soLuong float64) {
nl.SoLuong += soLuong
fmt.Printf("📦 Nhập %s: +%.2f %s\n", nl.Ten, soLuong, nl.DonVi)
}

func (nl *NguyenLieu) Xuat(soLuong float64) bool {
if nl.SoLuong >= soLuong {
nl.SoLuong -= soLuong
fmt.Printf("📤 Xuất %s: -%.2f %s\n", nl.Ten, soLuong, nl.DonVi)
return true
}
fmt.Printf("❌ Không đủ %s! Còn: %.2f %s\n", nl.Ten, nl.SoLuong, nl.DonVi)
return false
}

func (nl NguyenLieu) GiaTri() int {
return int(nl.SoLuong * float64(nl.GiaMotDonVi))
}

func main() {
thitBo := NguyenLieu{
Ten: "Thịt Bò",
DonVi: "kg",
SoLuong: 10.0,
GiaMotDonVi: 250000,
}

fmt.Printf("📊 Tồn kho: %s - %.2f %s\n", thitBo.Ten, thitBo.SoLuong, thitBo.DonVi)
thitBo.Nhap(5.5)
thitBo.Xuat(3.0)
fmt.Printf("💰 Giá trị kho: %d VND\n", thitBo.GiaTri())
}

⚠️ Những Lỗi Đầu Bếp Thường Gặp

Lỗi 1: Quên Khởi Tạo Struct

// ❌ SAI: Struct nil
var don *DonGoiMon
don.TenKhach = "A" // Panic!

// ✅ ĐÚNG: Khởi tạo trước
don := &DonGoiMon{}
don.TenKhach = "A" // OK

Lỗi 2: Method Value vs Pointer Receiver

// ❌ SAI: Dùng value receiver khi cần sửa
func (d DonGoiMon) ThanhToan() {
d.DaThanhToan = true // Không thay đổi được!
}

// ✅ ĐÚNG: Dùng pointer receiver
func (d *DonGoiMon) ThanhToan() {
d.DaThanhToan = true // OK, thay đổi được
}

Lỗi 3: Struct Comparison với Slice/Map

// ❌ SAI: So sánh struct có slice/map
type Don struct {
MonAn []string
}

don1 := Don{MonAn: []string{"Phở"}}
don2 := Don{MonAn: []string{"Phở"}}
// don1 == don2 // Lỗi! Không thể so sánh

// ✅ ĐÚNG: So sánh từng field
func (d Don) Equal(other Don) bool {
if len(d.MonAn) != len(other.MonAn) {
return false
}
for i := range d.MonAn {
if d.MonAn[i] != other.MonAn[i] {
return false
}
}
return true
}

Lỗi 4: JSON Tag Sai Format

// ❌ SAI: Tag không đúng format
type Mon struct {
Ten string `json: "ten"` // Có dấu cách!
}

// ✅ ĐÚNG: Tag đúng format
type Mon struct {
Ten string `json:"ten"` // Không có dấu cách
}

💡 Bí Quyết Của Đầu Bếp

  1. Pointer receiver khi cần sửa: Methods thay đổi struct dùng *
  2. Value receiver khi chỉ đọc: Methods không đổi struct dùng value
  3. Constructor pattern: Tạo hàm New...() hoặc Tao...() để khởi tạo
  4. JSON tags rõ ràng: Dùng snake_case cho API
  5. Struct nhỏ gọn: Tách struct lớn thành nhiều struct nhỏ, dễ quản lý

🎓 Bạn Đã Học Được

  • ✅ Struct = Đơn gọi món (gom nhiều thông tin liên quan)
  • ✅ Fields = Các ô thông tin trên đơn (tên, giá, bàn số...)
  • ✅ Methods = Hành động với đơn hàng (in hóa đơn, tính tổng, thanh toán)
  • ✅ Pointer receiver (*) = Thay đổi được nội dung struct
  • ✅ Value receiver = Chỉ đọc thông tin, không đổi
  • ✅ JSON tags = Chuyển đổi struct ↔ JSON cho API
  • ✅ Constructor = Hàm tạo struct mới với giá trị mặc định
  • ✅ Nested struct = Struct lồng nhau để tổ chức tốt hơn

🍜 Món Tiếp Theo

Đơn gọi món đã có! Giờ cần học cách truyền đơn giữa các bộ phận:

👉 Băng Chuyền Món Ăn - Channels


💡 Lời Khuyên Cuối: Struct giống đơn gọi món trong nhà hàng - càng rõ ràng, càng ít nhầm lẫn! Thiết kế struct tốt = hệ thống dễ bảo trì. Thực hành tạo nhiều struct khác nhau để quản lý dữ liệu như đầu bếp chuyên nghiệp!