Khi nào nên sử dụng các lớp tĩnh và khởi tạo


170

PHP là ngôn ngữ lập trình đầu tiên của tôi. Tôi hoàn toàn không thể quấn đầu khi sử dụng các lớp tĩnh so với các đối tượng được khởi tạo.

Tôi nhận ra rằng bạn có thể nhân đôi và nhân bản các đối tượng. Tuy nhiên, trong tất cả thời gian của tôi sử dụng php, bất kỳ đối tượng hoặc hàm nào luôn kết thúc dưới dạng một giá trị trả về (mảng, chuỗi, int) hoặc void.

Tôi hiểu các khái niệm trong sách như một lớp nhân vật trò chơi video. sao chép đối tượng xe hơi và làm cho cái mới màu đỏ , tất cả đều có ý nghĩa, nhưng những gì không phải là ứng dụng của nó trong ứng dụng php và web.

Một ví dụ đơn giản. Một blog. Những đối tượng nào của blog sẽ được triển khai tốt nhất dưới dạng đối tượng tĩnh hoặc tức thời? Lớp DB? Tại sao không khởi tạo đối tượng db trong phạm vi toàn cầu? Tại sao không làm cho mọi đối tượng tĩnh thay thế? Hiệu suất thì sao?

Có phải tất cả chỉ là phong cách? Có một cách thích hợp để làm công cụ này?

Câu trả lời:


123

Đây là một câu hỏi khá thú vị - và câu trả lời cũng có thể trở nên thú vị ^^

Cách đơn giản nhất để xem xét mọi thứ có thể là:

  • sử dụng một lớp có sẵn trong đó mỗi đối tượng có dữ liệu riêng (như người dùng có tên)
  • sử dụng một lớp tĩnh khi nó chỉ là một công cụ hoạt động trên các công cụ khác (ví dụ như trình chuyển đổi cú pháp cho mã BB sang HTML; nó không có sự sống riêng)

(Vâng, tôi thừa nhận, thực sự rất đơn giản hóa quá mức ...)

Một điều về các phương thức / lớp tĩnh là chúng không tạo điều kiện cho kiểm thử đơn vị (ít nhất là trong PHP, nhưng có lẽ bằng các ngôn ngữ khác).

Một điều khác về dữ liệu tĩnh là chỉ có một phiên bản của nó tồn tại trong chương trình của bạn: nếu bạn đặt MyClass :: $ myData thành một giá trị nào đó ở đâu đó, nó sẽ có giá trị này và chỉ có nó, ở mọi nơi - Nói về người dùng, bạn sẽ chỉ có thể có một người dùng - điều đó không tuyệt lắm phải không?

Đối với một hệ thống blog, tôi có thể nói gì? Thực sự không có nhiều thứ tôi sẽ viết dưới dạng tĩnh, thực sự, tôi nghĩ vậy; có thể là lớp truy cập DB, nhưng cuối cùng có lẽ là không


Vì vậy, về cơ bản sử dụng các đối tượng khi làm việc với những thứ độc đáo như ảnh, người dùng, bài đăng vv và sử dụng tĩnh khi nó có ý nghĩa gì cho những thứ chung chung?
Robert Rocha

1
nói tốt về người dùng, bạn chỉ có một người dùng hoạt động cho mỗi yêu cầu. vì vậy việc lôi kéo người dùng tích cực của yêu cầu tĩnh sẽ có ý nghĩa
My1

69

Hai lý do chính chống lại việc sử dụng các phương thức tĩnh là:

  • mã bằng các phương thức tĩnh rất khó kiểm tra
  • mã sử dụng các phương thức tĩnh khó mở rộng

