Header Ads

[OOP] Một số vấn đề cần nhớ về OOP


Nguồn: Click here

1. Lập trình hướng đối tượng (OOP) là gì?

Lập trình hướng đối tượng là một phương pháp lập trình phổ biến, nó tập trung vào việc tạo ra các đối tượng để giải quyết các vấn đề phức tạp. Dưới đây là một số điều cần chú ý khi làm việc với OOP:

  • Tạo các lớp và đối tượng: Trong OOP, lớp là một bản thiết kế cho các đối tượng có thể được tạo ra, nó bao gồm các thuộc tínhphương thức của đối tượng. Để tạo ra một đối tượng, chúng ta cần khởi tạo một thể hiện (instance) của lớp đó. Ví dụ: Student s = new Student(1, "NVA",...);
  • Tính trừu tượng: Tính trừu tượng là tính năng cho phép bạn tạo ra các lớp và phương thức mà không cần biết các chi tiết cụ thể về chúng. Điều này cho phép chúng ta tạo ra các chức năng phức tạp mà không cần quan tâm đến cách chúng được thực hiện.
  • Tính kế thừa: Kế thừa là tính năng cho phép chúng ta tạo ra các lớp mới bằng cách sử dụng các lớp hiện có. Lớp mới này sẽ kế thừa tất cả các thuộc tính và phương thức có thể truy cập của lớp cha (nghĩa là những phương thức không để private). Tính kế thừa cho phép chúng ta tái sử dụng mã và giảm thiểu việc lặp lại mã.
  • Tính đa hình: Đa hình là tính năng cho phép các đối tượng cùng loại có thể có các hành vi khác nhau. Nó cho phép các phương thức của lớp cha được ghi đè bởi các phương thức của lớp con, để thực hiện các hành vi khác nhau.
  • Tính đóng gói: Tính đóng gói là tính năng cho phép chúng ta bảo vệ các thuộc tính và phương thức của đối tượng khỏi sự can thiệp từ bên ngoài. Nó cho phép chúng ta đảm bảo tính toàn vẹn dữ liệu và hạn chế các lỗi không đáng có.

2. Tính trừu tượng (abstraction) 

Tính trừu tượng (abstraction) là khả năng tách biệt giữa các khía cạnh của đối tượng để tập trung vào các tính năng quan trọng và ẩn đi các chi tiết phức tạp.

Ví dụ: Giả sử bạn đang viết một ứng dụng quản lý nhân viên, trong đó có các nhân viên full-time và part-time. Bạn muốn tạo ra một lớp trừu tượng là Employee để đại diện cho các nhân viên, bao gồm các thuộc tính chung như tên, địa chỉ, số điện thoạithu nhập. Tuy nhiên, bạn không thể tạo đối tượng Employee trực tiếp, mà chỉ có thể tạo đối tượng từ các lớp con của Employee.

Bạn sẽ tạo ra lớp FullTimeEmployeePartTimeEmployee để kế thừa từ Employee, và định nghĩa lại phương thức tính lương dựa trên loại nhân viên. Tuy nhiên, giá trị lương sẽ được tính toán bằng cách gọi phương thức tính lương trong các lớp con, chứ không phải trong lớp Employee.

Ví dụ mã Java sau đây mô tả cách triển khai tính trừu tượng trong trường hợp này:

// lớp trừu tượng Employee

public abstract class Employee {
    protected String name;
    protected String address;
    protected String phone;
    protected double salary;

    // phương thức tính lương trừu tượng
    public abstract double calculateSalary();

    // các getter/setter cho các thuộc tính
}

// lớp FullTimeEmployee kế thừa từ Employee

public class FullTimeEmployee extends Employee {
    private double baseSalary;

    // constructor, getter/setter cho baseSalary
    // phương thức tính lương cho FullTimeEmployee
    public double calculateSalary() {
        // tính lương cho FullTimeEmployee
    }
}

// lớp PartTimeEmployee kế thừa từ Employee

public class PartTimeEmployee extends Employee {
    private double hourlyRate;
    private double hoursWorked;

    // constructor, getter/setter cho hourlyRate và hoursWorked
    // phương thức tính lương cho PartTimeEmployee
    public double calculateSalary() {
        // tính lương cho PartTimeEmployee
    }
}
Trong ví dụ này, lớp Employee là lớp trừu tượng vì nó không thể tạo đối tượng trực tiếp, mà chỉ có thể tạo đối tượng từ các lớp con. Các thuộc tính của Employee cũng không có định nghĩa cụ thể về giá trị, mà chỉ được khai báo và định nghĩa lại trong các lớp con.

3. Tính kế thừa (inheritance) 

Tính kế thừa là khả năng cho phép lớp mới được tạo ra dựa trên một hoặc nhiều lớp đã có sẵn. Lớp con có thể sử dụng các thuộc tính và phương thức đã được định nghĩa trong lớp cha của nó.

Ví dụ: Giả sử bạn đang xây dựng một chương trình quản lý động vật trong một sở thú. Bạn sẽ tạo một lớp Animal để đại diện cho các động vật, với các thuộc tính như tên, tuổi và mô tả. Sau đó, bạn sẽ tạo ra các lớp con để đại diện cho các loài động vật khác nhau.

// lớp Animal cơ bản

public class Animal {
    protected String name;
    protected int age;
    protected String description;

    // constructor, getter/setter cho các thuộc tính
    public Animal(String name, int age, String description) {
        this.name = name;
        this.age = age;
        this.description = description;
    }

