Download as pdf or txt
Download as pdf or txt
You are on page 1of 47

KỸ THUẬT LẬP TRÌNH NÂNG CAO

Bài 3: C# OOP

Huỳnh Quốc Thịnh


Email: hqthinh@hcmus.edu.vn
Ngày 5 tháng 4 năm 2024
Bộ môn Điện tử, Khoa Điện tử - Viễn thông, Đại học Khoa học Tự nhiên - Tp. Hồ Chí Minh
TỪ KHÓA STATIC
Các Thành Phần Static

Ta có thể khai báo một class là static (VD: pubic static class Car).
Tương tự, các thành phần sau cũng có thể được khai báo bằng static:

• Trường hoặc dữ liệu của class.


• Methods.
• Properties.
• Constructor.

1
Các Thành Phần Static

• Các thành phần được khai báo bằng static sẽ được khởi tạo và
tồn tại ở cấp độ class. Do đó ta chỉ có thể truy xuất các thành
phần này ở cấp độ class.
// Error! WriteLine() is a static method. Object level is not allowed
Console c = new Console();
c.WriteLine("I can't be printed...");
// Correct! Access at class level
Console.WriteLine("Much better! Thanks...");

• Từ khóa static thường được sử dụng trong các class utility như
Console, Math,....

2
Static Fields

Khai báo và sử dụng static fields.


class SavingsAccount
{
// Instance -level data.
public double currBalance;
// A static point of data.
public static double currInterestRate = 0.04;
public SavingsAccount(double balance)
{
currBalance = balance;
}
}
static void Main(string[] args)
{
SavingsAccount s1 = new SavingsAccount(50);
SavingsAccount s2 = new SavingsAccount(100);
SavingsAccount s3 = new SavingsAccount(10000.75);
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.currInterestRate);
Console.ReadLine();
}

3
Static Fields

• Tất cả các object của một class sẽ dùng chung static fields của
class đó.
• Như ví dụ trước:
• s1, s2, và s3 sẽ sử dụng riêng trường currBalance. Tất nhiên khi
currBalance của s1 thay đổi, thì s2 và s3 cũng không bị ảnh hưởng.
• currInterestRate được lưu ở class và được dùng chung cho cả 3
object. Bất cứ thay đổi nào trên biến này đều sẽ ảnh hưởng tới s1,
s2, và s3.

4
Static Methods

• Non-static methods có thể truy xuất tới cả static hoặc non-static


fields, methods,... trong cùng class. VD:
public void AddBalanceInterest()
{
currBalance += currBalance*currInterestRate;
}

• Static methods chỉ có thể truy xuất tới các thành phần cũng
được khai báo là static. VD:
//Correct: A static method can access a static field
public static void PrintInterestRate()
{
Console.WriteLine("Current Interest: {0}", currInterestRate);
}
//Error! A static method can NOT access a non-static field (currBalance)
public static void AddBalance(double balance)
{
currBalance += balance;
}

5
Static Methods

• Từ các class khác, static methods cần được truy xuất từ cấp độ
class. Còn non-static methods cần được truy xuất từ object.
static void Main(string[] args)
{
SavingsAccount s1 = new SavingsAccount(50);

//Correct: AddBalance() is non-static -> object level access


s1.AddBalance(50);
//Correct: PrintInterestRate() is static -> class level access
SavingsAccount.PrintInterestRate();

//Error!
s1.PrintInterestRate();
//Error!
SavingsAccount.AddBalance(50);
}

6
Static Constructors

• Để khởi tạo biến static, ta phải sử dụng static constructor.


static SavingsAccount()
{
Console.WriteLine("In static ctor!");
//The value 0.04 may be obtained by a read from database
//before applying to currInterestRate
currInterestRate = 0.04;
}

• Một class chỉ có thể có 1 static constructor.


• Khai báo static constructor:
• Không có biến truyền vào.
• Không có lệnh khai báo quyền truy cập (public, private,...)
• Static constructor (nếu có) là constructor được chạy đầu tiên khi
khai báo object của class hoặc truy xuất các thành phần static.
• Trong toàn bộ thời gian hoạt động của chương trình, static
constructor chỉ hoạt động một lần duy nhất.

7
Static Constructors

