kiến thức Những ghi chép của tôi về lập trình hướng đối tượng

cba

Senior Member
Cho dù người ta có nói ngược nói xuôi, chê bôi OOP thì tôi vẫn trung thành với nó và ngày càng yêu thích mô hình lập trình này.

Sở dĩ tôi thích vì cũng code qua Hướng mệnh lệnh - Imperative programming, Hướng thủ tục - Procedural programming, Hướng chức năng - Functional programming, Hướng khai báo - Declarative programming, Sau tất cả tôi quay lại OOP. Lúc mới tìm hiểu về lập trình, chắc tôi cũng giống với nhiều bạn. Hóng thấy cái mô hình mình lập trình bị chê bôi thì lung lay và tìm tới các mô hình khác tinh vi hơn. Nhưng rồi khi va vào vấn đề thì với mô hình lập trình mà mình cho là tinh vi ấy nó giải quyết có vẻ phức tạp, rối rắm thì mới chợt nhận ra là OOP nó giải quyết gọn gàng thật sự.

Thời gian đầu khi viết code OOP tôi thường bị cứng nhắc phải thể hiện sao cho đầy đủ mấy đặc tính của OOP. 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). Chính vì cứ bám lấy cái khuôn phép do mình tự đặt ra này mà bản thân thấy OOP thực sự gò bó. Một lần đọc được lời tâm sự của chính tác giả của mô hình lập trình OOP, mr.Alan Kay một lần nữa thấy thấm thía.

Alan Kay cho rằng các đối tượng trong một hệ thống phần mềm nên giao tiếp với nhau thông qua các thông điệp (messages) thay vì trực tiếp tương tác với nhau. Việc này giúp tách biệt các đối tượng ra khỏi nhau, tăng tính module hóa và đơn giản hóa việc quản lý mã nguồn. Nó cũng giúp cải thiện tính linh hoạt của hệ thống và khả năng mở rộng của nó. Alan Kay cũng nhấn mạnh rằng việc thiết kế giao tiếp giữa các đối tượng là rất quan trọng trong việc xây dựng các hệ thống phần mềm lớn và phức tạp.

Kay đã giải thích rằng lập trình hướng đối tượng cung cấp một số lợi ích quan trọng. Đầu tiên, nó cho phép các lập trình viên chia nhỏ hệ thống phức tạp thành các đối tượng đơn giản hơn, dễ quản lý hơn và tái sử dụng hơn. Thứ hai, nó giúp tăng tính linh hoạt và mở rộng của hệ thống bằng cách cho phép các đối tượng tương tác với nhau một cách tổng hợp hơn.

Từ đây, tôi bắt đầu suy ngẫm về case thực tế:

Một hệ thống, có các đối tượng chính như Khách hàng, Sản phẩm, Giỏ hàng và Đơn hàng. Làm thế nào vận hành nó theo một quy trình? OOP hướng ngay đến việc ta phải mô hình hóa được quy trình vận hành của các hành vi. Từ đấy mới tiến hành viết code.

Quy trình vận hành của các hành vi có thể được mô tả như sau:

Khi khách hàng truy cập vào trang web mua sắm, họ có thể thực hiện các hành động như xem danh sách sản phẩm, thêm sản phẩm vào giỏ hàng và đặt hàng. Những hành động này tương tác giữa các đối tượng trong hệ thống:

1. Khách hàng tương tác với danh sách sản phẩm để xem thông tin sản phẩm.
2. Khách hàng tương tác với giỏ hàng để thêm và xóa sản phẩm.
3. Khách hàng tương tác với đơn hàng để xem trạng thái đơn hàng.
4. Để hỗ trợ các tương tác này, các đối tượng phải giao tiếp với nhau và chia sẻ thông tin. Ví dụ, khi khách hàng thêm sản phẩm vào giỏ hàng, sản phẩm phải được thêm vào giỏ hàng và cập nhật số lượng sản phẩm còn lại trong kho. Các đối tượng phải giao tiếp để cập nhật thông tin sản phẩm và giỏ hàng.

Trong lập trình hướng đối tượng, các đối tượng tương tác với nhau thông qua các phương thức và thuộc tính của chúng. Ví dụ, để thêm sản phẩm vào giỏ hàng, đối tượng Giỏ hàng có thể có phương thức thêm sản phẩm, trong đó sản phẩm được truyền vào như một tham số. Đối tượng Sản phẩm có thể có thuộc tính số lượng còn lại, được cập nhật sau khi sản phẩm được thêm vào giỏ hàng.

Các đối tượng trong hệ thống mua bán trực tuyến phải có khả năng tương tác và giao tiếp với nhau để cung cấp các chức năng và tính năng cho khách hàng.

Để viết một đoạn code mô tả quy trình trên thì dài quá. Tôi sẽ lấy ví dụ với nghiệp vụ ngắn hơn đó là quy trình quản lý đăng nhập:

Giả sử chúng ta có hai đối tượng là User AuthenticationService.

User đại diện cho người dùng và chứa thông tin về tên đăng nhập và mật khẩu của người dùng.

PHP:
class User
{
    private $username;
    private $password;

    public function __construct(string $username, string $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getPassword(): string
    {
        return $this->password;
    }
}

AuthenticationService đại diện cho dịch vụ xác thực, chứa phương thức authenticate() để xác thực tên đăng nhập và mật khẩu của người dùng.

PHP:
class AuthenticationService
{
    public function authenticate(User $user): bool
    {
        // Kiểm tra tên đăng nhập và mật khẩu của người dùng trong cơ sở dữ liệu
        // Nếu đúng, trả về true. Nếu sai, trả về false.
    }
}

Sử dụng các đối tượng trên, chúng ta có thể tạo ra một đoạn mã để kiểm tra tên đăng nhập và mật khẩu của người dùng.

PHP:
$username = 'john';
$password = 'secret';

$user = new User($username, $password);
$authenticationService = new AuthenticationService();

if ($authenticationService->authenticate($user)) {
    echo 'Đăng nhập thành công!';
} else {
    echo 'Đăng nhập thất bại!';
}


Trong đoạn mã trên, đối tượng User được tạo ra với tên đăng nhập và mật khẩu của người dùng. Đối tượng AuthenticationService được sử dụng để kiểm tra tên đăng nhập và mật khẩu của người dùng. Các đối tượng này giao tiếp với nhau thông qua phương thức authenticate() của AuthenticationService.

Ok, Nếu voz thấy hay cho xin 1 like để tôi lấy động lực viết tieetsp :)
 
Cho dù người ta có nói ngược nói xuôi, chê bôi OOP thì tôi vẫn trung thành với nó và ngày càng yêu thích mô hình lập trình này.

