2012-02-18 31 views
13

Tôi đang sử dụng __get() để làm cho một số thuộc tính của tôi "động" (chỉ khởi tạo chúng khi được yêu cầu). Các thuộc tính "giả" này được lưu trữ bên trong một thuộc tính mảng riêng tư, mà tôi đang kiểm tra bên trong __get.Sử dụng __get() (ma thuật) để mô phỏng tính thích hợp chỉ đọc và tải chậm

Dù sao, bạn có nghĩ rằng tốt hơn nên tạo phương pháp cho mỗi trong số các proprties này thay vì làm nó trong một tuyên bố chuyển đổi?


Chỉnh sửa: kiểm tra tốc độ

Tôi chỉ quan tâm đến hiệu suất, công cụ khác mà @Gordon đề cập không phải là quan trọng với tôi:

  • không cần thiết thêm phức tạp - nó không thực sự tăng mức độ phức tạp của ứng dụng của tôi
  • API không rõ ràng dễ vỡ - Tôi đặc biệt muốn API của mình bị "cách ly"; Các tài liệu nên nói cho người khác làm thế nào để sử dụng nó: P

Vì vậy, đây là những bài kiểm tra mà tôi đã làm, mà làm cho tôi nghĩ rằng hiệu suất hit agument là phi lý:

Kết quả cho 50.000 cuộc gọi (trên PHP 5.3 .9):

enter image description here

(t1 = ma thuật với switch, t2 = getter, t3 = ma thuật với cuộc gọi getter thêm)

Không chắc những gì các "Cum" điều có ý nghĩa trên t3. Nó không thể là thời gian tích lũy vì t2 nên có 2K rồi ...

Mã:

class B{} 



class A{ 
    protected 
    $props = array(
     'test_obj' => false, 
    ); 

    // magic 
    function __get($name){ 
    if(isset($this->props[$name])){ 
     switch($name){ 

     case 'test_obj': 
      if(!($this->props[$name] instanceof B)) 
      $this->props[$name] = new B; 

     break; 
     } 

     return $this->props[$name]; 
    } 

    trigger_error('property doesnt exist'); 
    } 

    // standard getter 
    public function getTestObj(){ 
    if(!($this->props['test_obj'] instanceof B)) 
     $this->props['test_obj'] = new B; 

    return $this->props['test_obj']; 
    } 
} 



class AA extends A{ 

    // magic 
    function __get($name){ 
    $getter = "get".str_replace('_', '', $name); // give me a break, its just a test :P 

    if(method_exists($this, $getter)) 
     return $this->$getter(); 


    trigger_error('property doesnt exist'); 
    } 


} 


function t1(){ 
    $obj = new A; 

    for($i=1;$i<50000;$i++){ 
    $a = $obj->test_obj; 

    } 
    echo 'done.'; 
} 

function t2(){ 
    $obj = new A; 

    for($i=1;$i<50000;$i++){ 
    $a = $obj->getTestObj(); 

    } 
    echo 'done.'; 
} 

function t3(){ 
    $obj = new AA; 

    for($i=1;$i<50000;$i++){ 
    $a = $obj->test_obj; 

    } 
    echo 'done.'; 
} 

t1(); 
t2(); 
t3(); 

ps: tại sao tôi muốn sử dụng __get() trong phương pháp getter tiêu chuẩn? lý do duy nhất là vẻ đẹp api; vì tôi không thấy bất kỳ bất lợi thật, tôi đoán nó có giá trị nó: P


Edit: Nhiều bài kiểm tra tốc độ

thời gian này tôi đã sử dụng microtime để đo lường một số trung bình:

PHP 5.2. 4 và 5.3.0 (kết quả tương tự):

t1 - 0.12s 
t2 - 0.08s 
t3 - 0.24s 

PHP 5.3.9, với Xdebug tích cực này là lý do tại sao nó rất chậm:

t1 - 1.34s 
t2 - 1.26s 
t3- 5.06s 

