Bài tập 2(B9_C#):LỚP SẢN PHẨM

Sau khi hoàn thành bài tập này, bạn sẽ:

  1. ✅ Xây dựng lớp SanPham với các thuộc tính cơ bản

  2. ✅ Sử dụng static để tự động sinh mã sản phẩm

  3. ✅ Áp dụng validation cho dữ liệu

  4. ✅ Xây dựng phương thức nhập khoxuất kho

  5. ✅ Sử dụng kiểu decimal cho tiền tệ

  6. ✅ Tính toán giá trị tồn kho tự động

BƯỚC 1: TẠO PROJECT MỚI

1.1. Mở Visual Studio

  • Mở Visual Studio 2022

File → New → Project

Hoặc nhấn: Ctrl + Shift + N

1.3. Chọn loại Project

  • Gõ “Console” vào ô tìm kiếm

  • Chọn Console App (.NET Core) hoặc Console App (.NET Framework)

  • Nhấn Next

1.4. Đặt tên Project

Project name: Bai2_SanPham
Location: D:\CSharp\LapTrinhHuongDoiTuong (hoặc thư mục của bạn)
Solution: LapTrinhHuongDoiTuong (chọn Add to Solution)
  • Nhấn Next

1.5. Chọn Framework

  • Chọn .NET 6.0 hoặc .NET 8.0 (LTS)

  • Nhấn Create

BƯỚC 2: TẠO CẤU TRÚC THƯ MỤC

2.1. Tạo thư mục Models

  1. Trong Solution Explorer, click chuột phải vào Project Bai2_SanPham

  2. Chọn Add → New Folder

  3. Đặt tên: Models

  4. Nhấn Enter

BƯỚC 3: TẠO FILE LỚP SANPHAM

3.1. Tạo file SanPham.cs

  1. Click chuột phải vào thư mục Models

  2. Chọn Add → Class

  3. Đặt tên: SanPham.cs

  4. Nhấn Add

BƯỚC 4: VIẾT CODE CHO LỚP SANPHAM

4.1. Khai báo Namespace và Class

Mở file SanPham.cs và viết:

using System;
namespace Bai2_SanPham.Models
{
    public class SanPham
    {
        // Code sẽ viết ở đây
    }
}

4.2. Khai báo Fields (Trường dữ liệu)

public class SanPham
{
    // === FIELDS (Trường dữ liệu) ===
    private string _maSP;          // Mã sản phẩm (tự sinh)
    private string _tenSP;         // Tên sản phẩm
    private decimal _donGia;       // Đơn giá (dùng decimal cho tiền tệ)
    private int _soLuongTon;       // Số lượng tồn kho
    private static int _soLuongSP = 0;  // Đếm số SP đã tạo (static)
}

Giải thích Fields:

FieldKiểuMục đíchLưu ý
_maSPstringLưu mã sản phẩmSẽ tự động sinh
_tenSPstringLưu tên sản phẩmKiểm tra không rỗng
_donGiadecimalLưu đơn giáDùng decimal cho tiền tệ
_soLuongTonintLưu số lượng tồnKhông được âm
_soLuongSPstatic intĐếm số SP đã tạoDùng chung cho tất cả SP

4.3. Khai báo Properties (Thuộc tính)

a. Property MaSP (Read-only – chỉ đọc)

public class SanPham
{
    // ... Fields ở trên ...

    // === PROPERTIES ===
    
    // 1. Mã sản phẩm - Chỉ đọc, tự sinh
    public string MaSP
    {
        get { return _maSP; }
        // Không có set → Không thể thay đổi từ bên ngoài
    }
}

b. Property TenSP (Có validation)

    // 2. Tên sản phẩm - Có kiểm tra không rỗng
    public string TenSP
    {
        get { return _tenSP; }
        set
        {
            // Kiểm tra tên không được để trống
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Tên sản phẩm không được để trống!");
            _tenSP = value.Trim();
        }
    }

c. Property DonGia (Có validation)

    // 3. Đơn giá - Không được âm
    public decimal DonGia
    {
        get { return _donGia; }
        set
        {
            if (value < 0)
                throw new ArgumentException("Đơn giá không được âm!");
            _donGia = value;
        }
    }

d. Property SoLuongTon (Có validation)

    // 4. Số lượng tồn - Không được âm
    public int SoLuongTon
    {
        get { return _soLuongTon; }
        set
        {
            if (value < 0)
                throw new ArgumentException("Số lượng tồn không được âm!");
            _soLuongTon = value;
        }
    }

e. Property GiaTriTon (Computed – tự tính)

    // 5. Giá trị tồn kho - Tự động tính (Read-only)
    public decimal GiaTriTon
    {
        get { return _soLuongTon * _donGia; }
    }

4.4. Khai báo Constructors (Phương thức khởi tạo)

a. Constructor mặc định (Tự sinh mã SP)

public class SanPham
{
    // ... Fields và Properties ở trên ...

    // === CONSTRUCTORS ===

    // Constructor 1: Không tham số - Tự sinh mã SP
    public SanPham()
    {
        // Tăng số lượng SP lên 1 trước
        _soLuongSP++;
        
        // Tự sinh mã SP: SP001, SP002, SP003, ...
        _maSP = "SP" + _soLuongSP.ToString("D3");
        
        // Gán giá trị mặc định
        _tenSP = "Chưa xác định";
        _donGia = 0;
        _soLuongTon = 0;
    }
}

Giải thích:

  • _soLuongSP++ – Tăng biến đếm lên 1

  • ToString("D3") – Định dạng số thành 3 chữ số: 001, 002, 003, …

  • _maSP = "SP" + "001" → “SP001”

b. Constructor có tham số

    // Constructor 2: Có tham số đầy đủ
    public SanPham(string tenSP, decimal donGia, int soLuongTon) : this()
    {
        // this() gọi constructor mặc định để tự sinh mã SP
        
        // Gán giá trị (có validation)
        TenSP = tenSP;
        DonGia = donGia;
        SoLuongTon = soLuongTon;
    }

Giải thích:

  • : this() – Gọi constructor mặc định trước (để tự sinh mã SP)

  • Sau đó gán các giá trị khác


4.5. Khai báo Methods (Phương thức)

a. Phương thức HienThi()

public class SanPham
{
    // ... Fields, Properties, Constructors ở trên ...

    // === METHODS ===

    // 1. Hiển thị thông tin sản phẩm
    public void HienThi()
    {
        Console.WriteLine("═══════════════════════════════════════");
        Console.WriteLine($"  Mã SP         : {_maSP}");
        Console.WriteLine($"  Tên SP        : {_tenSP}");
        Console.WriteLine($"  Đơn giá       : {_donGia:N0} VND");
        Console.WriteLine($"  SL Tồn        : {_soLuongTon}");
        Console.WriteLine($"  Giá trị tồn   : {GiaTriTon:N0} VND");
        Console.WriteLine("═══════════════════════════════════════");
    }
}

b. Phương thức NhapKho()

    // 2. Nhập kho (tăng số lượng tồn)
    public void NhapKho(int soLuong)
    {
        // Kiểm tra số lượng nhập phải > 0
        if (soLuong <= 0)
        {
            Console.WriteLine("❌ Số lượng nhập phải lớn hơn 0!");
            return;
        }
        
        // Tăng số lượng tồn
        _soLuongTon += soLuong;
        Console.WriteLine($"✓ Đã nhập kho {soLuong} sản phẩm. SL tồn: {_soLuongTon}");
    }

c. Phương thức XuatKho()

    // 3. Xuất kho (giảm số lượng tồn)
    public void XuatKho(int soLuong)
    {
        // Kiểm tra số lượng xuất phải > 0
        if (soLuong <= 0)
        {
            Console.WriteLine("❌ Số lượng xuất phải lớn hơn 0!");
            return;
        }
        
        // Kiểm tra số lượng tồn có đủ không
        if (soLuong > _soLuongTon)
        {
            Console.WriteLine($"❌ Không đủ hàng! Tồn: {_soLuongTon}, Yêu cầu: {soLuong}");
            return;
        }
        
        // Giảm số lượng tồn
        _soLuongTon -= soLuong;
        Console.WriteLine($"✓ Đã xuất kho {soLuong} sản phẩm. SL tồn: {_soLuongTon}");
    }

4.6. Code hoàn chỉnh của SanPham.cs

using System;

namespace Bai2_SanPham.Models
{
    public class SanPham
    {
        // === FIELDS ===
        private string _maSP;
        private string _tenSP;
        private decimal _donGia;
        private int _soLuongTon;
        private static int _soLuongSP = 0;

        // === PROPERTIES ===
        
        // 1. Mã sản phẩm - Read-only
        public string MaSP => _maSP;

        // 2. Tên sản phẩm - Có validation
        public string TenSP
        {
            get => _tenSP;
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentException("Tên sản phẩm không được để trống!");
                _tenSP = value.Trim();
            }
        }

        // 3. Đơn giá - Không được âm
        public decimal DonGia
        {
            get => _donGia;
            set
            {
                if (value < 0)
                    throw new ArgumentException("Đơn giá không được âm!");
                _donGia = value;
            }
        }

        // 4. Số lượng tồn - Không được âm
        public int SoLuongTon
        {
            get => _soLuongTon;
            set
            {
                if (value < 0)
                    throw new ArgumentException("Số lượng tồn không được âm!");
                _soLuongTon = value;
            }
        }

        // 5. Giá trị tồn kho - Tự động tính
        public decimal GiaTriTon => _soLuongTon * _donGia;

        // === CONSTRUCTORS ===
        
        // Constructor mặc định - Tự sinh mã SP
        public SanPham()
        {
            _soLuongSP++;
            _maSP = "SP" + _soLuongSP.ToString("D3");
            _tenSP = "Chưa xác định";
            _donGia = 0;
            _soLuongTon = 0;
        }

        // Constructor có tham số
        public SanPham(string tenSP, decimal donGia, int soLuongTon) : this()
        {
            TenSP = tenSP;
            DonGia = donGia;
            SoLuongTon = soLuongTon;
        }

        // === METHODS ===
        
        // 1. Hiển thị thông tin
        public void HienThi()
        {
            Console.WriteLine("═══════════════════════════════════════");
            Console.WriteLine($"  Mã SP         : {_maSP}");
            Console.WriteLine($"  Tên SP        : {_tenSP}");
            Console.WriteLine($"  Đơn giá       : {_donGia:N0} VND");
            Console.WriteLine($"  SL Tồn        : {_soLuongTon}");
            Console.WriteLine($"  Giá trị tồn   : {GiaTriTon:N0} VND");
            Console.WriteLine("═══════════════════════════════════════");
        }

        // 2. Nhập kho
        public void NhapKho(int soLuong)
        {
            if (soLuong <= 0)
            {
                Console.WriteLine("❌ Số lượng nhập phải lớn hơn 0!");
                return;
            }
            _soLuongTon += soLuong;
            Console.WriteLine($"✓ Đã nhập kho {soLuong} sản phẩm. SL tồn: {_soLuongTon}");
        }

        // 3. Xuất kho
        public void XuatKho(int soLuong)
        {
            if (soLuong <= 0)
            {
                Console.WriteLine("❌ Số lượng xuất phải lớn hơn 0!");
                return;
            }
            if (soLuong > _soLuongTon)
            {
                Console.WriteLine($"❌ Không đủ hàng! Tồn: {_soLuongTon}, Yêu cầu: {soLuong}");
                return;
            }
            _soLuongTon -= soLuong;
            Console.WriteLine($"✓ Đã xuất kho {soLuong} sản phẩm. SL tồn: {_soLuongTon}");
        }
    }
}

