Slide
Lý thuyết
Tính kế thừa – Inheritance
Một trong những đặc tính quan trọng nhất của OOP là kế thừa. Ưu điểm của đặc tính kế thừa: sử dụng lại các đoạn code đã có trong chương trình 1 cách hiệu quả. Khi tạo 1 class, thay vì việc viết 1 class mới hoàn toàn, người lập trình viên có thể kế thừa một số thuộc tính và phương thức từ 1 class đã có trong project. Class đã có trước đấy gọi là lớp cơ sở (Base Class), class kế thừa từ Base Class (hay superclass) gọi là lớp dẫn xuất (Derived Class).
Tính kế thừa (Inheritance) đóng vai trò cực kỳ quan trọng và là một trong những trụ cột định hình nên sức mạnh của OOP. Nó là cơ chế chính thúc đẩy tái sử dụng mã, cho phép tạo cấu trúc phân cấp logic giữa các lớp và đồng thời là nền tảng thiết yếu cho tính đa hình.
Có thể nói, kế thừa không chỉ là một kỹ thuật code đơn thuần mà còn là một phần quan trọng trong tư duy thiết kế hướng đối tượng, giúp tạo ra các phần mềm có cấu trúc tốt, dễ quản lý và linh hoạt hơn hẳn so với các phương pháp lập trình truyền thống.
Tái sử dụng mã – Đây là lợi ích cốt lõi và dễ nhận thấy nhất của kế thừa. Thay vì phải viết đi viết lại các thuộc tính và phương thức chung cho nhiều lớp có liên quan, bạn chỉ cần định nghĩa chúng một lần duy nhất trong lớp cha (superclass) là đủ.
Các lớp con (subclasses) sau đó sẽ tự động “thừa hưởng” và có thể sử dụng lại các thành viên đó. Điều này giúp tiết kiệm đáng kể thời gian phát triển, giảm thiểu code trùng lặp, hạn chế lỗi và làm cho việc bảo trì (sửa lỗi, cập nhật) trở nên đơn giản hơn nhiều.
Ví dụ thực tế:
Lớp cha: Người lao động (có các thuộc tính như tiền lương, giờ làm việc, nơi làm việc).
Lớp con: Công nhân, Bác sĩ, Lập trình viên (mỗi lớp con đều có những đặc điểm chung từ lớp cha, nhưng thêm các đặc tính riêng).



