Nếu bạn không thể giải thích một cách đơn giản, bạn chưa thực sự hiểu nó.

Khi bắt đầu sự nghiệp của mình với tư cách là một Junior Engineer, tôi được yêu cầu giải thích bốn nguyên tắc của OOP (Lập trình hướng đối tượng) tại hầu hết mọi công ty mà tôi đã tham gia phỏng vấn. Tôi đã từng rất lo lắng khi trả lời những câu hỏi này. Tôi lo lắng không phải vì tôi không biết trả lời, mà là do có quá nhiều cách trả lời cho câu hỏi này. Thứ tôi đang nói ở đây chính là Tính đóng gói, Tính trừu tượng, Tính kế thừaTính đa hình. Đôi khi, tôi tìm thấy chính mình đang tham gia vào những cuộc trò chuyện liên quan đến những nguyên tắc này và tôi nhận ra rằng, vẫn còn nhiều Developers có kinh nghiệm có thể hiểu các nguyên tắc nhưng không phải lúc nào cũng biết cách giải thích chúng cho các kỹ sư trẻ. Tôi sẽ cố gắng hết sức để giải thích cặn kẽ mà đơn giản về từng nguyên tắc.

 

Tính đóng gói (Encapsulation)

Ảnh thumbnail của bài viết

Đóng gói có nghĩa là chỉ cho phép các thay đổi đối với các thành viên private của lớp, bao gồm các thuộc tính và phương thức, thông qua các phương thức public. Không được phép truy cập trực tiếp vào các trường thuộc tính của lớp.

Hãy xem ví dụ code của class Student bên dưới:

public class Student {
    public string FirstName;
    public string LastName;
    public int Age;
    public string Id;
}

Lớp này vi phạm nguyên tắc đóng gói vì các thành viên thuộc tính của lớp có thể được truy cập trực tiếp từ các class khác thông qua việc khai báo public cho chúng. Việc áp dụng tính năng đóng gói cho lớp này rất dễ dàng chỉ bằng cách khai báo private cho các thuộc tính và tạo các getters, setters cho chúng.

public class Student {
    private string _firstName;
    private string _lastName;
    private int _age;
    private string _id;

    public string GetFirstName() {
        return this._firstName;
    }

    public string GetLastName() {
        return this._lastName;
    }

    public int GetAge() {
        return this._age;
    }

    public string GetID() {
        return this._id;
    }

    public void SetFirstname(string firstName) {
        this._firstName = firstName;
    }

    public void SetLastName(string lastName) {
        this._lastName = lastName;
    }

    public void SetAge(int age) {
        this._age = age;
    }

    public void SetID(string id) {
        this._id = id;
    }
}

Tính đóng gói cho phép bạn với tư cách là Developer, kiểm soát cách các thành viên trong lớp được sử dụng. Bạn thậm chí còn có thể xác thực cho từng thành viên thuộc tính của lớp.

 

Tính kế thừa (Inheritance)

Ảnh thumbnail của bài viết

Giả sử bạn đang làm việc trên một ứng dụng liên quan đến xe cộ. Nó sẽ có bốn loại phương tiện: Xe tải, Xe buýt, SUV và Xe máy. Tất cả các loại xe sẽ có cấu tạo, kiểu xe, cách trang trí và năm sản xuất. Sau đó, sẽ có các thuộc tính bổ sung cho từng loại phương tiện. Bạn có thể khai báo mỗi lớp với các thuộc tính giống nhau, nhưng cách đó lại là tiêu chuẩn của viết code xấu. Bất cứ khi nào bạn có các thuộc tính chung trong nhiều lớp, bạn cần tạo một lớp cơ sở (cha) và để các lớp khác (con) kế thừa từ lớp cơ sở đó. Nói cách khác, kế thừa tương tự như việc trẻ sơ sinh nhận được tất cả các đặc điểm của cha mẹ nhưng vẫn có tính cách riêng của chúng.

Dưới đây là cách triển khai kịch bản đã thảo luận ở trên (ứng dụng xe cộ) với tính kế thừa:

//LỚP CHA (a.k.a Base Class)
public class Vehicle
{
    public String make { get; set; }
    public String model { get; set; }
    public String trim { get; set; }
    public String year { get; set;   }
}
//LỚP CON (a.k.a Derived/ Subclass)
public class Truck extends Vehicle
{
    //các thuộc tính và phương thức của Truck
}
public class SUV extends Vehicle
{
    //các thuộc tính và phương thức của SUV
}
public class Bus extends Vehicle
{
   //các thuộc tính và phương thức của Bus
}
public class Motorcycle extends Vehicle
{
   //các thuộc tính và phương thức của Motorcycle
}

 

Tính trừu tượng (Abstraction)

Ảnh thumbnail của bài viết