BƯỚC 5: VIẾT CHƯƠNG TRÌNH KIỂM TRA (TEST)

5.1. Mở file Program.cs

Trong Solution Explorer, mở file Program.cs của Project Bai2_SanPham.

5.2. Viết code test

csharp
using System;
using Bai2_SanPham.Models;  // Import namespace để dùng class SanPham

namespace Bai2_SanPham
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.UTF8;
            Console.WriteLine("===== BÀI TẬP 2: LỚP SẢN PHẨM =====\n");

            // ============================================
            // TEST 1: Constructor mặc định
            // ============================================
            Console.WriteLine("1. Constructor mặc định:");
            SanPham sp1 = new SanPham();  // Tạo SP với giá trị mặc định
            sp1.HienThi();                // Hiển thị thông tin

            // ============================================
            // TEST 2: Constructor có tham số
            // ============================================
            Console.WriteLine("\n2. Constructor có tham số:");
            SanPham sp2 = new SanPham("Laptop Dell", 15000000, 10);
            sp2.HienThi();

            // ============================================
            // TEST 3: Tạo thêm sản phẩm để xem tự sinh mã
            // ============================================
            Console.WriteLine("\n3. Tạo thêm sản phẩm:");
            SanPham sp3 = new SanPham("Điện thoại iPhone", 25000000, 5);
            SanPham sp4 = new SanPham("Máy tính bảng", 8000000, 8);
            
            Console.WriteLine($"   Mã SP3: {sp3.MaSP}");
            Console.WriteLine($"   Mã SP4: {sp4.MaSP}");

            // ============================================
            // TEST 4: Nhập kho
            // ============================================
            Console.WriteLine("\n4. Nhập kho:");
            sp2.NhapKho(5);      // Nhập thêm 5 sản phẩm
            sp2.HienThi();       // Xem lại thông tin

            // ============================================
            // TEST 5: Xuất kho (đủ hàng)
            // ============================================
            Console.WriteLine("\n5. Xuất kho (đủ hàng):");
            sp2.XuatKho(3);      // Xuất 3 sản phẩm
            sp2.HienThi();

            // ============================================
            // TEST 6: Xuất kho (không đủ hàng)
            // ============================================
            Console.WriteLine("\n6. Xuất kho (không đủ hàng):");
            sp2.XuatKho(20);     // Yêu cầu 20 nhưng chỉ có 12

            // ============================================
            // TEST 7: Validation - Tên rỗng
            // ============================================
            Console.WriteLine("\n7. Kiểm tra validation (Tên rỗng):");
            try
            {
                SanPham sp5 = new SanPham("", 100000, 5);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"   ❌ Lỗi: {ex.Message}");
            }

            // ============================================
            // TEST 8: Validation - Đơn giá âm
            // ============================================
            Console.WriteLine("\n8. Kiểm tra validation (Đơn giá âm):");
            try
            {
                SanPham sp6 = new SanPham("Sản phẩm test", -50000, 5);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"   ❌ Lỗi: {ex.Message}");
            }

            Console.WriteLine("\nNhấn phím bất kỳ để thoát...");
            Console.ReadKey();
        }
    }
}