Có một cuộc gọi phương thức tĩnh bên trong một số phương thức khác thực sự tồi tệ hơn việc nhập một biến toàn cục. Trong PHP, các lớp là các ký hiệu toàn cục, vì vậy mỗi khi bạn gọi một phương thức tĩnh, bạn dựa vào một ký hiệu toàn cục (tên lớp). Đây là một trường hợp khi toàn cầu là xấu xa. Tôi gặp vấn đề với cách tiếp cận này với một số thành phần của Zend Framework. Có các lớp sử dụng các cuộc gọi phương thức tĩnh (nhà máy) để xây dựng các đối tượng. Tôi không thể cung cấp một nhà máy khác cho trường hợp đó để lấy lại một đối tượng tùy chỉnh. Giải pháp cho vấn đề này là chỉ sử dụng các thể hiện và phương pháp instace và thực thi các singletons và tương tự khi bắt đầu chương trình.

Miško Hevery , người làm việc như một Huấn luyện viên Agile tại Google, có một lý thuyết thú vị, hay nói đúng hơn là, chúng ta nên tách thời gian tạo đối tượng khỏi thời gian chúng ta sử dụng đối tượng. Vì vậy, vòng đời của một chương trình được chia làm hai. Phần đầu tiên ( main()phương thức giả sử), sẽ xử lý tất cả các hệ thống dây đối tượng trong ứng dụng của bạn và phần thực hiện công việc thực tế.

Vì vậy, thay vì có:

class HttpClient
{
    public function request()
    {
        return HttpResponse::build();
    }
}

Chúng ta nên làm:

class HttpClient
{
    private $httpResponseFactory;

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

    public function request()
    {
        return $this->httpResponseFactory->build();
    }
}

Và sau đó, trong trang chỉ mục / trang chính, chúng tôi sẽ làm (đây là bước nối dây đối tượng hoặc thời gian để tạo biểu đồ các trường hợp sẽ được chương trình sử dụng):

$httpResponseFactory = new HttpResponseFactory;
$httpClient          = new HttpClient($httpResponseFactory);
$httpResponse        = $httpClient->request();

Ý tưởng chính là tách các phần phụ thuộc ra khỏi các lớp của bạn. Bằng cách này, mã có thể mở rộng hơn nhiều và, phần quan trọng nhất đối với tôi, có thể kiểm chứng được. Tại sao nó quan trọng hơn để được kiểm tra? Bởi vì tôi không phải lúc nào cũng viết mã thư viện, nên khả năng mở rộng không quan trọng lắm, nhưng khả năng kiểm tra rất quan trọng khi tôi thực hiện tái cấu trúc. Dù sao, mã có thể kiểm tra thường mang lại mã mở rộng, vì vậy nó không thực sự là một tình huống hoặc.

Miško Hevery cũng phân biệt rõ ràng giữa singletons và Singletons (có hoặc không có chữ S viết hoa). Sự khác biệt rất đơn giản. Singletons với chữ "s" chữ thường được thi hành bằng cách nối dây trong chỉ mục / chính. Bạn khởi tạo một đối tượng của một lớp không triển khai mẫu Singleton và lưu ý rằng bạn chỉ chuyển thể hiện đó cho bất kỳ trường hợp nào khác cần nó. Mặt khác, Singleton, với chữ "S" viết hoa là một triển khai của mẫu (chống) cổ điển. Về cơ bản là một sự ngụy trang toàn cầu không được sử dụng nhiều trong thế giới PHP. Tôi đã không nhìn thấy một cho đến thời điểm này. Nếu bạn muốn một kết nối DB duy nhất được sử dụng bởi tất cả các lớp của bạn thì tốt hơn là làm như thế này:

$db = new DbConnection;

$users    = new UserCollection($db);
$posts    = new PostCollection($db);
$comments = new CommentsCollection($db);

Bằng cách làm ở trên, rõ ràng rằng chúng tôi có một người độc thân và chúng tôi cũng có một cách hay để tiêm giả hoặc sơ khai trong các bài kiểm tra của chúng tôi. Thật đáng ngạc nhiên khi các bài kiểm tra đơn vị dẫn đến một thiết kế tốt hơn. Nhưng nó rất có ý nghĩa khi bạn nghĩ rằng các bài kiểm tra buộc bạn phải suy nghĩ về cách bạn sử dụng mã đó.

/**
 * An example of a test using PHPUnit. The point is to see how easy it is to
 * pass the UserCollection constructor an alternative implementation of
 * DbCollection.
 */
