Ejemplo conceptual
Este ejemplo ilustra la estructura del patrón de diseño Template Method y se centra en las siguientes preguntas:
¿De qué clases se compone?
¿Qué papeles juegan esas clases?
¿De qué forma se relacionan los elementos del patrón?
Después de conocer la estructura del patrón, será más fácil comprender el siguiente ejemplo basado en un caso de uso real de PHP.
index.php: Ejemplo conceptual
<?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: Resultado de la ejecución
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
Ejemplo del mundo real
En este ejemplo, el patrón Template Method define el esqueleto del algoritmo de publicación de mensajes en redes sociales. Cada subclase representa una red social separada e implementa todos los pasos de forma diferente, pero reutiliza el algoritmo base.
index.php: Ejemplo del mundo real
<?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: Resultado de la ejecución
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.