Khai báo dữ liệu static trong non-static constructor chắc chắn sẽ gây
ra các lỗi về logic của chương trình!
//Non-static constructor
public SavingsAccount(double balance)
{
// This is static data! -> potential error
currInterestRate = 0.04;
currBalance = balance;
}
static void Main(string[] args)
{
// Make an account.
SavingsAccount s1 = new SavingsAccount(50);
// Print the current interest rate.
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
// Change the interest rate
SavingsAccount.currInterestRate = 0.08;
// Make a second account.
SavingsAccount s2 = new SavingsAccount(100);
// Should print 0.08...right??
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Console.ReadLine();
}

8
Static Constructors

• Ta có thể khai báo một class là static. VD:


//A static class
static class SavingsAccount
{
...
}

• Tất cả các phần tử trong một static class đều phải được khai báo
là static.
• Do đó, ta không thể tạo object từ static class (VD: class Console).
• Tất nhiên, tất cả các phần tử trong static class đều phải được
truy xuất thông qua class.

9
PROPERTIES
Public & Private

• Để kiểm xoát khả năng truy cập tới các phần tử trong class. C# sử
dụng một số từ khóa như public, private, internal, sealed,...
• public: cho phép tất cả các class/struct/method khác class khai
báo được phép truy xuất.
static void Main()
{
SavingsAccount s1 = new SavingsAccount(50);
//currBalance is public so Main() can access it
s1.currBalance = 30
}

• private: Chỉ có class khai báo được truy xuất. Tất cả thành phần
nằm ngoài class khai báo đều không thể truy cập.
class SavingsAccount
{
private double currBalance;
}
static void Main()
{
SavingsAccount s1 = new SavingsAccount();
//Error! currBalance is private so Main() can no longer access it
s1.currBalance = 30
}
10
Tại Sao Cần Dùng private?

Cho class Employee sau:


class Employee
{
// Fields.
public string empName;
public int empID;
public float currPay;

// Constructors.
public Employee() { }
public Employee(string name, int id, float pay)
{
empName = name;
empID = id;
currPay = pay;
}

public void DisplayStats()


{
Console.WriteLine("Name: {0}", empName);
Console.WriteLine("ID: {0}", empID);
Console.WriteLine("Pay: {0}", currPay);
}
}

11
Tại Sao Cần Dùng private?

• Nếu ta quy định empID luôn có 4 chữ số. Vậy ta có thể khai báo
hàm:
public void SetEmpID(int id)
{
if (id < 1000 || id > 9999)
Console.WriteLine("Error: 1000 <= id <= 9999");
else
empID = id;
}

• Để ngăn người sử dụng thay đổi trực tiếp giá trị trên empID, ta
cần chuyển quyền truy xuất về private:
class SavingsAccount
{
private int empID;
...

public Employee(string name, int id, float pay)


{
empName = name;
SetEmpID(id);
currPay = pay;
}
}
12
Properties

• Nếu ta cho phép đọc giá trị của empID. Vậy ta cần thể khai báo
thêm hàm:
public int GetEmpID()
{
return empID;
}

• Sau khi chương trình được phát hành, ta nhận thấy empName
không thể có hơn 16 ký tự do không đủ chỗ hiển thị. Vậy ta cần
lặp lại các bước trên! Đồng thời ta cần thay đổi lệnh truy xuất tới
empName của class Employee ở tất cả các class, struct,
method,... khác.
• Để tránh hiện tượng này, ta phải dùng propery ngay từ khi bắt
đầu xây dựng class.

13
Khai Báo Properties

private string empName;


public string Name
{
get { return empName; }
set
{
if (value.Length > 15)
Console.WriteLine("Error! Name must be less than 16characters!");
else
empName = value;
}
}

• Name: tên của property. Ta có thể đặt tên bất kỳ. Tuy nhiên theo
quy định thì thường dùng tên giống với trường.
• Việc đọc empName được kiểm soát bởi lệnh get.
• Việc gán giá trị cho empName được kiểm soát bởi lệnh set.
value là giá trị được gán.

14
Sử Dụng Properties

Việc sử dụng property giống như sử dụng fields


static void Main(string[] args)
{
Employee emp = new Employee("Marvin", 456, 30000);
//Using Set of Name property.
emp.Name = "Marv";
//Using Get of Name property.
Console.WriteLine("Employee is named: {0}", emp.Name);
Console.ReadLine();
}