BƯỚC 6: CHẠY CHƯƠNG TRÌNH

6.1. Build Project

  • Nhấn Ctrl + Shift + B để biên dịch

  • Hoặc menu Build → Build Solution

6.2. Chạy chương trình

  • Nhấn F5 để chạy (Debug mode)

  • Hoặc Ctrl + F5 để chạy không debug


KẾT QUẢ DỰ KIẾN

text
===== BÀI TẬP 2: LỚP SẢN PHẨM =====

1. Constructor mặc định:
═══════════════════════════════════════
  Mã SP         : SP001
  Tên SP        : Chưa xác định
  Đơn giá       : 0 VND
  SL Tồn        : 0
  Giá trị tồn   : 0 VND
═══════════════════════════════════════

2. Constructor có tham số:
═══════════════════════════════════════
  Mã SP         : SP002
  Tên SP        : Laptop Dell
  Đơn giá       : 15,000,000 VND
  SL Tồn        : 10
  Giá trị tồn   : 150,000,000 VND
═══════════════════════════════════════

3. Tạo thêm sản phẩm:
   Mã SP3: SP003
   Mã SP4: SP004

4. Nhập kho:
✓ Đã nhập kho 5 sản phẩm. SL tồn: 15
═══════════════════════════════════════
  Mã SP         : SP002
  Tên SP        : Laptop Dell
  Đơn giá       : 15,000,000 VND
  SL Tồn        : 15
  Giá trị tồn   : 225,000,000 VND