class UserCollection extends PHPUnit_Framework_TestCase
{
    public function testGetAllComments()
    {
        $mockedMethods = array('query');
        $dbMock = $this->getMock('DbConnection', $mockedMethods);
        $dbMock->expects($this->any())
               ->method('query')
               ->will($this->returnValue(array('John', 'George')));

        $userCollection = new UserCollection($dbMock);
        $allUsers       = $userCollection->getAll();

        $this->assertEquals(array('John', 'George'), $allUsers);
    }
}

Tình huống duy nhất mà tôi sử dụng (và tôi đã sử dụng chúng để bắt chước đối tượng nguyên mẫu JavaScript trong PHP 5.3) là khi tôi biết rằng trường tương ứng sẽ có cùng thể hiện giá trị chéo. Tại thời điểm đó, bạn có thể sử dụng một thuộc tính tĩnh và có thể là một cặp phương thức getter / setter tĩnh. Dù sao, đừng quên thêm khả năng ghi đè thành viên tĩnh bằng một thành viên thể hiện. Ví dụ, Zend Framework đã sử dụng một thuộc tính tĩnh để chỉ định tên của lớp bộ điều hợp DB được sử dụng trong các trường hợp của Zend_Db_Table. Đã được một lúc kể từ khi tôi sử dụng chúng vì vậy nó có thể không còn phù hợp nữa, nhưng đó là cách tôi nhớ nó.

Các phương thức tĩnh không xử lý các thuộc tính tĩnh phải là các hàm. PHP có các chức năng và chúng ta nên sử dụng chúng.


1
@ Tôi, tôi không nghi ngờ điều đó một chút. Tôi nhận thấy có giá trị ở vị trí / vai trò là tốt; tuy nhiên, giống như mọi thứ liên quan đến XP / Agile, nó có một cái tên rất hay.
jason

2
"Dependency Injection" Tôi tin là thuật ngữ nghe có vẻ quá phức tạp cho việc này. RẤT NHIỀU và rất nhiều đọc trên tất cả trên SO và google-đất. Theo như "singletons", đây là một cái gì đó thực sự khiến tôi suy nghĩ: sl slideshoware.net/go_oh/ Khăn Một cuộc nói chuyện về lý do tại sao các singletons PHP thực sự không có ý nghĩa gì. Hy vọng điều này đóng góp một chút cho một câu hỏi cũ :)
dudewad

22

Vì vậy, trong PHP tĩnh có thể được áp dụng cho các hàm hoặc các biến. Các biến không tĩnh được gắn với một thể hiện cụ thể của một lớp. Các phương thức không tĩnh hành động trên một thể hiện của một lớp. Vì vậy, hãy tạo nên một lớp gọi là BlogPost.

titlesẽ là một thành viên không tĩnh. Nó chứa tiêu đề của bài viết blog đó. Chúng ta cũng có thể có một phương thức gọi là find_related(). Nó không tĩnh vì nó yêu cầu thông tin từ một thể hiện cụ thể của lớp bài đăng trên blog.

Lớp này sẽ trông giống như thế này:

class blog_post {
    public $title;
    public $my_dao;

    public function find_related() {
        $this->my_dao->find_all_with_title_words($this->title);
    }
}

Mặt khác, bằng cách sử dụng các hàm tĩnh, bạn có thể viết một lớp như thế này:

class blog_post_helper {
    public static function find_related($blog_post) {
         // Do stuff.
    }
}

Trong trường hợp này, vì hàm là tĩnh và không hoạt động trên bất kỳ bài đăng blog cụ thể nào, bạn phải vượt qua trong bài đăng blog dưới dạng đối số.

Về cơ bản đây là một câu hỏi về thiết kế hướng đối tượng. Các lớp học của bạn là danh từ trong hệ thống của bạn và các chức năng hoạt động trên chúng là các động từ. Hàm tĩnh là thủ tục. Bạn truyền vào đối tượng của các hàm làm đối số.


