Lập trình hướng đối tượng (OOP) trong C#
Lập trình hướng đối tượng (OOP) là gì? Tại sao lập trình hướng đối tượng lại hay được hỏi khi đi phỏng vấn. Lập trình hướng đối tượng có những tính chất gì? …
Qua bài viết này mình hi vọng sẽ giải đáp được đầy đủ và chính xác về lập trình hướng đối tượng trong C#.
Lập trình hướng đối tượng là gì?
Lập trình hướng đối tượng (OOP – Object Oriented Programming) là một phương pháp lập trình sử dụng các đối tượng (object) để xây dựng hệ thống phần mềm hoặc ứng dụng web.
5 khái niệm trong lập trình hướng đối tượng
Bạn sẽ bắt gặp một số khái niệm như: lớp (class), phương thức (method), thuộc tính (property), trường (field), đối tượng (object) … trong lập trình hướng đối tượng.
Chúng ta cùng tìm hiểu chúng là gì nào.
Lớp (class)
Một class trong lập trình hướng đối tượng là đại diện cho tập hợp các đối tượng có cùng các đặc điểm, hành vi, phương thức hoạt động.
Trong đó các đặc điểm của đối tượng được gọi là các trường (field), các đặc điểm của đối tượng được thể hiện ra bên ngoài thông qua thuộc tính (property) của class.
Các hành vi hay phương thức hoạt động của đối tượng được gọi là phương thức (method) của class.
Các trường, các thuộc tính, các phương thức, … được gọi chung là các thành viên của lớp.
Dưới đây là một ví dụ về class trong C# – lớp Box:
class Box
{
// properties
public double Length { get; set; }
public double Breadth { get; set; }
public double Height { get; set; }
// default constructor
public Box()
{}
// constructor with parameters
public Box(double length, double breadth, double height)
{
Length = length;
Breadth = breadth;
Height = height;
}
// method
public void Print()
{
Console.WriteLine("Length = {0}, Breadth = {1}, Height = {2}", Length, Breadth, Height);
}
}
Các bạn có thể tìm hiểu chi tiết về class tại bài viết này:
Phương thức (method)
Phương thức là các hành vi hay phương thức hoạt động của đối tượng. Phương thức trong C# là một tập các câu lệnh cùng nhau thực hiện một tác vụ nào đó.
Mỗi chương trình C# có ít nhất một lớp với một phương thức là Main.
Để sử dụng một phương thức trong C#, bạn cần:
- Định nghĩa phương thức
- Gọi phương thức
Dưới đây là một ví dụ về phương thức trong C# – phương thức Print của lớp Box:
class Box
{
// method
public void Print()
{
Console.WriteLine("Print something");
}
}
Xem thêm về phương thức trong C# ở bài viết dưới đây:
Trường (field)
Trường là các biến của class hay còn gọi là biến toàn cục. Trường được sử dụng để lưu trữ dữ liệu về các đặc điểm của đối tượng.
Trường nên sử dụng chỉ thị truy cập là private để nó chỉ có thể truy cập được ở bên trong class. Nếu muốn truy cập trường từ bên ngoài class chúng ta sẽ sử dụng thuộc tính (property) sẽ được trình bày ở ngay phía dưới.
Một ví dụ về trường (field) trong C#:
class Box
{
// fields
private double _length;
private double _breadth;
private double _height;
}
Quy ước đặt tên trường hoặc biến toàn cục trong C# là bắt đầu bằng dấu gạch dưới _, ký tự tiếp theo là chữ thường a-z.
Best practice: luôn sử dụng property để thay thế cho các trường public.
Thuộc tính (property)
Một thuộc tính là một thành viên của class, nó cung cấp một cơ chế linh hoạt để truy cập các trường (field) của class.
Trong thuộc tính có 2 phương thức đặc biệt được gọi là accessor đó là get và set. Trong đó get sẽ trả về giá trị của trường còn set sẽ gán giá trị cho trường.
Một ví dụ về thuộc tính trong C#:
class Box
{
// fields
private double _length;
private double _breadth;
private double _height;
// properties
public double Length
{
get
{
return _length;
}
set
{
_length = value;
}
}
public double Breadth
{
get
{
return _breadth;
}
set
{
_breadth = value;
}
}
public double Height
{
get
{
return _height;
}
set
{
_height = value;
}
}
}
Ngoài ra C# còn cung cấp auto-implemented property – một cách định nghĩa nhanh thuộc tính như sau:
class Box
{
// automatic properties
public double Length { get; set; }
public double Breadth { get; set; }
public double Height { get; set; }
}
Khi biên dịch, trình biên dịch sẽ tự động chuyển auto-implemented property về dạng property thông thường với đầy đủ các field cho từng property.
Đối tượng (object)
Một đối tượng là một thực thể trong thế giới thực có các đặc điểm và các hành vi.
Trong C# thì đối tượng là thể hiện của một lớp. Có nhiều bạn nhầm lẫn xem class là đối tượng hoặc ngược lại – đây là nhầm lẫn rất tai hại. Như đã đề cập ở trên, class đại diện cho một tập hợp các đối tượng có chung những đặc điểm và hành vi. Khi bạn khởi tạo thể hiện cho một class thì thể hiện đó mới là đối tượng.
Dưới đây là một ví dụ về cách tạo đối tượng:
using System;
namespace BoxApplication
{
class Box
{
// properties
public double Length { get; set; }
public double Breadth { get; set; }
public double Height { get; set; }
// default constructor
public Box()
{}
// constructor with parameters
public Box(double length, double breadth, double height)
{
Length = length;
Breadth = breadth;
Height = height;
}
// methods
public void Print()
{
Console.WriteLine("Length = {0}, Breadth = {1}, Height = {2}", Length, Breadth, Height);
}
}
class Boxtester
{
static void Main(string[] args)
{
// box1 is an object
Box box1 = new Box(6.0, 7.0, 5.0);
box1.Print();
Console.ReadKey();
}
}
}
Bạn hãy để ý trong phương thức Main có đoạn code Box box = new Box(6.0, 7.0, 5.0);
thì box
chính là một đối tượng. Mỗi khi tạo một thể hiện của lớp ta có một đối tượng.
4 tính chất của lập trình hướng đối tượng
Lập trình hướng đối tượng có 4 tính chất quan trọng là:
- Tính đóng gói (Encapsulation).
- Tính kế thừa (Inheritance).
- Tính đa hình (Polymorphism).
- Tính trừu tượng (Abstraction).
4 tính chất này bạn sẽ gặp thường xuyên khi lập trình lẫn khi đi phỏng vấn. Do đó việc hiểu và nắm vững 4 tính chất này rất quan trọng.
Tính đóng gói (Encapsulation)
Tính đóng gói là khả năng che giấu thông tin của đối tượng với môi trường bên ngoài. Việc cho phép môi trường bên ngoài tác động lên các dữ liệu nội tại của đối tượng hoàn toàn tùy thuộc vào người viết mã.
Tính chất này giúp đảm bảo sự toàn vẹn và bảo mật của đối tượng. Tính chất này được thể hiện thông qua các từ khóa chỉ định truy cập như: public, private, protected, internal, protected internal.
Tính kế thừa (Inheritance)
Tính kế thừa là khả năng tạo một lớp mới dựa trên một lớp có sẵn. Lớp có sẵn là lớp cha (hoặc lớp cơ sở), lớp mới là lớp con (hoặc lớp dẫn xuất) và lớp con thừa kế các thành viên được định nghĩa ở lớp cha.
Dưới đây là ví dụ class Rectangle kế thừa class Shape:
using System;
namespace InheritanceApplication
{
class Shape
{
public int Width { get; set; }
public int Height { get; set; }
}
// Derived class
class Rectangle : Shape
{
public int GetArea()
{
return Width * Height;
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle rectangle = new Rectangle();
rectangle.Width = 5;
rectangle.Height = 7;
// Print the area of the object.
Console.WriteLine("Total area: {0}", rectangle.GetArea());
Console.ReadKey();
}
}
}
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Total area: 35
Tính đa hình (Polymorphism)
Tính đa hình là khả năng một đối tượng có thể thực hiện một tác vụ theo nhiều cách khác nhau. Đa hình gồm 2 loại là đa hình tĩnh và đa hình động.
Đa hình tĩnh (overloading)
Đa hình tĩnh còn gọi là đa hình tại thời điểm biên dịch (compile time). C# cung cấp hai kỹ thuật để thực hiện đa hình tĩnh là:
- Nạp chồng phương thức: là các phương thức có cùng tên nhưng khác nhau về số lượng và/hoặc kiểu dữ liệu của tham số truyền vào.
- Nạp chồng toán tử: Bạn có thể định nghĩa lại hoặc nạp chồng hầu hết các toán tử tích hợp sẵn có trong C#. Nhờ đó, bạn có thể sử dụng các toán tử với các kiểu do người dùng định nghĩa.
Dưới đây là ví dụ về đa hình tĩnh trong C#:
using System;
namespace BoxApplication
{
class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
// overload method
public void Resize(double sameSize)
{
Width = sameSize;
Height = sameSize;
}
public void Resize(int width, int height)
{
Width = width;
Height = height;
}
public void Resize(double width, double height)
{
Width = width;
Height = height;
}
// overload operator
public static Rectangle operator +(Rectangle a, Rectangle b)
{
return new Rectangle(a.Width + b.Width, a.Height + b.Height);
}
public static Rectangle operator -(Rectangle a, Rectangle b)
{
return new Rectangle(a.Width - b.Width, a.Height - b.Height);
}
// other methods
public double GetArea()
{
return Width * Height;
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle rectangle1 = new Rectangle(6.0, 7.0);
Console.WriteLine("Area = {0} x {1} = {2}", rectangle1.Width, rectangle1.Height, rectangle1.GetArea());
rectangle1.Resize(5.0);
Console.WriteLine("Area = {0} x {1} = {2}", rectangle1.Width, rectangle1.Height, rectangle1.GetArea());
Rectangle rectangle2 = new Rectangle(6.0, 4.0);
// use operator +
Rectangle rectangle3 = rectangle1 + rectangle2;
Console.WriteLine("Area = {0} x {1} = {2}", rectangle3.Width, rectangle3.Height, rectangle3.GetArea());
Console.ReadKey();
}
}
}
Kết quả khi biên dịch và chạy chương trình trên:
Area = 6 x 7 = 42
Area = 5 x 5 = 25
Area = 11 x 9 = 99
Đa hình động (overriding)
Đa hình động là đa hình lúc thực thi (runtime). C# cho phép bạn tạo các lớp trừu tượng (abstract class) được sử dụng để cung cấp triển khai lớp một phần của giao diện.
Việc thực hiện được hoàn thành khi một lớp dẫn xuất kế thừa từ nó. Các lớp trừu tượng chứa các phương thức trừu tượng (abstract method) sẽ được thực hiện bởi lớp dẫn xuất.
Dưới đây là ví dụ về đa hình động trong C#:
using System;
namespace PolymorphismApplication
{
abstract class Shape
{
public abstract int GetArea();
}
class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public Rectangle(int width, int height)
{
Width = width;
Height = height;
}
public override int GetArea()
{
return Width * Height;
}
}
class RectangleTester
{
static void Main(string[] args)
{
Rectangle rectangle = new Rectangle(10, 7);
Console.WriteLine("Area: {0}", rectangle.GetArea());
Console.ReadKey();
}
}
}
Kết quả khi biên dịch và chạy chương trình trên:
Area: 70
Tính trừu tượng (Abstraction)
Tính trừu tượng là khả năng ẩn chi tiết triển khai, chỉ cung cấp thông tin tính năng tới người dùng.
Tính trừu tượng được thể hiện qua abstract class và interface. Ví dụ ở phần đa hình động cũng có thể được sử dụng để làm ví dụ cho tính trưu tượng sử dụng abstract class và abstract method. Dưới đây là ví dụ về tính trừu tượng trong C# sử dụng interface:
using System;
namespace AbstractionApplication
{
interface IExportData
{
void Export();
}
class TextExport : IExportData
{
public void Export()
{
Console.WriteLine("Export data to .txt file");
}
}
class CsvExport : IExportData
{
public void Export()
{
Console.WriteLine("Export data to .csv file");
}
}
class Application
{
private readonly IExportData _exportData;
public Application(IExportData exportData)
{
_exportData = exportData;
}
public void ExportData()
{
_exportData.Export();
}
}
class RectangleTester
{
static void Main(string[] args)
{
Application app = new Application(new TextExport());
app.ExportData();
Console.ReadKey();
}
}
}
Kết quả khi biên dịch và chạy chương trình trên:
Export data to .txt file
Như các bạn thấy, lớp Application có chức năng ExportData, nó gọi phương thức Export của interface IExportData mà không cần quan tâm tới phương thức này làm gì.
Ở chương trình trên mình muốn xuất dữ liệu ra file txt nên khi khởi tạo thể hiện của class Application, mình chỉ cần truyền vào thể hiện của class TextExport là xong.
Nếu muốn xuất ra file csv thì mình chỉ cần thay thế bằng thể hiện của class CsvExport.
Lưu ý: ở ví dụ trên có áp dụng quy tắc “Dependency Inversion Principle” của SOLID.