2010-07-28 37 views
18

Tôi thích cách trong C#, nơi bạn có thể viết một phương pháp khuyến nông, và sau đó làm những việc như thế này:C# giống như các phương pháp mở rộng trong PHP?

string ourString = "hello"; 
ourString.MyExtension("another"); 

hoặc thậm chí

"hello".MyExtention("another"); 

Có cách nào để có hành vi tương tự trong PHP?

+5

Strings không đối tượng trong PHP. –

+2

Tôi xin lỗi, "phương pháp khuyến nông" là gì? nó khác với các phương pháp khác như thế nào? – Artefacto

+1

@Artefacto: "hello" .MyExtention ("another"); 'không giải thích được? – caesay

Trả lời

9

Bạn có thể nếu bạn reimplemented tất cả các chuỗi của bạn như là đối tượng.

class MyString { 
    ... 
    function foo() { ... } 
} 

$str = new MyString('Bar'); 
$str->foo('baz'); 

Nhưng bạn thực sự không muốn làm điều này. PHP đơn giản không phải là một ngôn ngữ hướng đối tượng ở lõi của nó, các chuỗi chỉ là các kiểu nguyên thủy và không có phương thức nào.

Cú pháp 'Bar'->foo('baz') không thể đạt được trong PHP mà không mở rộng công cụ lõi (không phải là thứ bạn muốn nhận vào, ít nhất là không vì mục đích này :)).


Cũng không có gì về việc mở rộng chức năng của đối tượng làm cho nó tốt hơn là viết một hàm mới chấp nhận nguyên thủy. Nói cách khác, PHP tương đương với

"hello".MyExtention("another"); 

my_extension("hello", "another"); 

Đối với tất cả ý định và mục đích nó có chức năng tương tự, chỉ cần cú pháp khác nhau.

+10

Vâng, các nhà thiết kế PHP thực sự thích phương pháp 'my_extension()' và đây là [** kết quả đáng yêu **] (http://www.php.net/manual/en/indexes.functions.php):) – cvsguimaraes

+0

Chúc may mắn chuỗi rất nhiều phương pháp. Trong C# bạn có thể làm "test" .ToUpper() ToLower.Split(). Join(). Trim(). Bây giờ tôi thậm chí sẽ không bận tâm viết mã PHP để làm điều đó. – Phiter

+0

@Phiter Đó chỉ là sự khác biệt giữa các ngôn ngữ OO và các ngôn ngữ không phải của OO. Chủ yếu là trong các ngôn ngữ không phải OO, bạn viết nó "từ trong ra ngoài" ('cắt (nối (...))') thay vì trong một kiểu xích. Tôi không bình luận về những gì tốt hơn hoặc tệ hơn, nó chỉ là cách nó được. – deceze

4

Vì PHP 5.4 có traits có thể được sử dụng làm phương pháp mở rộng.

Ví dụ:

<?php 
trait HelloWorld { 
    public function sayHelloWorld() { 
     echo 'Hello World'; 
    } 
} 

class MyHelloWorld { 
    use HelloWorld; 
    public function sayExclamationMark() { 
     echo '!'; 
    } 
} 

$o = new MyHelloWorld(); 
$o->sayHelloWorld(); 
$o->sayExclamationMark(); 
?> 

kết quả:

Hello World! 

Khi Bạn có một đặc điểm về một lớp, cho phép gọi nó là ví dụ với một tên Extension, Bạn có thể thêm bất kỳ phương pháp Bạn muốn và xác định vị trí ở nơi khác. Sau đó, trong ví dụ đó, use Extension trở thành trang trí một lần cho các lớp có thể mở rộng.

+13

Các đặc điểm không giống như các phương pháp mở rộng vì chúng vẫn yêu cầu sửa đổi lớp gốc. – zzzzBov

+0

Có nhưng một khi Bạn bao gồm một đặc điểm, hãy gọi nó bằng tên "Phần mở rộng", Bạn có thể thêm bất kỳ phương pháp nào bạn muốn và định vị chúng ở nơi khác. Sau đó, "sử dụng TraitName" trở thành trang trí chỉ một lần. –

4

Di chuyển ra khỏi vấn đề nguyên thủy phi đối tượng trong PHP, khi giao dịch với các lớp học thực tế trong PHP, nếu môi trường của bạn là lành mạnh, rất có thể là bạn có thể trang trí một lớp cho trước để sắp xếp-of § mở rộng thi đua phương pháp.

Với một giao diện và thực hiện:

interface FooInterface { 
    function sayHello(); 
    function sayWorld(); 
} 

class Foo implements FooInterface { 
    public function sayHello() { 
     echo 'Hello'; 
    } 
    public function sayWorld() { 
     echo 'World'; 
    } 
} 

Chừng nào phụ thuộc vào Foo thực sự phụ thuộc vào giao diện FooInterface (đây là những gì tôi muốn nói về lành mạnh), bạn có thể thực hiện FooInterface mình là một wrapper để Foo, chuyển tiếp cuộc gọi đến Foo khi cần thiết, và thêm các phương pháp bổ sung mà bạn cần:

class DecoratedFoo implements FooInterface { 
    private $foo; 
    public function __construct(FooInterface $foo) { 
     $this->foo = $foo; 
    } 
    public function sayHello() { 
     $this->foo->sayHello(); 
    } 
    public function sayWorld() { 
     $this->foo->sayWorld(); 
    } 
    public function sayDanke() { 
     echo 'Danke'; 
    } 
    public function sayShoen() { 
     echo 'Shoen'; 
    } 
} 

Các phương pháp sayHello()sayWorld() được vá qua đối tượng có chứa Foo, tuy nhiên chúng tôi đã thêm sayDanke()sayShoen().

Sau đây:

function acceptsFooInterface(FooInterface $foo) { 
    $foo->sayHello(); 
    $foo->sayWorld(); 
} 

$foo = new Foo(); 
acceptsFooInterface($foo); 

trình như mong đợi, năng suất HelloWorld; nhưng thực hiện như vậy:

$decoratedFoo = new DecoratedFoo($foo); 
acceptsFooInterface($decoratedFoo); 

$decoratedFoo->sayDanke(); 
$decoratedFoo->sayShoen(); 

Kết quả nào trong HelloWorldDankeShoen.

Đây là một hạn chế sử dụng tiềm năng trong mẫu trang trí; bạn có thể sửa đổi hành vi của các phương thức đã triển khai hoặc đơn giản là không chuyển tiếp chúng (tuy nhiên, chúng tôi muốn duy trì hành vi dự định theo định nghĩa lớp gốc trong ví dụ này)

Đây có phải là một-một giải pháp để triển khai các phương thức mở rộng (theo C#) trong PHP? Không, chắc chắn là không; nhưng khả năng mở rộng của phương pháp này sẽ giúp giải quyết vấn đề một cách lỏng lẻo hơn.


§ đặn Tôi muốn xây dựng dựa trên một số hội thoại trong chat của đối tượng: bạn sẽ không bao giờ lặp lại nó (không phải hôm nay, và có lẽ không ngày mai) trong PHP, tuy nhiên quan trọng trong việc câu trả lời của tôi là các mẫu thiết kế. Chúng cung cấp cơ hội để chuyển các chiến lược từ ngôn ngữ này sang ngôn ngữ khác khi bạn không nhất thiết phải (thông thường, hoặc bao giờ) tính năng cổng.

+1

+1 để lập trình cho giao diện. – rdlowrey

2

Tôi có một triển khai khác cho điều đó trong PHP> = 5.3.0 và giống như một Decorator mà Northborn Design đã giải thích.

Tất cả những gì chúng tôi cần là API để tạo tiện ích và trình trang trí để áp dụng các tiện ích. Chúng ta phải nhớ rằng trên phương pháp mở rộng C# chúng không phá vỡ sự đóng gói của đối tượng được mở rộng, và chúng không sửa đổi đối tượng (không có điểm nào cho điều đó, thay vào đó thực hiện phương thức sẽ hiệu quả hơn). Và các phương pháp mở rộng là tinh khiết tĩnh và họ nhận được trường hợp của các đối tượng, như trong ví dụ dưới đây (C#, from MSDN):

public static int WordCount(this String str) 
{ 
    return str.Split(new char[] { ' ', '.', '?' }, 
        StringSplitOptions.RemoveEmptyEntries).Length; 
} 

Dĩ nhiên chúng ta không có các đối tượng String trong PHP, nhưng đối với tất cả các đối tượng khác chúng ta có thể tạo ra các trang trí chung cho loại thuật sĩ đó.

Cho phép xem thi tôi cho rằng:

API:

<?php 

namespace Pattern\Extension; 

/** 
* API for extension methods in PHP (like C#). 
*/ 
class Extension 
{ 
    /** 
    * Apply extension to an instance. 
    * 
    * @param object $instance 
    * @return \Pattern\Extension\ExtensionWrapper 
    */ 
    public function __invoke($instance) 
    { 
     return Extension::apply($instance); 
    } 

    /** 
    * Apply extension to an instance. 
    * 
    * @param object $instance 
    * @return \Pattern\Extension\ExtensionWrapper 
    */ 
    public static function apply($instance) 
    { 
     return new ExtensionWrapper($instance, \get_called_class()); 
    } 

    /** 
    * @param mixed $instance 
    * @return boolean 
    */ 
    public static function isExtensible($instance) 
    { 
     return ($instance instanceof Extensible); 
    } 
} 
?> 

Các Decorator:

<?php 

namespace Pattern\Extension; 

/** 
* Demarcate decorators that resolve the extension. 
*/ 
interface Extensible 
{ 
    /** 
    * Verify the instance of the holded object. 
    * 
    * @param string $className 
    * @return bool true if the instance is of the type $className, false otherwise. 
    */ 
    public function holdsInstanceOf($className); 

    /** 
    * Returns the wrapped object. 
    * If the wrapped object is a Extensible the returns the unwrap of it and so on. 
    * 
    * @return mixed 
    */ 
    public function unwrap(); 

    /** 
    * Magic method for the extension methods. 
    * 
    * @param string $name 
    * @param array $args 
    * @return mixed 
    */ 
    public function __call($name, array $args); 
} 
?> 

Và việc thực hiện chung:

<?php 

namespace Pattern\Extension; 

/** 
* Generic version for the Extensible Interface. 
*/ 
final class ExtensionWrapper implements Extensible 
{ 
    /** 
    * @var mixed 
    */ 
    private $that; 

    /** 
    * @var Extension 
    */ 
    private $extension; 

    /** 
    * @param object $instance 
    * @param string | Extension $extensionClass 
    * @throws \InvalidArgumentException 
    */ 
    public function __construct($instance, $extensionClass) 
    { 
     if (!\is_object($instance)) { 
      throw new \InvalidArgumentException('ExtensionWrapper works only with objects.'); 
     } 

     $this->that = $instance; 
     $this->extension = $extensionClass; 
    } 

    /** 
    * {@inheritDoc} 
    * @see \Pattern\Extension\Extensible::__call() 
    */ 
    public function __call($name, array $args) 
    { 
     $call = null; 
     if (\method_exists($this->extension, '_'.$name)) { 
      // this is for abstract default interface implementation 
      \array_unshift($args, $this->unwrap()); 
      $call = array($this->extension, '_'.$name); 
     } elseif (\method_exists($this->extension, $name)) { 
      // this is for real implementations 
      \array_unshift($args, $this->unwrap()); 
      $call = array($this->extension, $name); 
     } else { 
      // this is for real call on object 
      $call = array($this->that, $name); 
     } 
     return \call_user_func_array($call, $args); 
    } 

    /** 
    * {@inheritDoc} 
    * @see \Pattern\Extension\Extensible::unwrap() 
    */ 
    public function unwrap() 
    { 
     return (Extension::isExtensible($this->that) ? $this->that->unwrap() : $this->that); 
    } 

    /** 
    * {@inheritDoc} 
    * @see \Pattern\Extension\Extensible::holdsInstanceof() 
    */ 
    public function holdsInstanceOf($className) 
    { 
     return \is_a($this->unwrap(), $className); 
    } 
} 
?> 

Sử dụng:

Giả sử một bữa tiệc hạng Ba tồn tại:

class ThirdPartyHello 
{ 
    public function sayHello() 
    { 
     return "Hello"; 
    } 
} 

Tạo mở rộng của bạn:

use Pattern\Extension\Extension; 

class HelloWorldExtension extends Extension 
{ 
    public static function sayHelloWorld(ThirdPartyHello $that) 
    { 
     return $that->sayHello().' World!'; 
    } 
} 

Plus: Đối với giao diện Lovers, tạo ra một Extension Tóm tắt:

<?php 
interface HelloInterfaceExtension 
{ 
    public function sayHelloFromInterface(); 
} 
?> 
<?php 
use Pattern\Extension\Extension; 

abstract class AbstractHelloExtension extends Extension implements HelloInterfaceExtension 
{ 
    public static function _sayHelloFromInterface(ThirdPartyOrLegacyClass $that) 
    { 
     return $that->sayHello(). ' from Hello Interface'; 
    } 
} 
?> 

Sau đó sử dụng nó :

//////////////////////////// 
// You can hide this snippet in a Dependency Injection method 

$thatClass = new ThirdPartyHello(); 

/** @var ThirdPartyHello|HelloWorldExtension $extension */ 
$extension = HelloWorldExtension::apply($thatClass); 

////////////////////////////////////////// 

$extension->sayHello(); // returns 'Hello' 
$extension->sayHelloWorld(); // returns 'Hello World!' 

////////////////////////////////////////// 
// Abstract extension 

$thatClass = new ThirdPartyHello(); 

/** @var ThirdPartyHello|HelloInterfaceExtension $extension */ 
$extension = AbstractHelloExtension::apply($instance); 

$extension->sayHello(); // returns 'Hello' 
$extension->sayHelloFromInterface(); // returns 'Hello from Hello Interface' 

Ưu điểm: cách

  • Rất tương tự của phương pháp # mở rộng C trong PHP;
  • Không thể kiểm tra trực tiếp một cá thể mở rộng như một thể hiện của đối tượng mở rộng, nhưng điều này là tốt đẹp vì nó an toàn hơn vì chúng ta có thể có những nơi mà thể hiện của bế tắc đó không được mở rộng;
  • Vì mục đích của một khuôn khổ để cải thiện sự nhanh nhẹn của nhóm bạn phải viết ít hơn;
  • Việc sử dụng mở rộng có vẻ là một phần của đối tượng nhưng nó chỉ được trang trí (có thể điều này rất thú vị đối với các nhóm phát triển nhanh nhưng xem xét trong tương lai việc thực hiện đối tượng mở rộng đó nếu có liên quan);
  • Bạn có thể sử dụng phương pháp tĩnh của tiện ích mở rộng trực tiếp để cải thiện hiệu suất nhưng làm như vậy bạn mất khả năng giả lập các phần mã của bạn (DI được chỉ định cao).

Nhược điểm:

  • Việc gia hạn phải được khai báo cho đối tượng, nó là không chỉ là một khẩu như trong C#, bạn phải "trang trí" thể hiện mong muốn để cung cấp cho phần mở rộng nó.
  • Không thể kiểm tra trực tiếp một cá thể tiện ích mở rộng làm phiên bản của đối tượng mở rộng, nhiều mã hơn để kiểm tra bằng cách sử dụng API;
  • Hạn chế hiệu suất do sử dụng các phương pháp ma thuật (nhưng khi thực hiện là cần thiết, chúng tôi thay đổi ngôn ngữ, tái tạo lõi, sử dụng khung công tác tối thiểu, lắp ráp nếu cần);

Dưới đây là một Gist cho Api rằng: https://gist.github.com/tennaito/9ab4331a4b837f836ccdee78ba58dff8

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