Code ví dụ
Ví dụ 1
class Vehicle // base class (parent)
{
public string brand = "Ford"; // Vehicle field
public void honk() // Vehicle method
{
Console.WriteLine("Tuut, tuut!");
}
}
class Car : Vehicle // derived class (child)
{
public string modelName = "Mustang"; // Car field
}
class Program
{
static void Main(string[] args)
{
// Create a myCar object
Car myCar = new Car();
// Call the honk() method (From the Vehicle class) on the myCar object
myCar.honk();
// Display the value of the brand field (from the Vehicle class) and the value of the modelName from the Car class
Console.WriteLine(myCar.brand + " " + myCar.modelName);
}
}
Ví dụ 2
using System;
namespace Inheritance {
// base class
class Animal {
public string name;
public void display() {
Console.WriteLine("I am an animal");
}
}
// derived class of Animal
class Dog : Animal {
public void getName() {
Console.WriteLine("My name is " + name);
}
}
class Program {
static void Main(string[] args) {
// object of derived class
Dog labrador = new Dog();
// access field and method of base class
labrador.name = "Rohu";
labrador.display();
// access method from own class
labrador.getName();
Console.ReadLine();
}
}
}
Ví dụ 3
// C# program to illustrate the above concept
using System;
// Base Class
public class Father
{
public string FatherName()
{
return "Ravi";
}
}
// Derived Class
public class ChildFirst : Father
{
public string ChildDName()
{
return "Rohan";
}
}
// Derived Class
public class ChildSecond : Father
{
public string ChildDName()
{
return "Nikhil";
}
}
class GFG{
static public void Main()
{
ChildFirst first = new ChildFirst();
// Displaying Child Name and Father Name for
// ChildFirst
Console.WriteLine("My name is " + first.ChildDName() +
". My father name is " +
first.FatherName() + ".");
ChildSecond second = new ChildSecond();
// Displaying Child Name and Father Name for
// ChildSecond
Console.WriteLine("My name is " + second.ChildDName() +
". My father name is " +
second.FatherName() + ".");
}
}
Ví dụ 4
// C# program to illustrate the above concept
using System;
// Base Class
class Person
{
public string name;
public int aadhar_id;
public int age;
public Person(int aadhar_id, int age, string name)
{
this.aadhar_id = aadhar_id;
this.name = name;
this.age = age;
}
}
// Derived Class
class Teacher : Person
{
public int teacher_salary;
public Teacher(int aadhar_id, int salary,
string name, int age) : base(aadhar_id,
age, name)
{
teacher_salary = salary;
}
public void TeacherDetails()
{
Console.WriteLine("teacher ID: " + aadhar_id);
Console.WriteLine("teacher Name: " + name);
Console.WriteLine("teacher Salary: " + teacher_salary);
Console.WriteLine("teacher Age: " + age);
}
}
// Derived Class
class Doctor : Person
{
public int doctor_fees;
public Doctor(int aadhar_id, int fees,
string name, int age) : base(aadhar_id,
age, name)
{
doctor_fees = fees;
}
public void DoctorDetails()
{
Console.WriteLine("Doctor ID: " + aadhar_id);
Console.WriteLine("Doctor Name: " + name);
Console.WriteLine("Doctor Fees: " + doctor_fees);
Console.WriteLine("Doctor Age: " + age);
}
}
class GFG{
static public void Main()
{
// Creating objects
Teacher t = new Teacher(25054, 50000, "Sanjay", 28);
Doctor d = new Doctor(25045, 750, "Rohit", 32);
t.TeacherDetails();
Console.WriteLine(
"-----------------------------------");
d.DoctorDetails();
}
}
Ví dụ 5
public class Account
{
public string AccountNumber { get; set; }
public double Balance { get; set; }
public void Deposit(double amount)
{
Balance += amount;
Console.WriteLine($"Deposited {amount}. New Balance: {Balance}");
}
public void Withdraw(double amount)
{
if (Balance >= amount)
{
Balance -= amount;
Console.WriteLine($"Withdrawn {amount}. Remaining Balance: {Balance}");
}
else
{
Console.WriteLine("Insufficient funds.");
}
}
}
public class SavingsAccount : Account
{
public double InterestRate { get; set; }
public void ApplyInterest()
{
double interest = Balance * InterestRate / 100;
Balance += interest;
Console.WriteLine($"Interest applied: {interest}. New Balance: {Balance}");
}
}
class Program
{
static void Main(string[] args)
{
SavingsAccount savings = new SavingsAccount();
savings.AccountNumber = "SA12345";
savings.Balance = 1000;
savings.InterestRate = 5;
savings.Deposit(500);
savings.Withdraw(200);
savings.ApplyInterest();
}
}
Ghi Đè Phương Thức (Method Overriding)
Cho phép lớp con thay đổi cách triển khai phương thức của lớp cha.
Sử dụng từ khóa virtual ở lớp cha và override ở lớp con.
class Animal
{
public virtual void Speak()
{
Console.WriteLine("The animal makes a sound.");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("The dog barks.");
}
}
// Sử dụng
Animal myDog = new Dog();
myDog.Speak(); // The dog barks.
Ví dụ khác về ghi đè override
namespace Inheritance {
// base class
class Animal {
public virtual void eat() {
Console.WriteLine("I eat food");
}
}
// derived class of Animal
class Dog : Animal {
// overriding method from Animal
public override void eat() {
Console.WriteLine("I eat Dog food");
}
}
class Program {
static void Main(string[] args) {
// object of derived class
Dog labrador = new Dog();
// accesses overridden method
labrador.eat();
}
}
}
Nạp chồng (Overloading)
Trong OOP (Lập trình hướng đối tượng), “nạp chồng” (Overloading) là kỹ thuật cho phép có nhiều phương thức (Method Overloading) hoặc toán tử (Operator Overloading) có cùng tên nhưng khác nhau về số lượng hoặc kiểu tham số (đối số) trong cùng một lớp (class), giúp code ngắn gọn, dễ hiểu hơn và thể hiện tính đa hình tại thời điểm biên dịch (compile-time polymorphism).
Nạp chồng phương thức (Method Overloading)
- Khái niệm: Tạo nhiều hàm có cùng tên, nhưng khác nhau về danh sách tham số (kiểu hoặc số lượng tham số) trong cùng một lớp.
- Ví dụ: Một lớp Sum có thể có sum(int a, int b) và sum(int a, int b, int c).
Mục đích: Tái sử dụng tên phương thức cho các chức năng tương tự nhưng xử lý dữ liệu khác nhau, làm code trực quan hơn.
Nạp chồng toán tử (Operator Overloading)
- Khái niệm: Định nghĩa lại ý nghĩa của các toán tử (như +, -, *, /) cho các kiểu dữ liệu do người dùng tự định nghĩa (như lớp PhanSo).
- Ví dụ: Nạp chồng toán tử + cho lớp PhanSo để thực hiện phép cộng hai phân số.
- Mục đích: Biểu thức trở nên ngắn gọn, dễ đọc hơn (ví dụ: phanSo1 + phanSo2) thay vì gọi phương thức cong(phanSo1, phanSo2).
Sự khác biệt với Ghi đè (Overriding):
- Nạp chồng (Overloading): Cùng tên, khác tham số, trong cùng một lớp (hoặc lớp cha và lớp con, nhưng khác tham số).
- Ghi đè (Overriding): Cùng tên, cùng tham số, nhưng ở lớp cha và lớp con (lớp con định nghĩa lại phương thức của lớp cha).
class Calculator
{
// Nạp chồng theo số lượng tham số
public int Add(int a, int b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
// Nạp chồng theo kiểu tham số
public double Add(double a, double b)
{
return a + b;
}
}
Sử dụng
Calculator cal = new Calculator();
Console.WriteLine(cal.Add(2, 3)); // 5
Console.WriteLine(cal.Add(2, 3, 4)); // 9
Console.WriteLine(cal.Add(2.5, 3.5)); // 6.0
Một ví dụ khác
class Printer
{
public void Print(string text)
{
Console.WriteLine(text);
}
public void Print(int number)
{
Console.WriteLine("Số: " + number);
}
public void Print(string text, int times)
{
for (int i = 0; i < times; i++)
Console.WriteLine(text);
}
}
Ví dụ khác nữa nè!
class Vector2D
{
public double X { get; set; }
public double Y { get; set; }
public Vector2D(double x, double y)
{
X = x;
Y = y;
}
// Nạp chồng toán tử +
public static Vector2D operator +(Vector2D a, Vector2D b)
{
return new Vector2D(a.X + b.X, a.Y + b.Y);
}
public override string ToString()
{
return $"({X}, {Y})";
}
}
Sử dụng
class Program
{
static void Main()
{
Vector2D v1 = new Vector2D(2, 3);
Vector2D v2 = new Vector2D(4, 5);
Vector2D v3 = v1 + v2; // dùng toán tử + đã được nạp chồng
Console.WriteLine(v3); // (6, 8)
}
}
Lớp bị khóa Sealed Class
sealed class Vehicle
{
...
}
class Car : Vehicle // -> bị lỗi nha vì ko được kế thừa lớp bị khóa bằng từ sealed
{
...
}
Bài tập
Bài tập 1

Mô tả:
§ Class Person
+ Person(string name, int birthYear, string address): Khởi tạo một đối tượng Person với các thông tin cho trước
+ Input(): Phương thức để người dùng lần lượt nhập các thông tin cho một Person
+ GetAge(): Tính tuổi của Person tính đến năm hiện tại
+ ToString(): Trả về chuỗi biểu diễn một Person
“Person[Name: {name }, Age: {birthYear}, Address: {address}]”
§ Class Student
+ Student(string name, int birthYear, string address, string program, int year): Khởi tạo tạo một đối tượng Student với các thông tin cho trước (gọi Constructor của class Person)
+ Input(): Overriding phương thức Input() của class Person để người dùng nhập thêm các thông tin program và year
+ ToString(): Overriding phương thức ToString() của class Person để trả về chuỗi biểu diễn một Student
“Student[ Person[Name: {name }, Age: {birthYear}, Address: {address}] – Program: {program}, Year: {year}]”
§ Class Staff
+ Staff(string name, int birthYear, string address, string department, double salary): Khởi tạo tạo một đối tượng Staff với các thông tin cho trước (gọi Constructor của class Person)
+ Input(): Overriding phương thức Input() của class Person để người dùng nhập thêm các thông tin department và salary
+ ToString(): Overriding phương thức ToString() của class Person để trả về chuỗi biểu diễn một Staff
“Staff[ Person[Name: {name }, Age: {birthYear}, Address: {address}] – Department: {department}, Salary: {salary}]”
class Person
{
protected string name;
protected int birthYear;
protected string address;
public string Name
{
get { return name; }
set { name = value; }
}
public int BirthYear
{
get { return birthYear; }
set { birthYear = value; }
}
public string Address
{
get { return address; }
set { address = value; }
}
public Person() { }
public Person(string name,int birthYear, string address)
{
this.name = name;
this.birthYear = birthYear;
this.address = address;
}
public virtual void Input()
{
Console.WriteLine("Input name: ");
name = Console.ReadLine();
Console.WriteLine("Input birth year: ");
birthYear = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Input address: ");
address = Console.ReadLine();
}
public int GetAge()
{
return DateTime.Now.Year - this.birthYear;
}
public virtual string ToString()
{
return (name + " " + birthYear + " " + address);
}
}
class Student:Person
{
private string program;
private int year;
public string Program
{
get { return program; }
set { program = value; }
}
public int Year
{
get { return year; }
set { year = value; }
}
public Student() { }
public Student(string name, int birthYear, string address, string program, int year) : base(name, birthYear, address)
{
this.program = program;
this.year = year;
}
public override void Input()
{
Console.WriteLine("Input name: ");
name = Console.ReadLine();
Console.WriteLine("Input birth year: ");
birthYear = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Input address: ");
address = Console.ReadLine();
Console.WriteLine("Input program: ");
program = Console.ReadLine();
Console.WriteLine("Input year: ");
year = Convert.ToInt32(Console.ReadLine());
}
public override string ToString()
{
return (name + " " + birthYear + " " + address + " " + program + " " + year);
}
public void ChangeProgram(string program)
{
this.program = program;
}
}
class Staff:Person
{
private string department;
private double salary;
public string Department
{
get { return department; }
set { department = value; }
}
public double Salary
{
get { return salary; }
set { salary = value; }
}
public Staff() { }
public Staff(string name, int birthYear, string address, string department,double salary) : base(name, birthYear, address)
{
this.department = department;
this.salary = salary;
}
public override void Input()
{
Console.WriteLine("Input name: ");
name = Console.ReadLine();
Console.WriteLine("Input birth year: ");
birthYear = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Input address: ");
address = Console.ReadLine();
Console.WriteLine("Input department: ");
department = Console.ReadLine();
Console.WriteLine("Input salary: ");
salary = Convert.ToInt32(Console.ReadLine());
}
public override string ToString()
{
return (name + " " + birthYear + " " + address + " " + department + " " + salary);
}
public void UpdateSalary(double salary)
{
this.salary = salary;
}
}
Bài tập 2

§ Class Fraction
+ Fraction(): Khởi tạo phân số có tử số = 0, mẫu số =1
+ Fraction(int numerator, int denominator): Khởi tạo phân số có tử số = numerator, mẫu số = denominator
+ Fraction(int numerator): Khởi tạo phân số có tử số = numerator, mẫu số = 1
+ ToString(): Trả về chuỗi biểu diễn một phân số có dạng “tử số/mẫu số”
§ Class MixedFraction
+ MixedFraction(int wholePart, int numerator, int denominator): Khởi tạo hỗn số có phần nguyên = wholePart và phần phân số có tử số = numerator, mẫu số = denominator
Gợi ý: Để việc xử lý đơn giản, các hỗn số đều được chuyển về lưu trữ dưới dạng phân số. Do đó constructor sẽ chuyển đổi hỗn số thành phân số và chỉ lưu trữ 2 thuộc tính numerator và denominator mà không cần khai báo thêm thuộc tính wholePart
+ MixedFraction(Fraction f): Khởi tạo hỗn số từ một phân số
+ ToString(): Trả về chuỗi biểu diễn một hỗn số có dạng “phần nguyên [tử số/mẫu số]”
class Fraction
{
protected int numerator;
protected int denominator;
//Property
public int Numerator
{
get { return numerator; }
set { numerator = value; }
}
public int Denominator
{
get { return denominator; }
set { denominator = value; }
}
public Fraction()
{
this.numerator = 0;
this.denominator = 1;
}
public Fraction(int numerator, int denominator)
{
this.numerator = numerator;
this.denominator = denominator;
Simplify();
}
public void Simplify()
{
int uocsochunglonnhat = USCLN(this.numerator, this.denominator);
this.numerator /= uocsochunglonnhat;
this.denominator /= uocsochunglonnhat;
}
static int USCLN(int a, int b)
{
if (b == 0) return a;
return USCLN(b, a % b);
}
public Fraction(int numerator)
{
this.numerator = numerator;
this.denominator = 1;
}
public void Input()
{
Console.Write("Input numerator (Nhập tử số) : ");
this.numerator = Int32.Parse(Console.ReadLine());
int x;
do
{
Console.Write("Inputdenominator (Nhập mẫu số) khác không 0: ");
x = Int32.Parse(Console.ReadLine());
}
while (x == 0);
Simplify();
}
public double Decimal()
{
return (this.numerator / this.denominator);
}
public Fraction Add(Fraction p)
{
Fraction result = new Fraction();
result.numerator = this.numerator * p.denominator + this.denominator * p.numerator;
result.denominator = this.denominator * p.denominator;
result.Simplify();
return result;
}
public Fraction Subtract(Fraction p)
{
Fraction result = new Fraction();
result.numerator = this.numerator * p.denominator - this.denominator * p.numerator;
result.denominator = this.denominator * p.denominator;
result.Simplify();
return result;
}
public Fraction Multiply(Fraction p)
{
Fraction result = new Fraction();
result.numerator = this.numerator * p.numerator;
result.denominator = this.denominator * p.denominator;
result.Simplify();
return result;
}
public Fraction Divide(Fraction p)
{
Fraction result = new Fraction();
result.numerator = this.numerator * p.denominator;
result.denominator = this.denominator * p.numerator;
result.Simplify();
return result;
}
public virtual string ToString()
{
return (this.numerator + "/" + this.denominator);
}
public static Fraction operator -(Fraction a)
{
Fraction c = new Fraction();
c.numerator = -a.numerator;
c.denominator = a.denominator;
return c;
}
public static Fraction operator +(Fraction a, Fraction b)
{
Fraction c = new Fraction();
c.numerator = a.numerator * b.denominator + a.denominator * b.numerator;
c.denominator = a.denominator * b.denominator;
c.Simplify();
return c;
}
public static Fraction operator -(Fraction a, Fraction b)
{
Fraction c = new Fraction();
c.numerator = a.numerator * b.denominator + a.denominator * b.numerator;
c.denominator = a.denominator * b.denominator;
c.Simplify();
return c;
}
public static Fraction operator *(Fraction a, Fraction b)
{
Fraction c = new Fraction();
c.numerator = a.numerator * b.numerator;
c.denominator = a.denominator * b.denominator;
c.Simplify();
return c;
}
public static Fraction operator /(Fraction a, Fraction b)
{
Fraction c = new Fraction();
c.numerator = a.numerator * b.denominator;
c.denominator = a.denominator * b.numerator;
c.Simplify();
return c;
}
public static bool operator ==(Fraction a, Fraction b)
{
if (a.numerator == b.numerator && a.denominator == b.denominator)
return true;
else
return false;
}
public static bool operator !=(Fraction a, Fraction b)
{
if (a.numerator != b.numerator || a.denominator != b.denominator)
return true;
else
return false;
}
public static bool operator >(Fraction a, Fraction b)
{
Fraction a1 = new Fraction();
Fraction b1 = new Fraction();
a1.numerator = a.numerator * b.denominator;
a1.denominator = a.denominator * b.denominator;
b1.numerator = b.numerator * a.denominator;
b1.denominator = b.denominator * a.denominator;
if (a1.numerator>b1.numerator)
return true;
else
return false;
}
public static bool operator <(Fraction a, Fraction b)
{
Fraction a1 = new Fraction();
Fraction b1 = new Fraction();
a1.numerator = a.numerator * b.denominator;
a1.denominator = a.denominator * b.denominator;
b1.numerator = b.numerator * a.denominator;
b1.denominator = b.denominator * a.denominator;
if (a1.numerator < b1.numerator) return true; else return false; } public static bool operator >=(Fraction a, Fraction b)
{
Fraction a1 = new Fraction();
Fraction b1 = new Fraction();
a1.numerator = a.numerator * b.denominator;
a1.denominator = a.denominator * b.denominator;
b1.numerator = b.numerator * a.denominator;
b1.denominator = b.denominator * a.denominator;
if (a1.numerator > b1.numerator)
return true;
else
return false;
}
public static bool operator <=(Fraction a, Fraction b)
{
Fraction a1 = new Fraction();
Fraction b1 = new Fraction();
a1.numerator = a.numerator * b.denominator;
a1.denominator = a.denominator * b.denominator;
b1.numerator = b.numerator * a.denominator;
b1.denominator = b.denominator * a.denominator;
if (a1.numerator < b1.numerator)
return true;
else
return false;
}
}
class MixedFraction:Fraction
{
public MixedFraction(int wholePart, int numerator, int denominator)
{
this.numerator = wholePart * denominator + numerator;
this.denominator = denominator;
this.Simplify();
}
public MixedFraction(Fraction f)
{
this.numerator = f.Numerator;
this.denominator = f.Denominator;
Simplify();
}
public override string ToString()
{
int a = this.numerator / this.denominator;
return (a + "[" + this.numerator%this.denominator + "/" + this.denominator);
}
}
Bài tập 3
Định nghĩa class Caculator theo mô tả dưới đây:

§ Viết hàm Main(), tạo đối tượng của class Caculator và gọi các phương thức tương ứng
§ Chuyển tất cả phương thức thành phương thức tĩnh (static), nhận xét sự khác nhau giữa static method và non-static method
Bài tập 4
Thực hiện nạp chồng các toán tử (operator overloading) cho class Fraction và class MixedFraction đã định nghĩa ở Bài tập 2
§ Nạp chồng toán tử một ngôi “–” để đổi dấu phân số/hỗn số
§ Nạp chồng các toán tử hai ngôi “+, –, *, /” để thực hiện các phép toán giữa 2 phân số/hỗn số
§ Nạp chồng các toán tử so sánh 2 phân số/hỗn số “==, !=, >, <, >=, <=”
class Calculator
{
public Calculator() { }
public static int Add(int a, int b)
{
return (a + b);
}
static double Add(double a, double b)
{
return (a + b);
}
static int Subtract(int a, int b)
{
return (a - b);
}
static double Subtract(double a, double b)
{
return (a - b);
}
static int Multiply(int a, int b)
{
return (a * b);
}
static double Multiply(double a, double b)
{
return (a * b);
}
static int Divide(int a, int b)
{
return (a * b);
}
static double Divide(double a, double b)
{
return (a * b);
}
static int Min(int a, int b)
{
if (a <= b) return a;
else return b;
}
static double Min(double a, double b)
{
if (a <= b) return a; else return b; } static int Max(int a, int b) { if (a >= b) return a;
else return b;
}
static double Max(double a, double b)
{
if (a >= b) return a;
else return b;
}
}