15
Properties

• Property dùng để kiểm tra quyền truy xuất trường nên một
property sẽ đi chung với một trường của class.
• Property có thể chỉ khai báo với lệnh get hoặc set.
• Property chỉ có lệnh get được gọi là read-only property.
• Property chỉ có lệnh set được gọi là write-only property.
• Ta cũng có thể khai báo một property là static tương tự như field.

16
Automatic Properties

• Để tránh vấn đề ở slide 13, ta cần khai báo tất cả các field là
private và khai báo property cho tất cả các field này.
• Để thuận tiện, C# hỗ trợ automatic property:
class Employee
{
public string empName { get; set; }
public int empID { get; set; }
public float currPay { get; set; }
}

• Automatic property:
• Không cần khai báo field tương ứng.
• get, set không có nội dung.
• Mục đích của automatic property là để tạo điều kiện cho người
lập trình dễ dàng thay đổi cách truy xuất dữ liệu.

17
ENCAPSULATION (ĐÓNG GÓI)
Ba Nguyên Lý Chính trong OOP

• Đóng gói (Encapsulation): Giấu chi tiết thực hiện của đối tượng
nhằm đơn giản hóa quá trình sử dụng class và đảm bảo tính
toàn vẹn của đối tượng.
• Thừa hưởng (Inheritance): Giúp tái sử dụng các class cũ trong
việc xây dựng các class mới.
• Đa dạng (Polymorphism): Giúp các đối tượng có cùng mối quan
hệ được xử lý một cách tương tự như nhau.

18
Encapsulation

• Class DatabaseReader ‘đóng’ chi tiết thực hiện việc truy xuất cơ
sở dữ liệu. Người sử dụng các object từ class này không cần phải
là chuyên gia về cơ sở dữ liệu. Họ chỉ cần quan tâm đến cách gọi
các hàm như Open() và Close().
// Assume this class encapsulates the details of opening
and closing a database.
DatabaseReader dbReader = new DatabaseReader();
dbReader.Open(@"C:\AutoLot.mdf");
// Do something with data file then close the file.
dbReader.Close();

• Console.WriteLine(), Console.ReadLine() cũng là 2 ví dụ cơ bản


trong nguyên lý đóng gói. Người sử dụng không cần quan tâm tới
việc các hàm này được viết như thế nào. Họ chỉ cần quan tâm tới
việc sử dụng.
• Ngoài ra, encapsulation cũng hỗ trợ cho việc bảo vệ dữ
liệu/trạng thái của đối tượng khỏi sự thay đổi của người dùng.

19
Encapsulation trong C#

• Các câu lệnh kiểm xoát quyền truy cập như private, protected,...
bảo đảm tính toàn vẹn của dữ liệu cũng như giấu chi tiết của
class khỏi người dùng.
• Property bảo đảm việc đọc/ghi tới trường được kiểm xoát nhờ
đó đảm bảo được tính toàn vẹn của dữ liệu.
• Ngoài ra, C# còn các lệnh khác như constant, readonly,...

20
INHERITANCE (THỪA HƯỞNG)
Thừa Hưởng

class Car
{
public readonly int maxSpeed; //read-only field
private int currSpeed; //private field
public Car(int max) //constructor
{
maxSpeed = max;
}
public Car() //constructor
{
maxSpeed = 55;
}
public int Speed //property
{
get { return currSpeed; }
set
{
currSpeed = value;
if (currSpeed > maxSpeed)
{
currSpeed = maxSpeed;
}
}
}
}

21
Thừa Hưởng

• Giả sử nếu ta cần xây dựng một class mới tên MiniVan. Class này
cũng sẽ có các thành phần cơ bản như Car nhưng cũng sẽ có
thêm một số phần tử mới.
• Để tránh viết lại nội dung như class Car, ta có thể dùng thừa
hưởng:
// MiniVan "is-a" Car.
class MiniVan : Car
{ }

• Ta gọi MiniVan là class con (child class, derived class) của Car.
Còn Car là class cha (parent class) hay class nền (base class) của
MiniVan.
• Class MiniVan và các object của class này có thể truy xuất trực
tiếp tới các thành phần public trong class Car.

22
Thừa Hưởng

static void Main(string[] args)


{
MiniVan myVan = new MiniVan();
myVan.Speed = 10;
Console.WriteLine("My van is going {0} MPH",myVan.Speed);
myVan.Speed = 66;
Console.WriteLine("My van is going {0} MPH",myVan.Speed);
Console.ReadLine();
}

23
Thừa Hưởng trong C#

• Trong C#, một class chỉ có thể thừa hưởng từ một class duy nhất.
• Ta có thể dùng từ khóa sealed trong khai báo class để cấm việc
thừa hưởng ở các class khác.
• Class con thừa hưởng hầu hết các phần tử của base class
(properties, methods,...) ngoại trừ constructor.

24
Quyền Truy Cập trong Thừa Hưởng

• public: Như đã học, quyền truy cập của các phần tử không bị giới
hạn dù ở trong hay ngoài class.
• private: Như đã học, object của class MiniVan KHÔNG thể truy
cập tới phần từ private của Car (VD: currSpeed)
• protected: Cho phép class khai báo và các class con của class
khai báo truy xuất tới phần tử.

25
Constructor trong Class Con

class Employee
{
public readonly string SecurityNumber { get; set; }
public string Name { get; set; }
public int ID { get; set; }
public int Age { get; set; }
public float Pay { get; set; }
// Constructors.
public Employee() { }
public Employee(string name, int id, int age, float pay, string ssn)
{
Name = name;
ID = id;
Age = age;
Pay = pay;
SecurityNumber = ssn;
}
public void DisplayStats()
{
Console.WriteLine("Name: {0}, ID: {1}, Age{2}", Name, ID, Age);
}
}

26
Constructor trong Class Con

• Class con không thừa hưởng constructor. Khi khởi tạo object, các
phần tử được khai báo ở base class sẽ được khởi tạo bằng
constructor của base class.
• Khi khai báo constructor trong class con ta cần dùng constructor
chaining:
public Manager(string fullName , int age, int empID, float currPay , string
ssn, int
numbOfOpts)
: base(fullName , age, empID, currPay , ssn)
{
StockOptions = numbOfOpts;
}

27
POLYMORPHIC (ĐA DẠNG)
Tính Đa Dạng

class Shape
{
public Shape(string name = "NoName")
{ PetName = name; }
public string PetName { get; set; }
public void Draw()
{ //Assume that we are drawing something.
Console.WriteLine("Inside Shape.Draw()");
}
}
class Circle : Shape
{
public Circle() { }
public Circle(string name) : base(name) { }
}
class Rectangular: Shape
{
public Rectangular() { }
public Rectangular(string name) : base(name) { }
}

28
Tính Đa Dạng

Tính đa dạng: các object có cùng quan hệ (Shape là base class còn
Circle và Rectangular là class con.) được xử lý một cách tương tự:
static void Main(string[] args)
{
// Make an array of Shape-compatible objects.
Shape[] myShapes = {new Rectangular(), new Circle(), new Rectangular("Mick"),
new Circle("Beth"), new Rectangular("Linda")};
// Loop over each item and interact with the
// polymorphic interface.
foreach (Shape s in myShapes)
{
s.Draw();
}
Console.ReadLine();
}

Class Shape có khai báo hàm Draw() nên chắc chắn các class con
đều sẽ thừa hưởng hàm này.

29
Chuyển Đổi Kiểu Dữ Liệu giữa Class

static void Main()


{
//Convert Child Class to Base Class
Shape cirSha = new Circle("CirSha");
WriteName(cirSha);
//Normal Init
Rectangular rec1 = new Rectangular("rec1");
WriteName(rec1);
// All object in C\# is a child or grand child of object
object cirObj = new Circle("CirObj");
WriteName((Shape)cirObj); //Convert cirObj to base class
WriteName((Circle)cirObj); //Convert cirObj back to Circle
Console.ReadLine();
}

static void WriteName(Shape sh)


{
Console.WriteLine("{0} is a shape", sh.PetName);
}

30
Chuyển Đổi Kiểu Dữ Liệu giữa Class

• Khi chuyển đổi kiểu dữ liệu giữa của object, luôn sử dụng từ
khóa as
Rectangular rec2 = cirObj as Rectangular;
if (rec2 == null)
Console.WriteLine("Sorry, cirObj is not a Rectangular...");
else
WriteName(rec2);