Sở dĩ tôi thích vì cũng code qua Hướng mệnh lệnh - Imperative programming, Hướng thủ tục - Procedural programming, Hướng chức năng - Functional programming, Hướng khai báo - Declarative programming, Sau tất cả tôi quay lại OOP. Lúc mới tìm hiểu về lập trình, chắc tôi cũng giống với nhiều bạn. Hóng thấy cái mô hình mình lập trình bị chê bôi thì lung lay và tìm tới các mô hình khác tinh vi hơn. Nhưng rồi khi va vào vấn đề thì với mô hình lập trình mà mình cho là tinh vi ấy nó giải quyết có vẻ phức tạp, rối rắm thì mới chợt nhận ra là OOP nó giải quyết gọn gàng thật sự.

Thời gian đầu khi viết code OOP tôi thường bị cứng nhắc phải thể hiện sao cho đầy đủ mấy đặc tính của OOP. 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). Chính vì cứ bám lấy cái khuôn phép do mình tự đặt ra này mà bản thân thấy OOP thực sự gò bó. Một lần đọc được lời tâm sự của chính tác giả của mô hình lập trình OOP, mr.Alan Kay một lần nữa thấy thấm thía.

Alan Kay cho rằng các đối tượng trong một hệ thống phần mềm nên giao tiếp với nhau thông qua các thông điệp (messages) thay vì trực tiếp tương tác với nhau. Việc này giúp tách biệt các đối tượng ra khỏi nhau, tăng tính module hóa và đơn giản hóa việc quản lý mã nguồn. Nó cũng giúp cải thiện tính linh hoạt của hệ thống và khả năng mở rộng của nó. Alan Kay cũng nhấn mạnh rằng việc thiết kế giao tiếp giữa các đối tượng là rất quan trọng trong việc xây dựng các hệ thống phần mềm lớn và phức tạp.

Kay đã giải thích rằng lập trình hướng đối tượng cung cấp một số lợi ích quan trọng. Đầu tiên, nó cho phép các lập trình viên chia nhỏ hệ thống phức tạp thành các đối tượng đơn giản hơn, dễ quản lý hơn và tái sử dụng hơn. Thứ hai, nó giúp tăng tính linh hoạt và mở rộng của hệ thống bằng cách cho phép các đối tượng tương tác với nhau một cách tổng hợp hơn.

Từ đây, tôi bắt đầu suy ngẫm về case thực tế:

Một hệ thống, có các đối tượng chính như Khách hàng, Sản phẩm, Giỏ hàng và Đơn hàng. Làm thế nào vận hành nó theo một quy trình? OOP hướng ngay đến việc ta phải mô hình hóa được quy trình vận hành của các hành vi. Từ đấy mới tiến hành viết code.

Quy trình vận hành của các hành vi có thể được mô tả như sau:

Khi khách hàng truy cập vào trang web mua sắm, họ có thể thực hiện các hành động như xem danh sách sản phẩm, thêm sản phẩm vào giỏ hàng và đặt hàng. Những hành động này tương tác giữa các đối tượng trong hệ thống:

1. Khách hàng tương tác với danh sách sản phẩm để xem thông tin sản phẩm.
2. Khách hàng tương tác với giỏ hàng để thêm và xóa sản phẩm.
3. Khách hàng tương tác với đơn hàng để xem trạng thái đơn hàng.
4. Để hỗ trợ các tương tác này, các đối tượng phải giao tiếp với nhau và chia sẻ thông tin. Ví dụ, khi khách hàng thêm sản phẩm vào giỏ hàng, sản phẩm phải được thêm vào giỏ hàng và cập nhật số lượng sản phẩm còn lại trong kho. Các đối tượng phải giao tiếp để cập nhật thông tin sản phẩm và giỏ hàng.

Trong lập trình hướng đối tượng, các đối tượng tương tác với nhau thông qua các phương thức và thuộc tính của chúng. Ví dụ, để thêm sản phẩm vào giỏ hàng, đối tượng Giỏ hàng có thể có phương thức thêm sản phẩm, trong đó sản phẩm được truyền vào như một tham số. Đối tượng Sản phẩm có thể có thuộc tính số lượng còn lại, được cập nhật sau khi sản phẩm được thêm vào giỏ hàng.

Các đối tượng trong hệ thống mua bán trực tuyến phải có khả năng tương tác và giao tiếp với nhau để cung cấp các chức năng và tính năng cho khách hàng.

Để viết một đoạn code mô tả quy trình trên thì dài quá. Tôi sẽ lấy ví dụ với nghiệp vụ ngắn hơn đó là quy trình quản lý đăng nhập:

Giả sử chúng ta có hai đối tượng là User AuthenticationService.

User đại diện cho người dùng và chứa thông tin về tên đăng nhập và mật khẩu của người dùng.

PHP:
class User
{
    private $username;
    private $password;

    public function __construct(string $username, string $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getPassword(): string
    {
        return $this->password;
    }
}

AuthenticationService đại diện cho dịch vụ xác thực, chứa phương thức authenticate() để xác thực tên đăng nhập và mật khẩu của người dùng.

PHP:
class AuthenticationService
{
    public function authenticate(User $user): bool
    {
        // Kiểm tra tên đăng nhập và mật khẩu của người dùng trong cơ sở dữ liệu
        // Nếu đúng, trả về true. Nếu sai, trả về false.
    }
}

Sử dụng các đối tượng trên, chúng ta có thể tạo ra một đoạn mã để kiểm tra tên đăng nhập và mật khẩu của người dùng.

PHP:
$username = 'john';
$password = 'secret';

$user = new User($username, $password);
$authenticationService = new AuthenticationService();

if ($authenticationService->authenticate($user)) {
    echo 'Đăng nhập thành công!';
} else {
    echo 'Đăng nhập thất bại!';
}


Trong đoạn mã trên, đối tượng User được tạo ra với tên đăng nhập và mật khẩu của người dùng. Đối tượng AuthenticationService được sử dụng để kiểm tra tên đăng nhập và mật khẩu của người dùng. Các đối tượng này giao tiếp với nhau thông qua phương thức authenticate() của AuthenticationService.

Ok, Nếu voz thấy hay cho xin 1 like để tôi lấy động lực viết tieetsp :)

Cái bạn nói không thể hiện được cái khó khăn của OOP đâu. Ví dụ phải như 1 product được code OOP ngon lành rổi, nhưng gặp 1 feature mới mà code kiểu gì cũng break OOP, phải refactor lại từ tầng core bên dưới thì nó mới đẹp đúng OOP. Mà mấy cái này thì không nói trong 1, 2 câu được.
 
Cái bạn nói không thể hiện được cái khó khăn của OOP đâu. Ví dụ phải như 1 product được code OOP ngon lành rổi, nhưng gặp 1 feature mới mà code kiểu gì cũng break OOP, phải refactor lại từ tầng core bên dưới thì nó mới đẹp đúng OOP. Mà mấy cái này thì không nói trong 1, 2 câu được.
Mô hình nào cũng có cả tá khó khăn của nó. Thực tế từ bản thân mình gặp cũng nhiều, kể chẳng hết nên note vài cái hay gặp thôi. Tuy nhiên những khó khăn của OOP được giải quyết qua các design pattern. Ví dụ laravel giải quyết sự phụ thuộc qua service container...
 
Vài ghi chép về Tính kế thừa (Inheritance).

