PHP로 작성된 템플릿 메서드
템플릿 메서드는 기초 클래스에서 알고리즘의 골격을 정의할 수 있도록 하는 행동 디자인 패턴입니다. 또 이 패턴은 자식 클래스들이 전체 알고리즘의 구조를 변경하지 않고도 기본 알고리즘의 단계들을 오버라이드할 수 있도록 합니다.
복잡도:
인기도:
사용 사례들: 템플릿 메서드 패턴은 PHP 프레임워크들에서 매우 일반적입니다. 이 패턴은 클래스 상속을 사용하여 기초 프레임워크의 행동을 확장하는 작업을 단순화합니다.
식별: 템플릿 메서드는 기초 클래스에 추상적이거나 비어 있는 다른 여러 메서드들을 호출하는 메서드가 있습니다.
개념적인 예시
이 예시는 템플릿 메서드의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 PHP 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
index.php: 개념적인 예시
<?php namespace RefactoringGuru\TemplateMethod\Conceptual; /** * The Abstract Class defines a template method that contains a skeleton of some * algorithm, composed of calls to (usually) abstract primitive operations. * * Concrete subclasses should implement these operations, but leave the template * method itself intact. */ abstract class AbstractClass { /** * The template method defines the skeleton of an algorithm. */ final public function templateMethod(): void { $this->baseOperation1(); $this->requiredOperations1(); $this->baseOperation2(); $this->hook1(); $this->requiredOperation2(); $this->baseOperation3(); $this->hook2(); } /** * These operations already have implementations. */ protected function baseOperation1(): void { echo "AbstractClass says: I am doing the bulk of the work\n"; } protected function baseOperation2(): void { echo "AbstractClass says: But I let subclasses override some operations\n"; } protected function baseOperation3(): void { echo "AbstractClass says: But I am doing the bulk of the work anyway\n"; } /** * These operations have to be implemented in subclasses. */ abstract protected function requiredOperations1(): void; abstract protected function requiredOperation2(): void; /** * These are "hooks." Subclasses may override them, but it's not mandatory * since the hooks already have default (but empty) implementation. Hooks * provide additional extension points in some crucial places of the * algorithm. */ protected function hook1(): void { } protected function hook2(): void { } } /** * Concrete classes have to implement all abstract operations of the base class. * They can also override some operations with a default implementation. */ class ConcreteClass1 extends AbstractClass { protected function requiredOperations1(): void { echo "ConcreteClass1 says: Implemented Operation1\n"; } protected function requiredOperation2(): void { echo "ConcreteClass1 says: Implemented Operation2\n"; } } /** * Usually, concrete classes override only a fraction of base class' operations. */ class ConcreteClass2 extends AbstractClass { protected function requiredOperations1(): void { echo "ConcreteClass2 says: Implemented Operation1\n"; } protected function requiredOperation2(): void { echo "ConcreteClass2 says: Implemented Operation2\n"; } protected function hook1(): void { echo "ConcreteClass2 says: Overridden Hook1\n"; } } /** * The client code calls the template method to execute the algorithm. Client * code does not have to know the concrete class of an object it works with, as * long as it works with objects through the interface of their base class. */ function clientCode(AbstractClass $class) { // ... $class->templateMethod(); // ... } echo "Same client code can work with different subclasses:\n"; clientCode(new ConcreteClass1()); echo "\n"; echo "Same client code can work with different subclasses:\n"; clientCode(new ConcreteClass2()); Output.txt: 실행 결과
Same client code can work with different subclasses: AbstractClass says: I am doing bulk of the work ConcreteClass1 says: Implemented Operation1 AbstractClass says: But I let subclasses to override some operations ConcreteClass1 says: Implemented Operation2 AbstractClass says: But I am doing bulk of the work anyway Same client code can work with different subclasses: AbstractClass says: I am doing bulk of the work ConcreteClass2 says: Implemented Operation1 AbstractClass says: But I let subclasses to override some operations ConcreteClass2 says: Overridden Hook1 ConcreteClass2 says: Implemented Operation2 AbstractClass says: But I am doing bulk of the work anyway 실제 사례 예시
이 예시에서 템플릿 메서드 패턴은 소셜 네트워크에 메시지를 게시하는 알고리즘의 골격을 정의합니다. 각 자식 클래스는 별도의 소셜 네트워크를 나타내며 모든 단계를 다르게 구현하나 기초 알고리즘을 재사용합니다.
index.php: 실제 사례 예시
<?php namespace RefactoringGuru\TemplateMethod\RealWorld; /** * The Abstract Class defines the template method and declares all its steps. */ abstract class SocialNetwork { protected $username; protected $password; public function __construct(string $username, string $password) { $this->username = $username; $this->password = $password; } /** * The actual template method calls abstract steps in a specific order. A * subclass may implement all of the steps, allowing this method to actually * post something to a social network. */ public function post(string $message): bool { // Authenticate before posting. Every network uses a different // authentication method. if ($this->logIn($this->username, $this->password)) { // Send the post data. All networks have different APIs. $result = $this->sendData($message); // ... $this->logOut(); return $result; } return false; } /** * The steps are declared abstract to force the subclasses to implement them * all. */ abstract public function logIn(string $userName, string $password): bool; abstract public function sendData(string $message): bool; abstract public function logOut(): void; } /** * This Concrete Class implements the Facebook API (all right, it pretends to). */ class Facebook extends SocialNetwork { public function logIn(string $userName, string $password): bool { echo "\nChecking user's credentials...\n"; echo "Name: " . $this->username . "\n"; echo "Password: " . str_repeat("*", strlen($this->password)) . "\n"; simulateNetworkLatency(); echo "\n\nFacebook: '" . $this->username . "' has logged in successfully.\n"; return true; } public function sendData(string $message): bool { echo "Facebook: '" . $this->username . "' has posted '" . $message . "'.\n"; return true; } public function logOut(): void { echo "Facebook: '" . $this->username . "' has been logged out.\n"; } } /** * This Concrete Class implements the Twitter API. */ class Twitter extends SocialNetwork { public function logIn(string $userName, string $password): bool { echo "\nChecking user's credentials...\n"; echo "Name: " . $this->username . "\n"; echo "Password: " . str_repeat("*", strlen($this->password)) . "\n"; simulateNetworkLatency(); echo "\n\nTwitter: '" . $this->username . "' has logged in successfully.\n"; return true; } public function sendData(string $message): bool { echo "Twitter: '" . $this->username . "' has posted '" . $message . "'.\n"; return true; } public function logOut(): void { echo "Twitter: '" . $this->username . "' has been logged out.\n"; } } /** * A little helper function that makes waiting times feel real. */ function simulateNetworkLatency() { $i = 0; while ($i < 5) { echo "."; sleep(1); $i++; } } /** * The client code. */ echo "Username: \n"; $username = readline(); echo "Password: \n"; $password = readline(); echo "Message: \n"; $message = readline(); echo "\nChoose the social network to post the message:\n" . "1 - Facebook\n" . "2 - Twitter\n"; $choice = readline(); // Now, let's create a proper social network object and send the message. if ($choice == 1) { $network = new Facebook($username, $password); } elseif ($choice == 2) { $network = new Twitter($username, $password); } else { die("Sorry, I'm not sure what you mean by that.\n"); } $network->post($message); Output.txt: 실행 결과
Username: > neo Password: > 123123 Message: > What is the Matrix? Choose the social network to post the message: 1 - Facebook 2 - Twitter > 1 Checking user's credentials... Name: neo Password: ****** ..... Facebook: 'neo' has logged in successfully. Facebook: 'neo' has posted 'What is the Matrix?'. Facebook: 'neo' has been logged out.