═══════════════════════════════════════

5. Xuất kho (đủ hàng):
✓ Đã xuất kho 3 sản phẩm. SL tồn: 12
═══════════════════════════════════════
  Mã SP         : SP002
  Tên SP        : Laptop Dell
  Đơn giá       : 15,000,000 VND
  SL Tồn        : 12
  Giá trị tồn   : 180,000,000 VND
═══════════════════════════════════════

6. Xuất kho (không đủ hàng):
❌ Không đủ hàng! Tồn: 12, Yêu cầu: 20

7. Kiểm tra validation (Tên rỗng):
   ❌ Lỗi: Tên sản phẩm không được để trống!

8. Kiểm tra validation (Đơn giá âm):
   ❌ Lỗi: Đơn giá không được âm!

Nhấn phím bất kỳ để thoát...

BÀI TẬP VỀ NHÀ MỞ RỘNG

  1. Thêm thuộc tính NhaSanXuat (Nhà sản xuất)

  2. Thêm phương thức CapNhatGia(decimal giaMoi)

  3. Thêm phương thức KiemTraTonKho(int soLuongYeuCau) trả về true/false

  4. Tạo danh sách 5 sản phẩm và hiển thị báo cáo tổng giá trị tồn kho

  5. Thêm validation cho tên sản phẩm (tối thiểu 2 ký tự)