수색…


수평 코드 재사용을 용이하게하는 특성

로깅을위한 인터페이스가 있다고 가정 해 보겠습니다.

interface Logger {
    function log($message);
}

이제 우리는 Logger 인터페이스의 두 가지 구현 인 FileLoggerConsoleLogger 가지고 있다고 가정 해 보겠습니다.

class FileLogger implements Logger {
    public function log($message) {
        // Append log message to some file
    }
}

class ConsoleLogger implements Logger {
    public function log($message) {
        // Log message to the console
    }
}

이제 로깅 작업을 수행 할 수있는 다른 클래스 Foo 를 정의하면 다음과 같이 할 수 있습니다.

class Foo implements Logger {
    private $logger;

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }

    public function log($message) {
        if ($this->logger) {
            $this->logger->log($message);
        }
    }
}

Foo 는 이제 Logger 이기도하지만, 그 기능은 setLogger() 를 통해 전달되는 Logger 구현에 따라 다릅니다. Bar 클래스에이 로깅 메커니즘을 사용하려면 Bar 클래스에서이 논리를 복제해야합니다.

코드를 복제하는 대신 특성을 정의 할 수 있습니다.

trait LoggableTrait {
    protected $logger;

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }

    public function log($message) {
        if ($this->logger) {
            $this->logger->log($message);
        }
    }
}

특성에서 논리를 정의 했으므로 특성을 사용하여 논리를 FooBar 클래스에 추가 할 수 있습니다.

class Foo {
    use LoggableTrait;
}

class Bar {
    use LoggableTrait;
}

예를 들어 다음과 같이 Foo 클래스를 사용할 수 있습니다.

$foo = new Foo();
$foo->setLogger( new FileLogger() );

//note how we use the trait as a 'proxy' to call the Logger's log method on the Foo instance
$foo->log('my beautiful message'); 

갈등 해결

하나의 클래스에 여러 특성을 사용하려고하면 충돌하는 메서드가 관련된 문제가 발생할 수 있습니다. 이러한 충돌을 수동으로 해결해야합니다.

예를 들어 다음과 같은 계층 구조를 만듭니다.

trait MeowTrait {
    public function say() {
        print "Meow \n";
    }
}

trait WoofTrait {
    public function say() {
        print "Woof \n";
    }
}

abstract class UnMuteAnimals {
    abstract function say();
}

class Dog extends UnMuteAnimals {
    use WoofTrait;
}

class Cat extends UnMuteAnimals {
    use MeowTrait;
}

이제 다음 클래스를 만들어 보겠습니다.

class TalkingParrot extends UnMuteAnimals {
    use MeowTrait, WoofTrait;
}

PHP 인터프리터는 치명적인 오류를 반환합니다.

치명적인 오류 : TalkingParrot의 다른 특성 메서드와 충돌이 발생하여 특성 메서드가 적용되지 않았습니다.

이 충돌을 해결하기 위해 다음과 같이 할 수 있습니다.

  • 키워드를 사용 insteadof 다른 특성에서 대신 방법의 하나 개의 특성에서 방법을 사용하여
  • WoofTrait::say as sayAsDog; 와 같은 WoofTrait::say as sayAsDog; 같은 WoofTrait::say as sayAsDog; 을 사용하여 메서드의 별칭을 만듭니다 WoofTrait::say as sayAsDog;
class TalkingParrotV2 extends UnMuteAnimals {
    use MeowTrait, WoofTrait {
        MeowTrait::say insteadof WoofTrait;
        WoofTrait::say as sayAsDog;
    }
}

$talkingParrot = new TalkingParrotV2();
$talkingParrot->say();
$talkingParrot->sayAsDog();

이 코드는 다음 출력을 생성합니다.

야옹

다중 특성 사용

trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();

위의 예는 다음과 같이 출력됩니다.

Hello World!

변경 방법 가시성

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Change visibility of sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}

이 예제 실행 :

(new MyClass1())->sayHello();
// Fatal error: Uncaught Error: Call to protected method MyClass1::sayHello()

(new MyClass2())->myPrivateHello();
// Fatal error: Uncaught Error: Call to private method MyClass2::myPrivateHello()

(new MyClass2())->sayHello();
// Hello World!

따라서 MyClass2 의 마지막 예제에서는 trait HelloWorld 에서 원래의 앨리어싱 제거 된 메서드가 그대로 액세스 가능 상태로 유지된다는 점에 유의하십시오.

형질이란 무엇입니까?

PHP는 단일 상속 만 허용합니다. 즉, 클래스는 하나의 다른 클래스 만 extend 할 수 있습니다. 그러나 부모 클래스에 속하지 않은 것을 포함해야한다면 어떻게해야할까요? PHP 5.4 이전에는 창의력을 발휘해야했지만 5.4에서는 특성이 도입되었습니다. 특성을 사용하여 기본적으로 클래스의 일부를 기본 클래스에 "복사하여 붙여 넣기"할 수 있습니다

trait Talk {
    /** @var string */
    public $phrase = 'Well Wilbur...';
    public function speak() {
         echo $this->phrase;
    }
}

class MrEd extends Horse {
    use Talk;
    public function __construct() {
         $this->speak();
    }

    public function setPhrase($phrase) {
         $this->phrase = $phrase;
    }
}

그래서 여기에 우리는 이미 Horse 연장하고있는 MrEd 가 있습니다. 그러나 모든 말 Talk , 그래서 우리는 그의 형질을 가지고있다. 이것이 무엇을하고 있는지 주목 해 봅시다.