    // phương thức phát ra tiếng kêu
    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}

// lớp con Dog kế thừa từ Animal

public class Dog extends Animal {
    private String breed;

    // constructor, getter/setter cho breed
    public Dog(String name, int age, String description, String breed) {
        super(name, age, description);
        this.breed = breed;
    }

    // phương thức phát ra tiếng sủa
    public void makeSound() {
        System.out.println("The dog barks.");
    }
}

// lớp con Cat kế thừa từ Animal

public class Cat extends Animal {
    private boolean isIndoor;

    // constructor, getter/setter cho isIndoor
    public Cat(String name, int age, String description, boolean isIndoor) {
        super(name, age, description);
        this.isIndoor = isIndoor;
    }

    // phương thức phát ra tiếng kêu
    public void makeSound() {
        System.out.println("The cat meows.");
    }
}
Trong ví dụ này, lớp Animal là lớp cha, cung cấp các thuộc tính chung và phương thức phát ra tiếng kêu. Các lớp con Dog và Cat được tạo ra bằng cách kế thừa từ lớp Animal, và định nghĩa lại phương thức phát ra tiếng kêu của riêng chúng. Lớp Dog và lớp Cat cũng có thêm các thuộc tính riêng của chúng, như là giống chó (breed) và có ở trong nhà hay ngoài trời

4. Tính đóng gói (encapsulation)

Trong lập trình hướng đối tượng, tính đóng gói (encapsulation) là khái niệm quan trọng để bảo vệ dữ liệu và tránh xảy ra lỗi không đáng có. Tính đóng gói cho phép các thuộc tính của một đối tượng chỉ có thể được truy cập thông qua các phương thức được cung cấp bởi lớp đó, và không thể truy cập trực tiếp từ bên ngoài lớp. Ví dụ về tính đóng gói:

public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        if (age >= 18 && age <= 60) {
            this.age = age;
        } else {
            System.out.println("Invalid age.");
        }
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        if (salary >= 0) {
            this.salary = salary;
        } else {
            System.out.println("Invalid salary.");
        }
    }
}
Trong ví dụ này, lớp Employee với các thuộc tính name, age và salary, và các thuộc tính này được khai báo là private, nghĩa là chúng chỉ có thể được truy cập bởi các phương thức của lớp Employee và KHÔNG THỂ truy cập từ bên ngoài.

Để truy cập các thuộc tính này từ bên ngoài lớp Employee, chúng ta phải sử dụng các phương thức gettersetter được cung cấp (lớp Employee có thể không cung cấp getter setter nếu muốn). Phương thức getName() cho phép truy cập thuộc tính name, và phương thức setName() cho phép thay đổi giá trị của thuộc tính name.

Tương tự, phương thức getAge() và setAge() được sử dụng để truy cập và thay đổi giá trị của thuộc tính age, và phương thức getSalary() và setSalary() được sử dụng để truy cập và thay đổi giá trị của thuộc tính salary. Các phương thức setter này cũng được sử dụng để kiểm tra tính hợp lệ của giá trị được gán cho các thuộc tính. Ví dụ, phương thức setAge() sẽ kiểm tra xem giá trị mới được gán cho thuộc tính age có nằm trong khoảng từ 18 đến 60 không, và chỉ cho phép gán giá trị hợp lệ. 

5. Tính đa hình (polymorphism)

Tính đa hình (polymorphism) là tính năng cho phép đối tượng có thể hiểu theo nhiều cách khác nhau tùy vào ngữ cảnh sử dụng. 

Ví dụ: Giả sử bạn đang viết một ứng dụng về hình học, trong đó có các đối tượng hình học khác nhau như hình tròn, hình vuông và hình chữ nhật. Bạn có thể tạo ra các lớp:

  • Lớp cha là Shape để đại diện cho các hình học, bao gồm phương thức tính diện tích và chu vi.
  • Lớp con Circle sẽ kế thừa từ Shape và có thêm thuộc tính bán kính, cũng như định nghĩa lại phương thức tính diện tích và chu vi dựa trên bán kính.
  • Lớp con Square và Rectangle cũng sẽ kế thừa từ Shape và có thêm thuộc tính chiều dài và chiều rộng tương ứng, cũng như định nghĩa lại phương thức tính diện tích và chu vi dựa trên chiều dài và chiều rộng.

Với tính đa hình, bạn có thể khai báo một biến đối tượng Shape và gán nó bằng một đối tượng của lớp con như Circle, Square hoặc Rectangle. Sau đó, bạn có thể gọi các phương thức tính diện tích và chu vi trên biến đối tượng Shape mà không cần biết chính xác đối tượng được gán cho biến đó là gì. Ví dụ:

Shape shape = new Circle(5.0); //shape là Circle

double area = shape.calculateArea(); // tính diện tích hình tròn bán kính 5.0

shape = new Square(4.0); //shape bây giờ lại là Square

area = shape.calculateArea() // gọi tính diện tích của hình vuông với cạnh là 4.0

shape = new Rectangle(3.0, 6.0); //shape bây giờ lại là Rectangle

area = shape1.calculateArea(); // tính diện tích hình hình chữ nhật có chiều dài 3.0 và chiều rộng 6.0

Với tính đa hình, bạn có thể xử lý các đối tượng của các lớp con khác nhau như một đối tượng duy nhất

Các bạn có thể làm bài tập tại link này: Bài tập OOP

Không có nhận xét nào

Được tạo bởi Blogger.