Tôi có tham gia vào các dự án thực tế, review code các dự án lớn trên github thì quan sát của tôi cho thấy. Hệ thống càng lớn, người ta càng tránh kế thừa. Bởi vì nó là nguyên nhân gây ra rò rỉ bộ nhớ cũng như gây bug và kìm hãm sự mở rộng của module bởi vì các lớp kế thừa nhau làm phức tạp trong hệ thống. Tóm lại, nên hạn chế kế thừa là tốt nhất. Nhưng cũng không nên loại bỏ tính kế thừa ra khỏi kiến trúc code. Vì thực tế kế thừa nó vẫn mang một ý nghĩa rất tối ưu là nó cho phép một class mới có thể sử dụng các thuộc tính và phương thức của class cha mà không cần phải định nghĩa lại chúng. Vấn đề là chúng ta khi nào thì nên kế thừa? Nó phải luôn đảm bảo 3 điều kiện:

1. Áp dụng kế thừa (inheritance) cho các thành phần phần mềm (software components) có sự tương đồng về chức năng và tính năng.

2. Chỉ nên kế thừa đến cấp 2. Nghĩa là lớp B kế thừa lớp A là dừng lại, không nên để lớp C kế thừa lớp B.

3. Lớp cha chỉ nên chứa thuộc tính/ phương thức chung chung cho toàn hệ thống.

Khi lớp con kế thừa lớp cha, khởi tạo lớp con thì lớp cha không khởi tạo đối tượng như nhiều người nghĩ. Mà thay vào đấy các vùng chứa dữ liệu định sẵn của lớp cha sẽ đẩy xuống lớp con khi lớp con được khởi tạo.

Điều này rất cần ghi nhớ để tránh khai báo một biến nào đấy chứa dữ liệu lớn tại lớp cha.

Chú ý rằng dù là lớp cha hay lớp con cũng không nên chứa các biến chứa dữ liệu lớn.

Giải pháp là sử dụng Composition, tôi sẽ giải thích về Composition như sau:

Composition là một kỹ thuật trong lập trình hướng đối tượng, nó cho phép ta xây dựng một đối tượng lớn bằng cách sử dụng các đối tượng nhỏ hơn. Ví dụ sau đây mô tả một ví dụ về composition trong PHP:

Giả sử chúng ta đang xây dựng một ứng dụng quản lý đơn hàng. Trong đó, mỗi đơn hàng sẽ có một số chi tiết về sản phẩm được đặt hàng, chẳng hạn như tên sản phẩm, giá, số lượng, v.v. Để biểu diễn các chi tiết sản phẩm này, chúng ta có thể tạo một lớp Product như sau:

PHP:
class Product
{
    private $name;
    private $price;
    private $quantity;

    public function __construct($name, $price, $quantity)
    {
        $this->name = $name;
        $this->price = $price;
        $this->quantity = $quantity;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }

    public function getQuantity()
    {
        return $this->quantity;
    }
}

Tiếp theo, chúng ta có thể tạo một lớp Order, đại diện cho một đơn hàng. Lớp Order sẽ bao gồm một danh sách các sản phẩm được đặt hàng, và các phương thức để thêm sản phẩm vào đơn hàng, tính tổng số tiền, v.v. Tuy nhiên, thay vì lưu trữ các chi tiết sản phẩm trong lớp Order trực tiếp, chúng ta sẽ sử dụng composition để lưu trữ các sản phẩm trong một đối tượng khác. Ví dụ:

PHP:
class Order
{
    private $products = [];

    public function addProduct(Product $product)
    {
        $this->products[] = $product;
    }

    public function getTotal()
    {
        $total = 0;
        foreach ($this->products as $product) {
            $total += $product->getPrice() * $product->getQuantity();
        }
        return $total;
    }
}

Như vậy, lớp Order không trực tiếp lưu trữ các chi tiết sản phẩm, mà thay vào đó lưu trữ các đối tượng Product. Khi tính tổng số tiền cho một đơn hàng, lớp Order sử dụng composition để truy xuất các giá trị của các đối tượng Product.

Chạy thử code:

PHP:
// Tạo các đối tượng sản phẩm
$product1 = new Product(10, 2);
$product2 = new Product(5, 3);

// Tạo đơn hàng và thêm sản phẩm vào đơn hàng
$order = new Order();
$order->addProduct($product1);
$order->addProduct($product2);

// Tính tổng giá trị của đơn hàng
$total = $order->getTotal();
echo 'Total: ' . $total;

Ouput: Total: 35

Thêm một ví dụ

Giả sử bạn đang xây dựng một ứng dụng hỗ trợ tìm đường đi từ điểm A đến điểm B trên một bản đồ.

Bạn đã tạo ra một class đại diện cho một điểm trên bản đồ, class này có các thuộc tính là tọa độ X và Y của điểm đó. Ngoài ra, bạn cần một class đại diện cho bản đồ, class này bao gồm danh sách các điểm và phương thức để tính toán đường đi từ một điểm đến một điểm khác trên bản đồ.

Ví dụ code:

PHP:
class Point
{
    private $x;
    private $y;

    public function __construct($x, $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX()
    {
        return $this->x;
    }

    public function getY()
    {
        return $this->y;
    }
}

class Map
{
    private $points = [];

    public function addPoint(Point $point)
    {
        $this->points[] = $point;
    }

    public function findShortestPath(Point $start, Point $end)
    {
        // Tính toán đường đi từ $start đến $end trên bản đồ và trả về đối tượng đường đi
        // ...
    }
}

// Tạo đối tượng bản đồ
$map = new Map();

// Thêm các điểm vào bản đồ
$pointA = new Point(10, 20);
$map->addPoint($pointA);

$pointB = new Point(30, 40);
$map->addPoint($pointB);

// Tìm đường đi từ A đến B
$path = $map->findShortestPath($pointA, $pointB);

Giải thích: class Map sử dụng class Point làm thành phần của nó bằng cách lưu trữ danh sách các điểm trên bản đồ trong mảng $points. Khi tính toán đường đi từ một điểm đến một điểm khác, class Map sử dụng các phương thức của class Point để truy xuất tọa độ của các điểm trên bản đồ.

Tóm lại: composition là phương pháp chống lại việc 2 class liên quan nhau lại đi kế thừa nhau. Giải pháp của composition chính là truyền đối tượng của lớp A vào lớp B thay vì B kế thừa A. Một khi không kế thừa nghĩa là lớp con không bị lớp cha đẩy tất cả các biến/phương thức xuống. Gây phức tạp và dư thừa vùng dữ liệu của lớp con mỗi khi lớp con được khởi tạo, vì chúng ta sẽ thấy có nhiều trường hợp lớp con không cần kế thừa all các biến + phương thức của lớp cha.

Thử viết một ví dụ mang tính học thuật, sử dụng composition thể hiện thuật toán Bubble Sort:

PHP:
<?php

class BubbleSort {
    public function sort(array $arr) {
        $n = count($arr);

        for ($i = 0; $i < $n-1; $i++) {
            for ($j = 0; $j < $n-$i-1; $j++) {
                if ($arr[$j] > $arr[$j+1]) {
                    $temp = $arr[$j];
                    $arr[$j] = $arr[$j+1];
                    $arr[$j+1] = $temp;
                }
            }
        }

        return $arr;
    }
}

class SortAndSearch {
    private $sortingAlgorithm;

