수색…
수평 코드 재사용을 용이하게하는 특성
로깅을위한 인터페이스가 있다고 가정 해 보겠습니다.
interface Logger {
function log($message);
}
이제 우리는 Logger
인터페이스의 두 가지 구현 인 FileLogger
와 ConsoleLogger
가지고 있다고 가정 해 보겠습니다.
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);
}
}
}
특성에서 논리를 정의 했으므로 특성을 사용하여 논리를 Foo
및 Bar
클래스에 추가 할 수 있습니다.
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
시킵니다.
MrEd
는 Talk
함수와 변수를 정의하지 않고 사용하는 데주의를 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 메서드도 허용하지 않는 것이 유용합니다.