Cập nhật: Tôi cũng nói thêm rằng quyết định hiếm khi giữa phương thức cá thể và phương thức tĩnh, và nhiều hơn nữa giữa việc sử dụng các lớp và sử dụng mảng kết hợp. Ví dụ: trong một ứng dụng viết blog, bạn có thể đọc các bài đăng blog từ cơ sở dữ liệu và chuyển đổi chúng thành các đối tượng hoặc bạn để chúng trong tập kết quả và coi chúng là mảng kết hợp. Sau đó, bạn viết các hàm lấy mảng kết hợp hoặc danh sách các mảng kết hợp làm đối số.

Trong kịch bản OO, bạn viết các phương thức trên BlogPostlớp của bạn hoạt động trên các bài đăng riêng lẻ và bạn viết các phương thức tĩnh hoạt động trên các bộ sưu tập các bài đăng.


4
Câu trả lời này khá hay, đặc biệt với bản cập nhật bạn đã làm, vì đó là lý do tôi kết thúc câu hỏi này. Tôi hiểu chức năng của "::" so với "$ this", nhưng khi bạn thêm vào tài khoản, ví dụ bạn đã đưa ra về các blog blog được trích xuất từ ​​DB trong một mảng kết hợp .. Nó thêm một chiều hoàn toàn mới, đó là cũng trong giai điệu cơ bản của câu hỏi này.
Gerben Jacobs

Đây là một trong những câu trả lời thực tế tốt nhất (so với lý thuyết) cho câu hỏi PHP được hỏi nhiều này, phần phụ lục rất hữu ích. Tôi nghĩ rằng đây là câu trả lời mà rất nhiều lập trình viên PHP đang tìm kiếm, nâng cao!
ajmedway

14

Có phải tất cả chỉ là phong cách?

Một chặng đường dài, vâng. Bạn có thể viết các chương trình hướng đối tượng hoàn toàn tốt mà không bao giờ sử dụng các thành viên tĩnh. Trong thực tế, một số người sẽ tranh luận rằng các thành viên tĩnh là một tạp chất ở nơi đầu tiên. Tôi sẽ đề nghị rằng - với tư cách là người mới bắt đầu - bạn cố gắng tránh tất cả các thành viên tĩnh cùng nhau. Nó sẽ buộc bạn theo hướng viết theo hướng đối tượng hơn là phong cách thủ tục .


13

Tôi có một cách tiếp cận khác với hầu hết các câu trả lời ở đây, đặc biệt là khi sử dụng PHP. Tôi nghĩ rằng tất cả các lớp nên tĩnh trừ khi bạn có một lý do chính đáng tại sao không. Một số lý do "tại sao không" là:

  • Bạn cần nhiều phiên bản của lớp
  • Lớp học của bạn cần được mở rộng
  • Các phần trong mã của bạn không thể chia sẻ các biến lớp với bất kỳ phần nào khác

Hãy để tôi lấy một ví dụ. Vì mỗi tập lệnh PHP tạo mã HTML, khung công tác của tôi có lớp trình soạn thảo html. Điều này đảm bảo rằng không có lớp nào khác sẽ cố gắng viết HTML vì đây là một nhiệm vụ chuyên biệt nên được tập trung vào một lớp duy nhất.

Thông thường, bạn sẽ sử dụng lớp html như thế này:

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();

Mỗi khi get_buffer () được gọi, nó sẽ thiết lập lại mọi thứ để lớp tiếp theo sử dụng trình ghi html bắt đầu ở trạng thái đã biết.

Tất cả các lớp tĩnh của tôi có hàm init () cần được gọi trước khi lớp được sử dụng lần đầu tiên. Đây là nhiều hơn theo quy ước hơn là một điều cần thiết.

Sự thay thế cho một lớp tĩnh trong trường hợp này là lộn xộn. Bạn sẽ không muốn mọi lớp cần viết một chút html phải quản lý một thể hiện của trình soạn thảo html.

Bây giờ tôi sẽ cho bạn một ví dụ về việc khi nào không sử dụng các lớp tĩnh. Lớp biểu mẫu của tôi quản lý một danh sách các thành phần biểu mẫu như nhập văn bản, danh sách thả xuống và hơn thế nữa. Nó thường được sử dụng như thế này:

$form = new form(stuff here);
$form->add(new text(stuff here));
$form->add(new submit(stuff here));
$form->render(); // Creates the entire form using the html class

Không có cách nào bạn có thể làm điều này với các lớp tĩnh, đặc biệt là xem xét rằng một số hàm tạo của mỗi lớp được thêm làm rất nhiều công việc. Ngoài ra, chuỗi thừa kế cho tất cả các yếu tố khá phức tạp. Vì vậy, đây là một ví dụ rõ ràng trong đó các lớp tĩnh không nên được sử dụng.

Hầu hết các lớp tiện ích như chuỗi chuyển đổi / định dạng là ứng cử viên tốt để trở thành lớp tĩnh. Quy tắc của tôi rất đơn giản: mọi thứ đều tĩnh trong PHP trừ khi có một lý do tại sao nó không nên.


bạn có thể viết một ví dụ về điểm dưới đây "Các phần trong mã của bạn không thể chia sẻ các biến lớp với bất kỳ phần nào khác" #JG Estiot
khurshed alam

10

"Có một cuộc gọi phương thức tĩnh bên trong một số phương thức khác thực sự tồi tệ hơn việc nhập một biến toàn cục." (định nghĩa "tệ hơn") ... và "Các phương thức tĩnh không xử lý các thuộc tính tĩnh sẽ là các hàm".

Đây là cả hai tuyên bố khá quét. Nếu tôi có một tập hợp các hàm có liên quan đến chủ đề, nhưng dữ liệu cá thể hoàn toàn không phù hợp, tôi muốn chúng được định nghĩa trong một lớp chứ không phải mỗi hàm trong không gian tên toàn cục. Tôi chỉ đang sử dụng các cơ chế có sẵn trong PHP5 để

  • cung cấp cho họ tất cả một không gian tên - tránh bất kỳ xung đột tên nào
  • giữ chúng ở vị trí vật lý cùng nhau thay vì nằm rải rác trong một dự án - các nhà phát triển khác có thể dễ dàng tìm thấy những gì đã có sẵn và ít có khả năng tái tạo lại bánh xe
  • hãy để tôi sử dụng các hằng lớp thay vì định nghĩa toàn cục cho bất kỳ giá trị ma thuật nào

đó hoàn toàn là một cách thuận tiện để thực thi sự gắn kết cao hơn và khớp nối thấp hơn.

Và FWIW - không có thứ đó, ít nhất là trong PHP5, là "các lớp tĩnh"; phương thức và tính chất có thể là tĩnh. Để ngăn chặn việc khởi tạo lớp, người ta cũng có thể khai báo nó là trừu tượng.


2
"Để ngăn chặn việc khởi tạo lớp, người ta cũng có thể tuyên bố nó trừu tượng" - Tôi rất thích điều đó. Trước đây tôi đã đọc về những người biến __construct () thành một hàm riêng tư, nhưng tôi nghĩ nếu tôi cần, tôi có thể sẽ sử dụng trừu tượng
Kavi Siegel

7

Trước tiên hãy tự hỏi, đối tượng này sẽ đại diện cho cái gì? Một đối tượng là tốt cho hoạt động trên các bộ dữ liệu động riêng biệt.

Một ví dụ tốt sẽ là ORM hoặc lớp trừu tượng cơ sở dữ liệu. Bạn có thể có nhiều kết nối cơ sở dữ liệu.

$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));

Hai kết nối này có thể hoạt động độc lập:

$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);

Bây giờ trong gói / thư viện này, có thể có các lớp khác như Db_Row, v.v. để thể hiện một hàng cụ thể được trả về từ câu lệnh SELECT. Nếu lớp Db_Row này là một lớp tĩnh, thì điều đó sẽ giả sử rằng bạn chỉ có một hàng dữ liệu trong một cơ sở dữ liệu và không thể thực hiện những gì một đối tượng có thể. Với một ví dụ, giờ đây bạn có thể có số lượng hàng không giới hạn trong một số lượng bảng không giới hạn trong số lượng cơ sở dữ liệu không giới hạn. Giới hạn duy nhất là phần cứng máy chủ;).

Ví dụ: nếu phương thức getRows trên đối tượng Db trả về một mảng các đối tượng Db_Row, thì bây giờ bạn có thể hoạt động trên mỗi hàng độc lập với nhau:

foreach ($someRecordsFromDb1 as $row) {
    // change some values
    $row->someFieldValue = 'I am the value for someFieldValue';
    $row->anotherDbField = 1;

    // now save that record/row
    $row->save();
}

foreach ($someRecordsFromDb2 as $row) {
    // delete a row
    $row->delete();
}

Một ví dụ điển hình của lớp tĩnh sẽ là thứ xử lý các biến đăng ký hoặc biến phiên vì sẽ chỉ có một sổ đăng ký hoặc một phiên cho mỗi người dùng.

Trong một phần của ứng dụng của bạn:

Session::set('someVar', 'toThisValue');

Và trong một phần khác:

Session::get('someVar'); // returns 'toThisValue'

Vì chỉ có một người dùng tại một thời điểm mỗi phiên, nên không có điểm nào trong việc tạo phiên bản cho Phiên.

Tôi hy vọng điều này sẽ giúp, cùng với các câu trả lời khác để giúp làm sáng tỏ mọi thứ. Là một lưu ý phụ, hãy kiểm tra " sự gắn kết " và " khớp nối ". Họ phác thảo một số thực tiễn rất, rất tốt để sử dụng khi viết mã của bạn áp dụng cho tất cả các ngôn ngữ lập trình.


6

Nếu lớp của bạn là tĩnh, điều đó có nghĩa là bạn không thể chuyển đối tượng của nó sang các lớp khác (vì không có trường hợp nào có thể), điều đó có nghĩa là tất cả các lớp của bạn sẽ được sử dụng trực tiếp lớp tĩnh đó, điều đó có nghĩa là mã của bạn hiện được liên kết chặt chẽ với lớp .

Khớp nối chặt chẽ làm cho mã của bạn ít sử dụng lại, dễ vỡ và dễ bị lỗi. Bạn muốn tránh các lớp tĩnh để có thể truyền thể hiện của lớp này sang các lớp khác.

Và vâng, đây chỉ là một trong nhiều lý do khác mà một số trong đó đã được đề cập.


3

Nói chung, bạn nên sử dụng các biến thành viên và các hàm thành viên trừ khi nó hoàn toàn phải được chia sẻ giữa tất cả các trường hợp hoặc trừ khi bạn đang tạo một singleton. Sử dụng dữ liệu thành viên và các chức năng thành viên cho phép bạn sử dụng lại các chức năng của mình cho nhiều phần dữ liệu khác nhau, trong khi bạn chỉ có thể có một bản sao dữ liệu mà bạn vận hành nếu bạn sử dụng dữ liệu tĩnh và chức năng. Ngoài ra, mặc dù không thể áp dụng cho PHP, các hàm và dữ liệu tĩnh dẫn đến mã không được gửi lại, trong khi dữ liệu lớp tạo điều kiện cho reentrancy.


3

Tôi muốn nói rằng chắc chắn có một trường hợp tôi muốn các biến tĩnh - trong các ứng dụng ngôn ngữ chéo. Bạn có thể có một lớp mà bạn chuyển một ngôn ngữ cho (ví dụ $ _SESSION ['ngôn ngữ']) và đến lượt nó truy cập các lớp khác được thiết kế như vậy:

Srings.php //The main class to access
StringsENUS.php  //English/US 
StringsESAR.php  //Spanish/Argentina
//...etc

Sử dụng String :: getString ("somestring") là một cách hay để trừu tượng hóa việc sử dụng ngôn ngữ của bạn ra khỏi ứng dụng của bạn. Bạn có thể làm điều đó theo cách bạn muốn, nhưng trong trường hợp này, mỗi tệp chuỗi có hằng số với các giá trị chuỗi được truy cập bởi lớp Chuỗi hoạt động khá tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.