    public function __construct($sortingAlgorithm) {
        $this->sortingAlgorithm = $sortingAlgorithm;
    }

    public function sort(array $arr) {
        return $this->sortingAlgorithm->sort($arr);
    }
}

$arr = [9, 2, 5, 1, 7, 3];
$sortAndSearch = new SortAndSearch(new BubbleSort());
$sortedArr = $sortAndSearch->sort($arr);
print_r($sortedArr);

?>

Giải thích code: ta định nghĩa hai lớp: BubbleSort và SortAndSearch. Lớp BubbleSort chứa thuật toán sắp xếp nổi bọt và có một phương thức sort() để áp dụng thuật toán này vào một mảng cụ thể.

Lớp SortAndSearch nhận một thuật toán sắp xếp bất kỳ thông qua hàm tạo của nó, và sử dụng phương thức sort() của thuật toán này để sắp xếp một mảng được cung cấp. Điều này giúp chúng ta dễ dàng thay đổi thuật toán sắp xếp mà không cần thay đổi code khác.

Ở đoạn mã chính, ta tạo ra một mảng số nguyên và tạo một đối tượng SortAndSearch sử dụng thuật toán BubbleSort. Sau đó, ta sắp xếp mảng này bằng cách gọi phương thức sort() của đối tượng SortAndSearch. Kết quả được in ra màn hình sau khi đã sắp xếp.
 
Last edited:
Chứng minh rằng việc kế thừa sẽ gây ra lãng phí bộ nhớ, phức tạp khi mở rộng

Để chứng minh tôi sẽ viết một đoạn code PHP thể hiện kế thừa và code C tương ứng để bạn thấy rõ việc kế thừa gây lãng phí tài nguyên thế nào.

Code PHP:
PHP:
class Animal {
  protected $name;

  public function __construct($name) {
    $this->name = $name;
  }

  public function speak() {
    return "{$this->name} speaks";
  }
}

class Dog extends Animal {
  public function bark() {
    return "{$this->name} barks";
  }
}

$dog = new Dog("Fido");
echo $dog->speak(); // Fido speaks
echo $dog->bark(); // Fido barks

Code C:
C:
#include <stdio.h>
#include <stdlib.h>

struct Animal {
  char name[20];
};

struct Dog {
  struct Animal animal;
  int age;
};

void speak(struct Animal *animal) {
  printf("%s speaks\n", animal->name);
}

void bark(struct Dog *dog) {
  printf("%s barks\n", dog->animal.name);
}

int main() {
  struct Dog dog = { .animal = { "Fido" }, .age = 3 };
  speak(&dog.animal);
  bark(&dog);
  return 0;
}

-> dễ thấy 2 phương thức tại 2 class, cùng chức năng nhưng lớp con kế thừa lớp cha thành ra dư thừa.
-> biên dịch code C ta cũng sẽ thấy một vùng bộ nhớ đc ghi vào dù chẳng cần đến, thữa thãi. Quên k huỷ đi sẽ là nguyên nhân memory stack overflow
 
Last edited:
Cái bạn nói không thể hiện được cái khó khăn của OOP đâu. Ví dụ phải như 1 product được code OOP ngon lành rổi, nhưng gặp 1 feature mới mà code kiểu gì cũng break OOP, phải refactor lại từ tầng core bên dưới thì nó mới đẹp đúng OOP. Mà mấy cái này thì không nói trong 1, 2 câu được.
Lúc thiết kế thì chọn luôn ngôn ngữ đi.
 
Vài ghi chép về Tính kế thừa (Inheritance).

Tôi có tham gia vào các dự án thực tế, review code các dự án lớn trên github thì quan sát của tôi cho thấy. Hệ thống càng lớn, người ta càng tránh kế thừa. Bởi vì nó là nguyên nhân gây ra rò rỉ bộ nhớ cũng như gây bug và kìm hãm sự mở rộng của module bởi vì các lớp kế thừa nhau làm phức tạp trong hệ thống. Tóm lại, nên hạn chế kế thừa là tốt nhất. Nhưng cũng không nên loại bỏ tính kế thừa ra khỏi kiến trúc code. Vì thực tế kế thừa nó vẫn mang một ý nghĩa rất tối ưu là nó cho phép một class mới có thể sử dụng các thuộc tính và phương thức của class cha mà không cần phải định nghĩa lại chúng. Vấn đề là chúng ta khi nào thì nên kế thừa? Nó phải luôn đảm bảo 3 điều kiện:

1. Áp dụng kế thừa (inheritance) cho các thành phần phần mềm (software components) có sự tương đồng về chức năng và tính năng.

2. Chỉ nên kế thừa đến cấp 2. Nghĩa là lớp B kế thừa lớp A là dừng lại, không nên để lớp C kế thừa lớp B.

3. Lớp cha chỉ nên chứa thuộc tính/ phương thức chung chung cho toàn hệ thống.

Khi lớp con kế thừa lớp cha, khởi tạo lớp con thì lớp cha không khởi tạo đối tượng như nhiều người nghĩ. Mà thay vào đấy các vùng chứa dữ liệu định sẵn của lớp cha sẽ đẩy xuống lớp con khi lớp con được khởi tạo.

Điều này rất cần ghi nhớ để tránh khai báo một biến nào đấy chứa dữ liệu lớn tại lớp cha.

Chú ý rằng dù là lớp cha hay lớp con cũng không nên chứa các biến chứa dữ liệu lớn.

Giải pháp là sử dụng Composition, tôi sẽ giải thích về Composition như sau:

Composition là một kỹ thuật trong lập trình hướng đối tượng, nó cho phép ta xây dựng một đối tượng lớn bằng cách sử dụng các đối tượng nhỏ hơn. Ví dụ sau đây mô tả một ví dụ về composition trong PHP:

Giả sử chúng ta đang xây dựng một ứng dụng quản lý đơn hàng. Trong đó, mỗi đơn hàng sẽ có một số chi tiết về sản phẩm được đặt hàng, chẳng hạn như tên sản phẩm, giá, số lượng, v.v. Để biểu diễn các chi tiết sản phẩm này, chúng ta có thể tạo một lớp Product như sau:

PHP:
class Product
{
    private $name;
    private $price;
    private $quantity;

    public function __construct($name, $price, $quantity)
    {
        $this->name = $name;
        $this->price = $price;
        $this->quantity = $quantity;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }

    public function getQuantity()
    {
        return $this->quantity;
    }
}

Tiếp theo, chúng ta có thể tạo một lớp Order, đại diện cho một đơn hàng. Lớp Order sẽ bao gồm một danh sách các sản phẩm được đặt hàng, và các phương thức để thêm sản phẩm vào đơn hàng, tính tổng số tiền, v.v. Tuy nhiên, thay vì lưu trữ các chi tiết sản phẩm trong lớp Order trực tiếp, chúng ta sẽ sử dụng composition để lưu trữ các sản phẩm trong một đối tượng khác. Ví dụ:

PHP:
class Order
{
    private $products = [];

    public function addProduct(Product $product)
    {
        $this->products[] = $product;
    }

    public function getTotal()
    {
        $total = 0;
        foreach ($this->products as $product) {
            $total += $product->getPrice() * $product->getQuantity();
        }
        return $total;
    }
}

Như vậy, lớp Order không trực tiếp lưu trữ các chi tiết sản phẩm, mà thay vào đó lưu trữ các đối tượng Product. Khi tính tổng số tiền cho một đơn hàng, lớp Order sử dụng composition để truy xuất các giá trị của các đối tượng Product.

Chạy thử code:

PHP:
// Tạo các đối tượng sản phẩm
$product1 = new Product(10, 2);
$product2 = new Product(5, 3);

// Tạo đơn hàng và thêm sản phẩm vào đơn hàng
$order = new Order();
$order->addProduct($product1);
$order->addProduct($product2);

// Tính tổng giá trị của đơn hàng
$total = $order->getTotal();
echo 'Total: ' . $total;

Ouput: Total: 35

Thêm một ví dụ

Giả sử bạn đang xây dựng một ứng dụng hỗ trợ tìm đường đi từ điểm A đến điểm B trên một bản đồ.

Bạn đã tạo ra một class đại diện cho một điểm trên bản đồ, class này có các thuộc tính là tọa độ X và Y của điểm đó. Ngoài ra, bạn cần một class đại diện cho bản đồ, class này bao gồm danh sách các điểm và phương thức để tính toán đường đi từ một điểm đến một điểm khác trên bản đồ.

Ví dụ code:

PHP:
class Point
{
    private $x;
    private $y;

    public function __construct($x, $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX()
    {
        return $this->x;
    }

    public function getY()
    {
        return $this->y;
    }
}

class Map
{
    private $points = [];

    public function addPoint(Point $point)
    {
        $this->points[] = $point;
    }

    public function findShortestPath(Point $start, Point $end)
    {
        // Tính toán đường đi từ $start đến $end trên bản đồ và trả về đối tượng đường đi
        // ...
    }
}

// Tạo đối tượng bản đồ
$map = new Map();

// Thêm các điểm vào bản đồ
$pointA = new Point(10, 20);
$map->addPoint($pointA);

$pointB = new Point(30, 40);
$map->addPoint($pointB);

// Tìm đường đi từ A đến B
$path = $map->findShortestPath($pointA, $pointB);

Giải thích: class Map sử dụng class Point làm thành phần của nó bằng cách lưu trữ danh sách các điểm trên bản đồ trong mảng $points. Khi tính toán đường đi từ một điểm đến một điểm khác, class Map sử dụng các phương thức của class Point để truy xuất tọa độ của các điểm trên bản đồ.

Tóm lại: composition là phương pháp chống lại việc 2 class liên quan nhau lại đi kế thừa nhau. Giải pháp của composition chính là truyền đối tượng của lớp A vào lớp B thay vì B kế thừa A. Một khi không kế thừa nghĩa là lớp con không bị lớp cha đẩy tất cả các biến/phương thức xuống. Gây phức tạp và dư thừa vùng dữ liệu của lớp con mỗi khi lớp con được khởi tạo, vì chúng ta sẽ thấy có nhiều trường hợp lớp con không cần kế thừa all các biến + phương thức của lớp cha.

Thử viết một ví dụ mang tính học thuật, sử dụng composition thể hiện thuật toán Bubble Sort:

PHP:
<?php

class BubbleSort {
    public function sort(array $arr) {
        $n = count($arr);

        for ($i = 0; $i < $n-1; $i++) {
            for ($j = 0; $j < $n-$i-1; $j++) {
                if ($arr[$j] > $arr[$j+1]) {
                    $temp = $arr[$j];
                    $arr[$j] = $arr[$j+1];
                    $arr[$j+1] = $temp;
                }
            }
        }

        return $arr;
    }
}

class SortAndSearch {
    private $sortingAlgorithm;

    public function __construct($sortingAlgorithm) {
        $this->sortingAlgorithm = $sortingAlgorithm;
    }

    public function sort(array $arr) {
        return $this->sortingAlgorithm->sort($arr);
    }
}

$arr = [9, 2, 5, 1, 7, 3];
$sortAndSearch = new SortAndSearch(new BubbleSort());
$sortedArr = $sortAndSearch->sort($arr);
print_r($sortedArr);

?>

Giải thích code: ta định nghĩa hai lớp: BubbleSort và SortAndSearch. Lớp BubbleSort chứa thuật toán sắp xếp nổi bọt và có một phương thức sort() để áp dụng thuật toán này vào một mảng cụ thể.

Lớp SortAndSearch nhận một thuật toán sắp xếp bất kỳ thông qua hàm tạo của nó, và sử dụng phương thức sort() của thuật toán này để sắp xếp một mảng được cung cấp. Điều này giúp chúng ta dễ dàng thay đổi thuật toán sắp xếp mà không cần thay đổi code khác.

Ở đoạn mã chính, ta tạo ra một mảng số nguyên và tạo một đối tượng SortAndSearch sử dụng thuật toán BubbleSort. Sau đó, ta sắp xếp mảng này bằng cách gọi phương thức sort() của đối tượng SortAndSearch. Kết quả được in ra màn hình sau khi đã sắp xếp.
Cái này thì mình đồng ý với bạn. Mình thì không code back-end, không java, không C++ mà code JS (FP). Tuy nhiên các project lớn mình làm về front-end đang có xu hướng apply TS và chuyển dần về dạng code OOP để tận dụng các kỹ thuật và lợi ích của OOP như DI, decorators, extends, ... Tuy nhiên các thanh niên code front-end chưa hiểu bản chất của OOP và có xu hướng extends vô tội vạ các services/controller/repository. Điều này dẫn tới project legacy code đang rất tường minh (dù hơi phức tạp một chút) sau khi "upgrade" trở nên lộn xộn, khó đọc, khó sửa. Memory leak case xuất hiện liên tục (do cái kiểu prototype delegation của JS). Hơn nữa nó còn conflict với chính cái paradigm của React là composite và tạo nhiều reusable component nhất có thể.

Lời khuyên của một người bước ra từ quá trình migrate project thì tốt nhất là hiểu thì hãy làm, chưa hiểu thì học cho hiểu. Còn ông nào technical lead muốn apply kỹ thuật hay migrate sang cái gì mới thì hãy tự lượng sức của team trước :>
 
Chứng minh rằng việc kế thừa sẽ gây ra lãng phí bộ nhớ, phức tạp khi mở rộng

Để chứng minh tôi sẽ viết một đoạn code PHP thể hiện kế thừa và code C tương ứng để bạn thấy rõ việc kế thừa gây lãng phí tài nguyên thế nào.

Code PHP:
PHP:
class Animal {
  protected $name;

  public function __construct($name) {
    $this->name = $name;
  }

  public function speak() {
    return "{$this->name} speaks";
  }
}

class Dog extends Animal {
  public function bark() {
    return "{$this->name} barks";
  }
}

$dog = new Dog("Fido");
echo $dog->speak(); // Fido speaks
echo $dog->bark(); // Fido barks

Code C:
C:
#include <stdio.h>
#include <stdlib.h>

struct Animal {
  char name[20];
};

struct Dog {
  struct Animal animal;
  int age;
};

void speak(struct Animal *animal) {
  printf("%s speaks\n", animal->name);
}

void bark(struct Dog *dog) {
  printf("%s barks\n", dog->animal.name);
}

int main() {
  struct Dog dog = { .animal = { "Fido" }, .age = 3 };
  speak(&dog.animal);
  bark(&dog);
  return 0;
}

-> dễ thấy 2 phương thức tại 2 class, cùng chức năng nhưng lớp con kế thừa lớp cha thành ra dư thừa.
-> biên dịch code C ta cũng sẽ thấy một vùng bộ nhớ đc ghi vào dù chẳng cần đến, thữa thãi. Quên k huỷ đi sẽ là nguyên nhân memory stack overflow
Overriding đâu, tự sinh vấn đề rồi chê à
 
Code PHP:
PHP:
class Animal {
  protected $name;

  public function __construct($name) {
    $this->name = $name;
  }

  public function speak() {
    return "{$this->name} speaks";
  }
}

class Dog extends Animal {
  public function bark() {
    return "{$this->name} barks";
  }
}

$dog = new Dog("Fido");
echo $dog->speak(); // Fido speaks
echo $dog->bark(); // Fido barks
Vậy tại sao chúng ta không biến animal thành 1 abstract class, speak thành 1 abstract function, như vậy thì class Dog sẽ không cần viết 1 hàm mới mà chỉ cần override lại hàm speak() là được mà?
 
Vậy tại sao chúng ta không biến animal thành 1 abstract class, speak thành 1 abstract function, như vậy thì class Dog sẽ không cần viết 1 hàm mới mà chỉ cần override lại hàm speak() là được mà?
Ah tôi cố thô để dẫn chứng thôi
Các giải pháp đề cập sau đi.
 
khái niệm OOP có thống nhất giữa các ngôn ngữ đâu, OOP Java khác OOP C++, phán kế thừa chỉ nên cho B kế thừa A chứ đừng cho C kế thừa B thì C++ ngắc ngoải à
8NVopNd.png


theo toy thì kế thừa chỉ là trường hợp đặc biệt của composition, và chỉ xài kế thừa khi cần đa hình, mấy thằng cha viết sách đặt kế thừa làm 1 trong 3/4 tính chất lớn là sai lầm, nó chỉ nên được đề cập như là biện pháp enable đa hình trong tính chất đa hình thoy

có đọc được trên SO ông nào viết rất hay là kế thừa ko phải để reuse code như sách viết mà là để existing code use nó, nghĩa là xài cho đa hình
 
Last edited:
khái niệm OOP có thống nhất giữa các ngôn ngữ đâu, OOP Java khác OOP C++, phán kế thừa chỉ nên cho B kế thừa A chứ đừng cho C kế thừa B thì C++ ngắc ngoải à
8NVopNd.png


theo toy thì kế thừa chỉ là trường hợp đặc biệt của composition, và chỉ xài kế thừa khi cần đa hình, mấy thằng cha viết sách đặt kế thừa làm 1 trong 3/4 tính chất lớn là sai lầm, nó chỉ nên được đề cập như là biện pháp enable đa hình trong tính chất đa hình thoy

có đọc được trên SO ông nào viết rất hay là kế thừa ko phải để reuse code như sách viết mà là để existing code use nó, nghĩa là xài cho đa hình
SO là trang nào vậy fen, cái 4 tính chất có từ bao đời nay sao lại sai được
 
Abstract class thì phải để ông maintainer phán xét là có tội hay ko :LOL:

Riêng tôi gặp issue nào liên quan thì thích lắm vì làm rất lâu, nhưng solution thì sửa mấy dòng thôi

Cả static method nữa haha
 
Vài ghi chép về Tính kế thừa (Inheritance).

Tôi có tham gia vào các dự án thực tế, review code các dự án lớn trên github thì quan sát của tôi cho thấy. Hệ thống càng lớn, người ta càng tránh kế thừa. Bởi vì nó là nguyên nhân gây ra rò rỉ bộ nhớ cũng như gây bug và kìm hãm sự mở rộng của module bởi vì các lớp kế thừa nhau làm phức tạp trong hệ thống. Tóm lại, nên hạn chế kế thừa là tốt nhất. Nhưng cũng không nên loại bỏ tính kế thừa ra khỏi kiến trúc code. Vì thực tế kế thừa nó vẫn mang một ý nghĩa rất tối ưu là nó cho phép một class mới có thể sử dụng các thuộc tính và phương thức của class cha mà không cần phải định nghĩa lại chúng. Vấn đề là chúng ta khi nào thì nên kế thừa? Nó phải luôn đảm bảo 3 điều kiện:

1. Áp dụng kế thừa (inheritance) cho các thành phần phần mềm (software components) có sự tương đồng về chức năng và tính năng.

2. Chỉ nên kế thừa đến cấp 2. Nghĩa là lớp B kế thừa lớp A là dừng lại, không nên để lớp C kế thừa lớp B.

3. Lớp cha chỉ nên chứa thuộc tính/ phương thức chung chung cho toàn hệ thống.

Khi lớp con kế thừa lớp cha, khởi tạo lớp con thì lớp cha không khởi tạo đối tượng như nhiều người nghĩ. Mà thay vào đấy các vùng chứa dữ liệu định sẵn của lớp cha sẽ đẩy xuống lớp con khi lớp con được khởi tạo.

Điều này rất cần ghi nhớ để tránh khai báo một biến nào đấy chứa dữ liệu lớn tại lớp cha.

Chú ý rằng dù là lớp cha hay lớp con cũng không nên chứa các biến chứa dữ liệu lớn.

Giải pháp là sử dụng Composition, tôi sẽ giải thích về Composition như sau:

Composition là một kỹ thuật trong lập trình hướng đối tượng, nó cho phép ta xây dựng một đối tượng lớn bằng cách sử dụng các đối tượng nhỏ hơn. Ví dụ sau đây mô tả một ví dụ về composition trong PHP:

Giả sử chúng ta đang xây dựng một ứng dụng quản lý đơn hàng. Trong đó, mỗi đơn hàng sẽ có một số chi tiết về sản phẩm được đặt hàng, chẳng hạn như tên sản phẩm, giá, số lượng, v.v. Để biểu diễn các chi tiết sản phẩm này, chúng ta có thể tạo một lớp Product như sau:

PHP:
class Product
{
    private $name;
    private $price;
    private $quantity;

    public function __construct($name, $price, $quantity)
    {
        $this->name = $name;
        $this->price = $price;
        $this->quantity = $quantity;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }

    public function getQuantity()
    {
        return $this->quantity;
    }
}

Tiếp theo, chúng ta có thể tạo một lớp Order, đại diện cho một đơn hàng. Lớp Order sẽ bao gồm một danh sách các sản phẩm được đặt hàng, và các phương thức để thêm sản phẩm vào đơn hàng, tính tổng số tiền, v.v. Tuy nhiên, thay vì lưu trữ các chi tiết sản phẩm trong lớp Order trực tiếp, chúng ta sẽ sử dụng composition để lưu trữ các sản phẩm trong một đối tượng khác. Ví dụ:

PHP:
class Order
{
    private $products = [];

    public function addProduct(Product $product)
    {
        $this->products[] = $product;
    }

    public function getTotal()
    {
        $total = 0;
        foreach ($this->products as $product) {
            $total += $product->getPrice() * $product->getQuantity();
        }
        return $total;
    }
}

Như vậy, lớp Order không trực tiếp lưu trữ các chi tiết sản phẩm, mà thay vào đó lưu trữ các đối tượng Product. Khi tính tổng số tiền cho một đơn hàng, lớp Order sử dụng composition để truy xuất các giá trị của các đối tượng Product.

Chạy thử code:

PHP:
// Tạo các đối tượng sản phẩm
$product1 = new Product(10, 2);
$product2 = new Product(5, 3);

// Tạo đơn hàng và thêm sản phẩm vào đơn hàng
$order = new Order();
$order->addProduct($product1);
$order->addProduct($product2);

// Tính tổng giá trị của đơn hàng
$total = $order->getTotal();
echo 'Total: ' . $total;

Ouput: Total: 35

Thêm một ví dụ

Giả sử bạn đang xây dựng một ứng dụng hỗ trợ tìm đường đi từ điểm A đến điểm B trên một bản đồ.

Bạn đã tạo ra một class đại diện cho một điểm trên bản đồ, class này có các thuộc tính là tọa độ X và Y của điểm đó. Ngoài ra, bạn cần một class đại diện cho bản đồ, class này bao gồm danh sách các điểm và phương thức để tính toán đường đi từ một điểm đến một điểm khác trên bản đồ.

Ví dụ code:

PHP:
class Point
{
    private $x;
    private $y;

    public function __construct($x, $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX()
    {
        return $this->x;
    }

    public function getY()
    {
        return $this->y;
    }
}

class Map
{
    private $points = [];

    public function addPoint(Point $point)
    {
        $this->points[] = $point;
    }

    public function findShortestPath(Point $start, Point $end)
    {
        // Tính toán đường đi từ $start đến $end trên bản đồ và trả về đối tượng đường đi
        // ...
    }
}

// Tạo đối tượng bản đồ
$map = new Map();

// Thêm các điểm vào bản đồ
$pointA = new Point(10, 20);
$map->addPoint($pointA);

$pointB = new Point(30, 40);
$map->addPoint($pointB);

// Tìm đường đi từ A đến B
$path = $map->findShortestPath($pointA, $pointB);

Giải thích: class Map sử dụng class Point làm thành phần của nó bằng cách lưu trữ danh sách các điểm trên bản đồ trong mảng $points. Khi tính toán đường đi từ một điểm đến một điểm khác, class Map sử dụng các phương thức của class Point để truy xuất tọa độ của các điểm trên bản đồ.

Tóm lại: composition là phương pháp chống lại việc 2 class liên quan nhau lại đi kế thừa nhau. Giải pháp của composition chính là truyền đối tượng của lớp A vào lớp B thay vì B kế thừa A. Một khi không kế thừa nghĩa là lớp con không bị lớp cha đẩy tất cả các biến/phương thức xuống. Gây phức tạp và dư thừa vùng dữ liệu của lớp con mỗi khi lớp con được khởi tạo, vì chúng ta sẽ thấy có nhiều trường hợp lớp con không cần kế thừa all các biến + phương thức của lớp cha.

Thử viết một ví dụ mang tính học thuật, sử dụng composition thể hiện thuật toán Bubble Sort:

PHP:
<?php

class BubbleSort {
    public function sort(array $arr) {
        $n = count($arr);

        for ($i = 0; $i < $n-1; $i++) {
            for ($j = 0; $j < $n-$i-1; $j++) {
                if ($arr[$j] > $arr[$j+1]) {
                    $temp = $arr[$j];
                    $arr[$j] = $arr[$j+1];
                    $arr[$j+1] = $temp;
                }
            }
        }

        return $arr;
    }
}

class SortAndSearch {
    private $sortingAlgorithm;

    public function __construct($sortingAlgorithm) {
        $this->sortingAlgorithm = $sortingAlgorithm;
    }

    public function sort(array $arr) {
        return $this->sortingAlgorithm->sort($arr);
    }
}

$arr = [9, 2, 5, 1, 7, 3];
$sortAndSearch = new SortAndSearch(new BubbleSort());
$sortedArr = $sortAndSearch->sort($arr);
print_r($sortedArr);

?>

Giải thích code: ta định nghĩa hai lớp: BubbleSort và SortAndSearch. Lớp BubbleSort chứa thuật toán sắp xếp nổi bọt và có một phương thức sort() để áp dụng thuật toán này vào một mảng cụ thể.

Lớp SortAndSearch nhận một thuật toán sắp xếp bất kỳ thông qua hàm tạo của nó, và sử dụng phương thức sort() của thuật toán này để sắp xếp một mảng được cung cấp. Điều này giúp chúng ta dễ dàng thay đổi thuật toán sắp xếp mà không cần thay đổi code khác.

Ở đoạn mã chính, ta tạo ra một mảng số nguyên và tạo một đối tượng SortAndSearch sử dụng thuật toán BubbleSort. Sau đó, ta sắp xếp mảng này bằng cách gọi phương thức sort() của đối tượng SortAndSearch. Kết quả được in ra màn hình sau khi đã sắp xếp.
Hai ví dụ về composition không liên quan đến chủ đề bài viết mà lại quá dài. Cả 2 TH trên gần như bắt buộc phải dùng composition, không thể dùng inheritance được. Ngoài sử dụng composition, còn có thể sử dụng abstract class (interface) để có thể tránh inheritance.
Chứng minh rằng việc kế thừa sẽ gây ra lãng phí bộ nhớ, phức tạp khi mở rộng

Để chứng minh tôi sẽ viết một đoạn code PHP thể hiện kế thừa và code C tương ứng để bạn thấy rõ việc kế thừa gây lãng phí tài nguyên thế nào.

Code PHP:
PHP:
class Animal {
  protected $name;

  public function __construct($name) {
    $this->name = $name;
  }

  public function speak() {
    return "{$this->name} speaks";
  }
}

class Dog extends Animal {
  public function bark() {
    return "{$this->name} barks";
  }
}

$dog = new Dog("Fido");
echo $dog->speak(); // Fido speaks
echo $dog->bark(); // Fido barks

Code C:
C:
#include <stdio.h>
#include <stdlib.h>

struct Animal {
  char name[20];
};

struct Dog {
  struct Animal animal;
  int age;
};

void speak(struct Animal *animal) {
  printf("%s speaks\n", animal->name);
}

void bark(struct Dog *dog) {
  printf("%s barks\n", dog->animal.name);
}

int main() {
  struct Dog dog = { .animal = { "Fido" }, .age = 3 };
  speak(&dog.animal);
  bark(&dog);
  return 0;
}

-> dễ thấy 2 phương thức tại 2 class, cùng chức năng nhưng lớp con kế thừa lớp cha thành ra dư thừa.
-> biên dịch code C ta cũng sẽ thấy một vùng bộ nhớ đc ghi vào dù chẳng cần đến, thữa thãi. Quên k huỷ đi sẽ là nguyên nhân memory stack overflow
Sử dụng ngôn ngữ C để nói về OOP có vẻ không phù hợp lắm (C không hỗ trợ OOP). Từ đó mình cũng chưa thể hiểu 2 câu chốt của bác có ý nghĩa là gì.

Nhưng đúng là mình đồng ý là nên tránh kế thừa nếu có thể. Bác thử tham khảo thêm video này:
 
Hai ví dụ về composition không liên quan đến chủ đề bài viết mà lại quá dài. Cả 2 TH trên gần như bắt buộc phải dùng composition, không thể dùng inheritance được. Ngoài sử dụng composition, còn có thể sử dụng abstract class (interface) để có thể tránh inheritance.

Sử dụng ngôn ngữ C để nói về OOP có vẻ không phù hợp lắm (C không hỗ trợ OOP). Từ đó mình cũng chưa thể hiểu 2 câu chốt của bác có ý nghĩa là gì.

Nhưng đúng là mình đồng ý là nên tránh kế thừa nếu có thể. Bác thử tham khảo thêm video này:
Cả 2 ví dụ trên đều có thể tạo 2 lớp kế thừa để chạy bình thường nhưng nó rối rắm, code shit nên mới cần dùng composition để giải quyết. Tôi lấy ví dụ để dẫn chứng vấn đề mà nãy giờ các bạn cứ lôi dẫn chứng ra soi làm gì? Tôi cố tình tạo tình huống như vậy mà. Vì trong dự án nhiều người lạm dụng kế thừa, k liên quan cũng kế thừa vì tư duy rằng kế thừa để tận dụng mấy cái biến đã khai báo từ trước đỡ phải khai báo lại, bừa bãi là ở đấy.

Còn vì sao tôi viết code C ra làm ví dụ vì PHP zend engine nó viết bằng C, opcode php do C nó build ra. Ví dụ code C chứ chẳng lẽ lôi java ra để biểu thị cho vùng nhớ đc khai báo?
 
Last edited:
Cả 2 ví dụ trên đều có thể tạo 2 lớp kế thừa để chạy bình thường nhưng nó rối rắm, code shit nên mới cần dùng composition để giải quyết. Tôi lấy ví dụ để dẫn chứng vấn đề mà nãy giờ các bạn cứ lôi dẫn chứng ra soi làm gì? Tôi cố tình tạo tình huống như vậy mà. Vì trong dự án nhiều người lạm dụng kế thừa, k liên quan cũng kế thừa vì tư duy rằng kế thừa để tận dụng mấy cái biến đã khai báo từ trước đỡ phải khai báo lại, bừa bãi là ở đấy.

Còn vì sao tôi viết code C ra làm ví dụ vì PHP zend engine nó viết bằng C, opcode php do C nó build ra. Ví dụ code C chứ chẳng lẽ lôi java ra để biểu thị cho vùng nhớ đc khai báo?
Trước khi trả lời bác, mình cũng ngồi nghĩ 1 hồi làm sao để dùng kế thừa giải quyết cho 2 ví dụ trên nhưng mãi không ra nên mình mới thật sự muốn biết làm như thế nào. Mình hình dung 2 bài toán trên thành cái túiquả táo. Cái túi có thể chứa quả táo chứ làm sao cái túi kế thừa quả táo được?

Ngôn ngữ lập trình A với ngôn ngữ B viết nên engine biên dịch cho ngôn ngữ A thì có liên quan gì nhỉ? Zend engine biên dịch thẳng từ PHP ra Zend opcodes, C chỉ là ngôn ngữ lập trình ra Zend Engine. Thành ra quay lại ví dụ bác, nó sẽ không có ý nghĩa gì khi đem C ra để khẳng định là "dễ thấy 2 phương thức tại 2 class, cùng chức năng nhưng lớp con kế thừa lớp cha thành ra dư thừa" và "biên dịch code C ta cũng sẽ thấy một vùng bộ nhớ đc ghi vào dù chẳng cần đến, thữa thãi. [..] nguyên nhân memory stack overflow". Đó là chưa nói đến ở ví dụ code C, Dog đang chứa Animal chứ không phải kế thừa Animal.

Còn chuyện soi ví dụ thì là tất nhiên. Ví dụ là minh chứng cho lí thuyết của bác, nên nếu có bàn luận hay phản biện thì phải đi từ ví dụ lên.
 
Hai ví dụ về composition không liên quan đến chủ đề bài viết mà lại quá dài. Cả 2 TH trên gần như bắt buộc phải dùng composition, không thể dùng inheritance được. Ngoài sử dụng composition, còn có thể sử dụng abstract class (interface) để có thể tránh inheritance.

Sử dụng ngôn ngữ C để nói về OOP có vẻ không phù hợp lắm (C không hỗ trợ OOP). Từ đó mình cũng chưa thể hiểu 2 câu chốt của bác có ý nghĩa là gì.

Nhưng đúng là mình đồng ý là nên tránh kế thừa nếu có thể. Bác thử tham khảo thêm video này:
Ví dụ của thớt thì hơi khó hiểu còn xem video này thì ví dụ hợp lý hơn. Lúc đầu JpgImage kế thừa Image (JpgImage is a Image), sau biến đổi thành JpgImage chứa Image (JpgImage has a Image)
 
Ví dụ của thớt thì hơi khó hiểu còn xem video này thì ví dụ hợp lý hơn. Lúc đầu JpgImage kế thừa Image (JpgImage is a Image), sau biến đổi thành JpgImage chứa Image (JpgImage has a Image)
vd trong video thì JpegImage is a Image là sai rồi. Kể cả has a Image cũng sai nốt. Jpeg là encoding của Image, tách rời với Image hoàn toàn. Image là array of pixels, còn image encoding là 1 thuật toán mã hóa. Nói jpeg/png là Image thì như nói UTF-8/UTF-16 encoding là Unicode text vậy. Bởi vậy nó sửa lại tách biệt Image và Jpeg thì thấy hợp lý. Mà đặt tên JpegImage là thấy tầm bậy rồi, Jpeg/Png/BmpEncoding/Encoder gì hay như nó miệng nói là JpegFileFormat mới đúng, sao tay lại ghi là JpegImage
aVgiONl.png
 
Last edited:
Back
Top