• Nếu ta chỉ cần kiểm tra kiểu dữ liệu của object, ta có thể dùng từ
khóa is
if (cirObj is Circle)
Console.WriteLine("cirObj is a circle");
if (cirObj is Shape)
Console.WriteLine("cirObj is a shape");
if (cirObj is Rectangular)
Console.WriteLine("cirObj is a rectangular");

31
Từ Khóa virtual & override

• Draw() được khai báo ở Shape nên sẽ thực hiện cùng chức năng
cho cả 3 class Shape, Circle, và Rectangular.
• Làm sao để Draw() trong Circle vẽ hình tròn còn Draw() trong
Rectangular vẽ hình vuông?
• Tất nhiên ta có thể khai báo hàm DrawCircle() trong Circle và
DrawRect() trong Rectangular. Nhưng như vậy sẽ khiến nội dung
lệnh foreach ở slide 29 trở nên phức tạp hơn.
• Để sửa lại Draw() hay bất kỳ một method nào khác ở base class
trong class con, ta sẽ dùng virtual & override.

32
Từ Khóa virtual & override

Chạy lại hàm Main() sau khi sửa lại các class như sau:
class Shape
{
...
public virtual void Draw() // A single virtual method.
{
Console.WriteLine("Drawing Done!");
}
}
class Circle : Shape // Circle DOES NOT override Draw().
{
public Circle() { }
public Circle(string name) : base(name) { }
}
class Rectangular : Shape // Rectangular DOES override Draw().
{
public Rectangular() { }
public Rectangular(string name) : base(name) { }
public override void Draw()
{
Console.WriteLine("Drawing {0} the Rectangular", PetName);
base.Draw();
}
}

33
ABSTRACT CLASSES
Abstract Classes

• Nếu ta không muốn người dùng tạo object từ base classes như
Shape, hoặc Employee. Ta có thể chuyển các class này thành
abstract classes. VD:
abstract class Shape
{
...
}

• Ngoài ra, ta còn có thể khai báo abstract methods trong abstract
classes. VD:
public abstract void Draw();

34
Abstract Method

• Một số method trong abstract class có thể không cần nội dung.
VD: Draw() trong class Shape vì tất cả các class con đều phải viết
lại hàm lại.
• Để bảo đảm tất cả các class con đều phải viết lại một hoặc
nhiều method của base class. Ta có thể dùng abstract method.
• Abstract method là các hàm chỉ được khai báo trong base class
ngoài ra không có nội dung.

35
Abstract Method

// The abstract base class of the hierarchy.


abstract class Shape
{
public string PetName { get; set; }
public Shape(string name = "NoName")
{ PetName = name; }
public abstract void Draw();
}
// Circle MUST override Draw().
class Circle : Shape
{
public Circle() { }
public Circle(string name) : base(name) { }
public override void Draw()
{
Console.WriteLine("Drawing {0} the Circle", PetName);
}
}

36
BÀI TẬP
Đọc Thêm

• Đọc object initialization syntax, constant field, và partial type từ


trang 203 đến 211 [Andrew Troelsen, 2012]
• Đọc member shadowing từ trang 239 đến 240
[Andrew Troelsen, 2012]

37
Bài Tập

• Viết Cows & Bulls với vai trò người chơi 2 (người đoán) theo các
bước sau:
• Tạo parent class để lưu field Bulls, Cows, và các method chung cho
cả PlayerOne và PlayerTwo. Sử dụng static với method đểm số
lượng Cows và Bulls.
• Viết class để tạo một list chứa tất cả các số phù hợp với quy tắc trò
chơi. Số lượng chữ số được khởi tạo từ constructor ở class gọi. Sử
dụng đệ quy nếu cần để tối ưu thuật toán. VD: mỗi số có 3 chữ số
thì class sẽ tạo 720 số, 4 chữ số -> 5040 số.
• Nghiên cứu paper: “A Mathematical Approach to Simple Bulls and
Cows”, Namanyay Goel, để tối ưu số lần đoán.

38
Tài Liệu

Pro C# 5.0 and the .NET 4.5 Framework 6 edition.


Andrew Troelsen
Apress, 2012.

39

You might also like