첫째, 우리는 우리의 특성을 정의합니다. 자동 로딩과 네임 스페이스와 함께 사용할 수 있습니다 (네임 스페이스 에서 클래스 또는 함수 참조하기 참조 ). 그런 다음 우리는 MrEd 클래스에 키워드 use 시킵니다.

MrEdTalk 함수와 변수를 정의하지 않고 사용하는 데주의를 MrEd 것입니다. 복사 및 붙여 넣기 에 관해 우리가 말한 것을 기억하십니까? 이 함수와 변수는 모두이 클래스가 정의한 것처럼 지금 클래스 내에서 모두 정의됩니다.

특성은 변수와 함수를 정의 할 수 있다는 점에서 추상 클래스 와 가장 밀접하게 관련됩니다. 또한 특성을 직접 인스턴스화 할 수 없습니다 (예 : new Trait() ). 특성은 클래스가 추상 클래스 나 인터페이스처럼 함수를 암시 적으로 정의하도록 강제 할 수 없습니다. (당신이 할 수 있기 때문에 형질은 명시 적 정의를위한 implement 당신이보고 원하는만큼 인터페이스 인터페이스 ).

특성은 언제 사용해야합니까?

특성을 고려할 때 가장 먼저해야 할 일은이 중요한 질문을 스스로에게하는 것입니다

내 코드를 재구성하여 특성 사용을 피할 수 있습니까?

종종 대답은 ' 예' 가 될 것입니다. 형질은 단일 상속으로 인한 엣지 케이스입니다. 형질을 남용하거나 남용하려는 유혹은 높을 수 있습니다. 그러나 Trait이 코드에 대한 또 다른 소스를 소개한다고 생각해보십시오. 즉, 또 다른 복잡성 계층이 있음을 의미합니다. 이 예제에서는 3 개의 클래스 만 처리합니다. 그러나 특성은 이제 당신이 그 이상을 처리 할 수 ​​있다는 것을 의미합니다. 각 특성에 대해 다루는 것이 훨씬 어려워집니다. 이제는 각 특성을 참조하여 정의 된 내용 (충돌이 발생한 위치, 충돌 해결 참조)을 찾아야합니다. 이상적으로 가능한 한 코드에 특성을 거의 유지하지 않아야합니다.

수업을 깨끗하게 유지하는 특성

시간이 지남에 따라 우리 클래스는 점점 더 많은 인터페이스를 구현할 수 있습니다. 이러한 인터페이스에 많은 메소드가있는 경우 클래스의 메소드 수가 매우 커집니다.

예를 들어 두 개의 인터페이스와이를 구현하는 클래스가 있다고 가정 해 보겠습니다.

interface Printable {
    public function print();   
    //other interface methods...
}

interface Cacheable {
    //interface methods
}

class Article implements Cachable, Printable {  
    //here we must implement all the interface methods
    public function print(){ {
        /* code to print the article */ 
    }
}

Article 클래스 안에 모든 인터페이스 메소드를 구현하는 대신 별도의 Trait을 사용하여 인터페이스를 구현 하고 클래스를 작게 유지하고 인터페이스 구현 코드를 클래스와 분리 할 수 ​​있습니다.

예를 들어 Printable 인터페이스를 구현하기 위해 다음과 같은 특성을 만들 수 있습니다.

trait PrintableArticle {
    //implements here the interface methods
    public function print() {
        /* code to print the article */ 
    }
}

그리고 그 반원이 그 특성을 사용하게한다.

class Article implements Cachable, Printable {
    use PrintableArticle;
    use CacheableArticle; 
} 

주요 이점은 인터페이스 구현 방법이 나머지 클래스와 분리되어 특정 유형의 객체에 대한 인터페이스를 구현할 단독 책임 이있는 특성에 저장 된다는 것입니다.

특성을 사용하여 싱글 톤 구현하기

면책 조항 : 이 예제는 싱글 톤의 사용을 옹호하지 않습니다. 싱글 톤은 많은주의를 기울여 사용해야합니다.

PHP에는 싱글 톤을 구현하는 표준적인 방법이 있습니다.

public class Singleton {
    private $instance;

    private function __construct() { };

    public function getInstance() {
        if (!self::$instance) {
            // new self() is 'basically' equivalent to new Singleton()
            self::$instance = new self();
        }

        return self::$instance;
    }

    // Prevent cloning of the instance
    protected function __clone() { }

    // Prevent serialization of the instance
    protected function __sleep() { }

    // Prevent deserialization of the instance
    protected function __wakeup() { }
}

코드 중복을 방지하려면이 동작을 특성으로 추출하는 것이 좋습니다.

trait SingletonTrait {
    private $instance;

    protected function __construct() { };

    public function getInstance() {
        if (!self::$instance) {
            // new self() will refer to the class that uses the trait
            self::$instance = new self();
        }

        return self::$instance;
    }

    protected function __clone() { }
    protected function __sleep() { }
    protected function __wakeup() { }
}

이제 싱글 톤으로 기능하고자하는 클래스는 단순히 특성을 사용할 수 있습니다.

class MyClass {
    use SingletonTrait;
}

// Error! Constructor is not publicly accessible
$myClass = new MyClass();

$myClass = MyClass::getInstance();

// All calls below will fail due to method visibility
$myClassCopy = clone $myClass; // Error!
$serializedMyClass = serialize($myClass); // Error!
$myClass = deserialize($serializedMyclass); // Error!

아직 싱글 톤을 직렬화하는 것은 불가능하지만 deserialize 메서드도 허용하지 않는 것이 유용합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow