Abstract class trong PHP

Bài viết được post tại: http://portal.daynhauhoc.com/p/abstract-class-trong-php/

Như ở bài trước về Interfaces, mình có đề cập cho các bạn về lớp trừu tượng hóa Abstract class rồi thì trong bài viết này, mình sẽ giới thiệu cho các bạn rõ hơn về Abstract class trong kỹ thuật lập trình hướng đối tượng. Khái niệm về Abstract…… xem thêm bài viết

1 Like

Cái ví dụ trong bài viết đưa ra dở tệ.
Kết luận cũng ko chính xác -_-

1 Like

Anh giúp em đính chính lại ví dụ và kết luận với :frowning:

Abstract class: là một class cha cho tất cả các class có cùng bản chất. Bản chất ở đây được hiểu là kiểu, loại, nhiệm vụ của class. Hai class cùng hiện thực một interface có thể hoàn toàn khác nhau về bản chất.

hay em hiểu sai về cái Abstract này rồi :expressionless:

Abstract class là 1 lớp trừu tượng. Lớp trừu tượng là nó vẫn còn cái gì đó trừu tượng (abstract method), cần phải được khai báo rõ ràng ở lớp con (concrete class).

Để hiểu hơn phải đặt nó trong ngữ cảnh: 1 hệ thống có interface , abstract class và concrete class.
Thì do là lớp trừu tượng nên bạn không thể tạo ra trực tiếp 1 instance từ abstract class. Chỉ có concrete class là có thể tạo ra 1 instance.

1 Like

Mình thường hiểu thế này,
Abstract class để miêu tả 1 khái niệm trừu tượng, mà từ đó , tất cả các khái niệm được mở rộng (trong php thường dùng từ khoá extents) sẽ kế thừa các properties (các biến được khai báo trong Abstract class), hay các phương thức chung.
Class dùng để diễn đạt một khái niệm cụ thể, rõ ràng hơn.
Interface thường dùng để diễn dải ta có thể làm được những việc gì, chú ý ở đây chỉ là diễn tả ta làm được gì thôi, chứ làm thế nào thì mỗi class phải tự implements riêng.
Một ví dụ cụ thể là

    // Mình có 1 khái niệm khá trừu tượng là Loài chim
    abstract class LoaiChim{
        // Đã là loài chim thì phải có cánh, nên mình có thuộc tính là cánh
        protected $canh;
        // Thuộc loài chim thì phải có chân
        protected $chân;
        // Chi thì phải có mỏ
        protected $mỏ;
    }
    // Bên cạnh đó mình có Interface thể hiện các hành động mà 1 class có thể làm được, chính vì vậy không thể khai báo properties, và viết function trong interface được. 
    // Khả năng bay
    interface iBay{
        public function bay();
    }
    // Khả năng đi bộ
    interface iĐibộ{
        public function đibộ();
    }
    
    // Khả năng bơi
    interface iBơi{
        public function bơi();
    }
    // Từ những khái niệm chung chung như trên ta tiến tới cụ thể hoá

    // Mình sẽ có 1 class là ChimCánhCụt
   class ChimCánhCụt extents LoàiChim implements iBơi, iĐiBộ{
       public function bơi(){
           $this->chân+=5; (quẫy chân đạp nước với 5 bước tiến 1 lần bơi)
       }

      public function đibộ{
           $this->chân ++ ;( mỗi lần đi bộ đi được có 1 bước, chim cánh cụt chân ngắn nên đi bộ chậm mà)
      }
   }

// Mình sẽ có 1 class là Đàđiểu
   class Đàđiểu extents LoàiChim implements iĐiBộ{
      public function đibộ{
           $this->chân +=5 ;( mỗi lần đi bộ đi được 5 bước, đà điều thì chạy rất nhanh)
      }
   }

 // Mình có 1 class ChimBóiCá
  class ChimBóiCá extents LoàiChim implements iĐiBộ, iBơi, iBay{
      public function đibộ{
           $this->chân +=1 ;
      }

       public function bơi(){
           $this->chân+=3; (Chim bói cá biết bơi, tất nhiên bơi thì chậm hơn bay)
       }

      public function bay(){
           $this->cánh+=6; (Chim bói cá biết bơi, tất nhiên bơi thì chậm hơn bay)
       }
   }

Với cách viết trên, ta sẽ thấy code của mình trở nên rõ ràng hơn rất nhiều, và đọc nhanh và dễ hơn, nhìn vào dòng lệnh

  class ChimBóiCá extents LoàiChim implements iĐiBộ, iBơi, iBay

sẽ dễ dàng biết được class ChimBoiCa có khả năng ( method) đi bộ, bay, và bơi

 class Đàđiểu extents LoàiChim implements iĐiBộ

Đà điểu thì chỉ có thể đi bộ được thôi, ít code thừa hơn. Và khi sử dụng ta có thể làm như sau

 class CuocThiBoi{
        $danh_sách_ứng_viên= array()

       // chỉ chấp nhận các ứng viên nào biết bơi
       public funcition thêmUngVien(IBoi $ứng_viên){
           $this->danh_sách_ứng_viên[] = $ứng_viên;
       }
       
    
       public function thiđấu(){
           foreach($this->danh_sach_ưng_viên as $ứng_viên){
                $ứng_viên->bơi();
    
           }
       }
    }
    
    Khi ta xử dụng sẽ là
    
    $cuoc_thi_bơi = new CuocThiBoi();
    
    $cuọc_thi_boi->thêmUngVien(Chim bói cá);
    $cuọc_thi_boi->thêmUngVien(Chim cánh cụt);
    $cuọc_thi_boi->thêmUngVien(Đà điểu); // quăng exception ngay từ vòng gửi xe

Một số sai lầm phổ biến của các bạn khi mới code thường gặp phải như

abstract class LoaiChim{
    // Đã là loài chim thì phải có cánh, nên mình có thuộc tính là cánh
    protected $canh;
    // Thuộc loài chim thì phải có chân
    protected $chân;
    // Chi thì phải có mỏ
    protected $mỏ;

     public function đibộ{
       $this->chân +=1 ;
     }

   public function bơi(){
       $this->chân+=1; 
   }

  public function bay(){
       $this->cánh+=1; 
   }
}
 // Mình sẽ có 1 class là ChimCánhCụt
   class ChimCánhCụt extents LoàiChim{
// không cần phải override lại vì dùng lại được 
      //public function đibộ

   // không biết bay nhưng vẫn phải bay vì bay được 0 bước
       public function bay(){
           $this->cánh+=0; 
       }

      public function bơi(){
           $this->chân+=5; (quẫy chân đạp nước với 5 bước tiến 1 lần bơi)
       }
   }

// Mình sẽ có 1 class là Đàđiểu
   class Đàđiểu extents LoàiChim implements iĐiBộ{
      public function đibộ{
           $this->chân +=5 ;( mỗi lần đi bộ đi được 5 bước, đà điều thì chạy rất nhanh)
      }

       // không biết bay nhưng vẫn phải bay vì bay được 0 bước
       public function bay(){
           $this->cánh+=0; 
       }

      public function bơi(){
           $this->chân+=0; 
       }
   }

 // Mình có 1 class ChimBóiCá
  class ChimBóiCá extents LoàiChim implements iĐiBộ, iBơi, iBay{
      // không cần ovrried lại
    //  public function đibộ

       public function bơi(){
           $this->chân+=3; (Chim bói cá biết bơi, tất nhiên bơi thì chậm hơn bay)
       }

      public function bay(){
           $this->cánh+=6; (Chim bói cá biết bơi, tất nhiên bơi thì chậm hơn bay)
       }
   }

Cách viết này thường được các bạn viết, vì nó có 1 ưu điểm là mình sẽ implement được 1 số các method thường dùng để khi extends ra cụ thể thì không tốn công viết lại nữa, ở trong trường hợp này là method đi bộ được tiết kiệm 2 lần, không cần viết lại. Nhưng thật chất nó cho ta một cấu trúc không rõ ràng. Rõ ràng chim cánh cụt không biết bay nhưng vẫn phải có method bay, hay Đà điểu thì làm gì biết bơi với bay, những vẫn phải ôm đồm 2 việc này.
Và khi các bạn cần sử dụng thì càng khó khăn hơn. Giống như trên tôi muốn tổ chức 1 cuộc thi bơi, giữa muôn thú thì tôi phải làm sao

class CuocThiBoi{
    $danh_sách_ứng_viên= array()
   public funcition thêmUngVien($ứng_viên){
       $this->danh_sách_ứng_viên[] = $ứng_viên;
   }
   

   public function thiđấu(){
       foreach($this->danh_sach_ưng_viên as $ứng_viên){
            $ứng_viên->bơi();

       }
   }
}

Khi ta xử dụng sẽ là

$cuoc_thi_bơi = new CuocThiBoi();

$cuọc_thi_boi->thêmUngVien(Chim bói cá);
$cuọc_thi_boi->thêmUngVien(Chim cánh cụt);
$cuọc_thi_boi->thêmUngVien(Đà điểu);

$cuộc_thi_bơi->thi đấu();  ====> đà điểu thiệt mạng

Bạn thấy rõ code rất mập mời

Bạn hiểu ý nghĩa của abstract class nhưng dùng nó thì phải lưu ý một chút là nếu abstract class mà không có cái gì đó trừu tượng ( abstract function) thì chuyển nó luôn về concrete class (lớp cụ thể) cho nó đơn giản

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?