Trừu tượng có nghĩa là chỉ hiển thị những thứ cần thiết cho người dùng cuối cùng, tức là bất kỳ người nào sẽ sử dụng lớp của bạn. Ví dụ: bạn không cần biết động cơ hoạt động như thế nào để lái ô tô. Tất cả những gì bạn cần biết là cách khởi động xe, tăng tốc, phanh và dừng (cũng cần có ý thức định hướng tốt 😃). Tuy nhiên, bên dưới mui xe ô tô có động cơ, máy bơm nước, bộ tản nhiệt,… hơn nữa mỗi bộ phận này được cấu tạo từ bộ phận khác cũng là bộ phận khác. Một động cơ có xi lanh, bugi, vv… Vì vậy, trừu tượng là khi lớp Car của chúng tôi chỉ đưa ra bốn phương thức (khởi động, tăng tốc, phanh, dừng) và giữ cho các chi tiết phức tạp khác ẩn khỏi người lái.

//LỚP CAR
public class Car {
    private Engine _engine;
    private ElectricalSystem electricalSystem;

    public Car() {
        this._engine = new Engine();
        this.electricalSystem = new ElectricalSystem();
    }

    public void Start() =>this._engine.Start();

    public void Accelerate() =>this._engine.Accelerate();

    public void Brake() =>this._engine.Brake();

    public void Stop() =>this._engine.Stop();
}

//LOGIC TRỪU TƯỢNG
public class Engine {
    public void Start() {
    } //khởi động xe...

    public void Stop() {
    } //dừng xe...

    public void Accelerate() {
    }//tăng tốc...

    public void Brake() {
    } //phanh...
}

public class ElectricalSystem {
    //hệ thống radio
    //hệ thống định hướng
    //hệ thóng cửa sổ trời
    //vân vân...
}

Nếu bạn là một Developer khác đang sử dụng lớp Car, động cơ và tất cả các hoạt động tương tác của nó sẽ bị trừu tượng hóa đối với bạn, giống như một số người lái xe mà không biết gì về cách hoạt động của một chiếc xe.

 

Tính đa hình (Polymorphism)

 

Từ Polymorphism có nghĩa là có nhiều dạng. Nhưng trong ngữ cảnh này, nó đề cập đến khả năng của một giao diện (Interface) hoặc một lớp trừu tượng (Abstract class) có nhiều dạng thông qua các lớp dẫn xuất được triển khai của nó. Điều đó đơn giản có nghĩa là, đối với giao diện Cat (Mèo) hoặc lớp trừu tượng (Abstract class), bạn có thể có nhiều lớp dẫn xuất như Lion, Cheetah, Tiger, Jaguar, v.v.… (các động vật thuộc họ mèo). Giao diện Cat sẽ có một phương thức được gọi là tiếng hét (Scream()) và phương thức đó sẽ in ra tiếng hét dành riêng cho mỗi một loại trong những con vật thuộc họ mèo này. Dù đó chỉ là một giao diện, chúng ta có thể lấy được vô số lớp với hành vi riêng biệt, miễn là chúng triển khai giao diện.

Hãy xem xét định nghĩa các lớp Cat này:

public interface ICat {
    void Scream();
}

public class Lion implements ICat {
    public void Scream() {
        System.out.println("Lions go ROARRRRR!!!");
    }
}

public class Jaguar implements ICat {
    public void Scream() {
        System.out.println("Jaguars go MEWWWWWW!!!");
    }
}

public class Cheetah implements ICat {
    public void Scream() {
        System.out.println("Cheetahs go PURRRRR!!!");
    }
}

 

Sử dụng giao diện (Interface) ICat, chúng ta có thể nhận biết được tiếng hét của các loại mèo khác nhau bằng cách gọi phương thức Scream().

var cats = new List<ICat>();
            cats.Add(new Lion());
            cats.Add(new Cheetah());
            cats.Add(new Jaguar());
cats.ForEach(cat =>
            {
                cat.Scream();
            });
//Lions go ROARRRRR!!!
//Cheetahs PURRRRR!!!
//Jaguars MEWWWWWW!!!

 

Chúng ta có thể nhận được ba tiếng hét khác nhau với cùng một giao diện (Interface) Cat và càng nhiều lớp triển khai giao diện, chúng ta sẽ có nhiều dạng mèo hơn. Hành vi tương tự có thể đạt được với một lớp trừu tượng và tất cả các lớp dẫn xuất sẽ ghi đè phương thức Scream() của lớp trừu tượng. Tóm lại, đó chính là khái niệm đa hình.

Tôi hy vọng điều này hữu ích cho các bạn và cảm ơn các bạn đã theo đến cùng. Chúc các bạn code vui vẻ và hẹn gặp lại vào lần sau.

Nguồn bài viết: https://medium.com/swlh/oop-four-principles-explained-in-five-minutes-8316ea348467