PHP 5.3.9 với Xdebug bị vô hiệu hóa:

t1 - 0.30 
t2 - 0.25 
t3 - 0.86 


Một phương pháp:

// magic 
    function __get($name){ 
    $getter = "get".str_replace('_', '', $name); 

    if(method_exists($this, $getter)){ 
     $this->$name = $this->$getter(); // <-- create it 
     return $this->$name;     
    } 


    trigger_error('property doesnt exist'); 
    } 

Một tài sản công cộng với tên yêu cầu sẽ được tạo ra tự động sau khi cuộc gọi __get đầu tiên. Điều này giải quyết các vấn đề về tốc độ - nhận được 0.1s trong PHP 5.3 (nhanh hơn 12 lần so với getter tiêu chuẩn) và vấn đề mở rộng do Gordon đưa ra. Bạn có thể chỉ cần ghi đè lên getter trong lớp con.

Điểm bất lợi là bất động sản trở nên ghi :(

+0

Trong các thử nghiệm này, "cum" là lượng thời gian mà hàm đã thực hiện, bao gồm thời gian của bất kỳ hàm nào được gọi từ bên trong (về cơ bản đó là lượng thời gian thực tế từ khi hàm bắt đầu thực hiện cho đến khi hàm đó trả về). Điều này khác với thời gian "tự", có hiệu quả "tạm dừng" bộ hẹn giờ khi các chức năng khác được gọi. Trong trường hợp này, "cum" trừ "self" cho t3 hoạt động ra bao nhiêu thời gian đã được chi tiêu trong $ this -> $ getter() gọi, và "tự" là bao lâu nó đã cho nó để làm những thứ khác trong hàm __get(). –

Trả lời

18

Dưới đây là kết quả của mã của bạn theo báo cáo của Zend Debugger với PHP 5.3.6 trên máy Win7 của tôi:

Benchmark results

Như bạn có thể thấy, các cuộc gọi đến __get phương pháp của bạn là một việc tốt (3-4 lần) chậm hơn so với các cuộc gọi thông thường. Chúng tôi vẫn đang giao dịch với ít hơn 1s cho tổng số 50k cuộc gọi, vì vậy nó là không đáng kể khi được sử dụng trên quy mô nhỏ. Tuy nhiên, nếu ý định của bạn là xây dựng toàn bộ mã của bạn xung quanh các phương pháp ma thuật, bạn sẽ muốn lập hồ sơ ứng dụng cuối cùng để xem liệu nó có còn không đáng kể hay không.

Rất nhiều cho khía cạnh hiệu suất khá thú vị. Bây giờ chúng ta hãy nhìn vào những gì bạn xem xét "không quan trọng". Tôi sẽ nhấn mạnh rằng vì nó thực sự quan trọng hơn nhiều so với khía cạnh hiệu suất.

Về Uneeded gia tăng phức tạp bạn viết

nó không thực sự tăng ứng dụng phức tạp của tôi

Dĩ nhiên là có. Bạn có thể dễ dàng phát hiện ra nó bằng cách nhìn vào chiều sâu làm tổ của mã của bạn. Mã tốt ở bên trái. Nếu/switch/case/if của bạn là bốn cấp độ sâu.Điều này có nghĩa là có nhiều đường dẫn thực hiện có thể và sẽ dẫn đến mức cao hơn Cyclomatic Complexity, điều đó có nghĩa là khó duy trì và hiểu hơn.

Đây là số lượng cho lớp học của bạn A (w \ ra Output Getter thường được rút ngắn từ PHPLoc.):

Lines of Code (LOC):         19 
    Cyclomatic Complexity/Lines of Code:   0.16 
    Average Method Length (NCLOC):      18 
    Cyclomatic Complexity/Number of Methods:  4.00 

Một giá trị 4.00 nghĩa này đã ở cạnh sự phức tạp đến trung bình. Con số này tăng thêm 2 cho mỗi trường hợp bổ sung mà bạn đưa vào công tắc của mình. Ngoài ra, nó sẽ biến mã của bạn thành một mớ hỗn độn theo thủ tục bởi vì tất cả các logic nằm bên trong switch/case thay vì chia nó thành các đơn vị rời rạc, ví dụ: Getters duy nhất.

Một trình tải gọn hơn, thậm chí là tải chậm, không cần phải phức tạp vừa phải. Hãy xem xét các lớp cùng với một đồng bằng cũ PHP Getter:

class Foo 
{ 
    protected $bar; 
    public function getBar() 
    { 
     // Lazy Initialization 
     if ($this->bar === null) { 
      $this->bar = new Bar; 
     } 
     return $this->bar; 
    } 
} 

Chạy PHPLoc về vấn đề này sẽ cung cấp cho bạn một tốt hơn nhiều Cyclomatic phức tạp

Lines of Code (LOC):         11 
    Cyclomatic Complexity/Lines of Code:   0.09 
    Cyclomatic Complexity/Number of Methods:  2.00 

Và điều này sẽ ở lại 2 cho mỗi đồng bằng thêm cũ Getter bạn thêm vào. Ngoài ra, hãy lưu ý rằng khi bạn muốn sử dụng các kiểu phụ của biến thể, bạn sẽ phải quá tải __get và sao chép và dán toàn bộ khối chuyển đổi/trường hợp để thực hiện thay đổi, trong khi với Getter cũ đơn giản bạn chỉ cần quá tải Getters bạn cần phải thay đổi. Có, đó là công việc đánh máy nhiều hơn để thêm tất cả các Getters, nhưng nó cũng đơn giản hơn nhiều và cuối cùng sẽ dẫn đến mã duy trì nhiều hơn và cũng có lợi ích của việc cung cấp cho bạn một API rõ ràng, dẫn chúng tôi đến tuyên bố khác của bạn

Tôi đặc biệt muốn API của mình bị "cách ly"; Tài liệu hướng dẫn nên cho người khác biết cách sử dụng: P

Tôi không biết ý của bạn là gì "bị cô lập" nhưng nếu API của bạn không thể diễn tả nó là gì. Nếu tôi phải đọc tài liệu của bạn bởi vì API của bạn không cho tôi biết làm thế nào tôi có thể giao tiếp với nó bằng cách nhìn vào nó, bạn đang làm điều đó sai. Bạn đang obfuscating mã. Khai báo các thuộc tính trong một mảng thay vì khai báo chúng ở cấp lớp (nơi chúng thuộc về) buộc bạn viết tài liệu cho nó, đó là công việc bổ sung và thừa. Tốt mã là dễ đọc và tự tài liệu. Cân nhắc mua cuốn sách "Clean Code" của Robert Martin.

Với những gì đã nói, khi bạn nói

lý do duy nhất là vẻ đẹp api;

thì tôi nói: sau đó không sử dụng __get vì nó sẽ có tác dụng ngược lại. Nó sẽ làm cho API xấu xí.Magic là phức tạp và không rõ ràng và đó là chính xác những gì dẫn đến những khoảnh khắc WTF:

Code Quality: WTF per minute

Để chấm dứt bây giờ:

tôi không thấy bất kỳ khó khăn thật, tôi đoán nó có giá trị nó

Bạn hy vọng sẽ thấy chúng ngay bây giờ. Nó không đáng.

Đối với phương pháp bổ sung cho Lazy tải, xem various Lazy Loading patterns from Martin Fowler's PoEAA:

Có bốn loại chính của tải lười biếng. Khởi tạo lười biếng sử dụng một giá trị đánh dấu đặc biệt (thường là null) để biểu thị trường không được tải. Mọi truy cập vào trường sẽ kiểm tra trường cho giá trị điểm đánh dấu và nếu được tải, hãy tải trường đó. Proxy ảo là một đối tượng có cùng giao diện với đối tượng thực. Lần đầu tiên một trong các phương thức của nó được gọi là nó tải đối tượng thực và sau đó là các đại biểu. Chủ giá trị là một đối tượng có phương thức getValue. Khách hàng gọi getValue để nhận đối tượng thực, cuộc gọi đầu tiên kích hoạt tải. Một ma là đối tượng thực mà không có bất kỳ dữ liệu nào. Lần đầu tiên bạn gọi một phương thức con ma sẽ tải toàn bộ dữ liệu vào các trường của nó.

Những cách tiếp cận này hơi khác biệt và có nhiều sự cân bằng. Bạn cũng có thể sử dụng phương pháp kết hợp. Cuốn sách chứa đầy đủ các cuộc thảo luận và ví dụ.

+0

Tôi phải thừa nhận rằng bạn đúng về hai điểm đó, nhưng liên quan đến tốc độ mà tôi nghĩ rằng Zend Debugger đang nói dối1! – Alex

+0

Bạn nghĩ gì về việc tạo thuộc tính trong lệnh gọi __get đầu tiên? :) – Alex

+2

@ Alex Tôi không thích nó. Nếu tôi cần biết những thuộc tính nào mà lớp đó định nghĩa tôi sẽ phải thu thập nó từ các phương thức của phương thức thay vì chỉ nhìn trên đầu trang của khai báo lớp. Ngoài ra, các thuộc tính sẽ được công khai khi bạn khai báo chúng một cách nhanh chóng. Bạn có thể thích Python nếu bạn muốn các công cụ như thế này :) – Gordon

2

Nếu viết hoa của bạn trong những cái tên lớp và tên chủ chốt trong $prop phù hợp, bạn có thể làm điều này:

class Dummy { 
    private $props = array(
     'Someobject' => false, 
     //etc... 
    ); 

    function __get($name){ 
     if(isset($this->props[$name])){ 
      if(!($this->props[$name] instanceof $name)) { 
       $this->props[$name] = new $name(); 
      } 

      return $this->props[$name]; 
     } 

     //trigger_error('property doesnt exist'); 
     //Make exceptions, not war 
     throw new Exception('Property doesn\'t exist');  
    } 
} 

Và ngay cả khi giá trị vốn hóa Nếu chữ cái đầu tiên luôn được viết hoa, bạn có thể sử dụng ucfirst() để lấy tên lớp.


EDIT

Có lẽ tốt hơn là nên sử dụng các phương thức đơn giản. Có một switch bên trong một getter, đặc biệt là khi code được thực hiện cho mỗi thứ bạn cố gắng nhận được là khác nhau, thực tế đánh bại mục đích của getter, để giúp bạn tránh phải lặp lại code. Sử dụng cách tiếp cận đơn giản:

class Something { 
    private $props = array('Someobject' => false); 

    public getSomeobject() { 
     if(!($this->props['Someobject'] instanceof Someobject)) { 
      //Instantiate and do extra stuff 
     } 

     return $this->props['Someobject']; 
    } 

    public getSomeOtherObject() { 
     //etc.. 
    } 
} 
+0

nó không theo cùng một mẫu. Trong một số câu, tôi cần phải làm nhiều hơn là chỉ làm nhanh lớp học ... – Alex

+0

@Alex Có lẽ tốt hơn là chỉ cần sử dụng các phương thức đơn giản sau đó. –

1

Sử dụng __get() được cho là lần truy cập hiệu suất. Do đó, nếu danh sách các tham số của bạn là tĩnh/cố định và không quá dài, nó sẽ hiệu quả hơn để tạo ra các phương thức cho mỗi và bỏ qua __get(). Ví dụ:

public function someobject() { 
    if(!($this->props[$name] instanceof Someobject)) 
     $this->props[$name] = new Someobject; 
     // do stuff to initialize someobject 
    } 
    if (count($argv = func_get_args())) { 
     // do stuff to SET someobject from $a[0] 
    } 
    return $this->props['someobject']; 
} 

Để tránh những phương pháp kỳ diệu, bạn sẽ phải thay đổi cách bạn sử dụng nó như

$bar = $foo->someobject; // this won't work without __get() 
$bar = $foo->someobject(); // use this instead 

$foo->someobject($bar); // this is how you would set without __set() 

EDIT này

Chỉnh sửa, như Alex chỉ ra, hiệu suất hit là mili giây nhỏ. Bạn có thể thử cả hai cách và thực hiện một số điểm chuẩn hoặc chỉ cần đi với __get vì nó không có khả năng có tác động đáng kể đến ứng dụng của bạn.

+0

đây là điều .. Tôi không thấy rằng hiệu suất hit. Sử dụng xdebug và wincache xay và thời gian tối đa tôi thấy cho các cuộc gọi __get là 2ms, hầu hết trong số họ có 0,1ms – Alex

+0

Vâng, đó là điều về ứng dụng web, hầu hết các lần truy cập hiệu suất là không đáng kể, và do đó không ** thực sự ** mối quan tâm trừ khi bạn là facebook. Điều đó nói rằng, tôi sử dụng __get trong dự án của tôi anyway. – Umbrella

+0

Điều kỳ lạ hơn là tôi đã tạo một phương thức bình thường như GetSomeObject(), và khi gọi nó phải mất một khoảng thời gian giống như phiên bản __get - 0.2ms – Alex

2

Tôi đang sử dụng __get() để làm cho một số thuộc tính của tôi "động" (chỉ khởi tạo chúng khi được yêu cầu). Các thuộc tính "giả" này được lưu trữ bên trong một thuộc tính mảng riêng tư, mà tôi đang kiểm tra bên trong __get.

Dù sao, bạn có nghĩ rằng tốt hơn nên tạo phương pháp cho mỗi trong số các proprties này thay vì làm nó trong một tuyên bố chuyển đổi?

Cách bạn đặt câu hỏi của mình Tôi không nghĩ đó là thực sự về những gì mọi người nghĩ. Để nói về những suy nghĩ, trước hết phải rõ ràng vấn đề bạn muốn giải quyết ở đây.

Cả ma thuật _get cũng như thông thường getter methods giúp cung cấp giá trị. Tuy nhiên, những gì bạn không thể làm trong PHP là tạo một thuộc tính chỉ đọc.

Nếu bạn cần có thuộc tính chỉ đọc, bạn chỉ có thể thực hiện điều đó với chức năng ma thuật _get trong PHP cho đến thời điểm này (thay thế bằng RFC).

Nếu bạn đồng ý với các phương pháp truy cập và bạn quan tâm đến việc nhập mã của phương thức, hãy sử dụng IDE tốt hơn cho bạn nếu bạn thực sự quan tâm đến khía cạnh viết đó.

Nếu các thuộc tính đó không cần phải cụ thể, bạn có thể giữ chúng động, vì giao diện cụ thể hơn sẽ là chi tiết vô dụng và chỉ làm cho mã của bạn phức tạp hơn cần thiết và do đó vi phạm nguyên tắc thiết kế OO phổ biến.

Tuy nhiên, năng động hoặc phép thuật cũng có thể là dấu hiệu cho thấy bạn làm điều gì đó sai. Và cũng khó để gỡ lỗi. Vì vậy, bạn thực sự cần biết những gì bạn đang làm.Điều đó cần thiết mà bạn làm cho vấn đề bạn muốn giải quyết cụ thể hơn bởi vì điều này phụ thuộc rất nhiều vào loại đối tượng.

Và tốc độ là điều bạn không nên thử nghiệm riêng biệt, nó không đưa ra đề xuất tốt cho bạn. Tốc độ trong câu hỏi của bạn nghe giống như một loại thuốc;) nhưng uống thuốc đó sẽ không cung cấp cho bạn sức mạnh để quyết định một cách khôn ngoan.

Các vấn đề liên quan