结构型

结构型处理的是类和对象的组成。利用接口或抽象类和方法,它们定义了组成对象的方法,进而获得新的功能。以下是我们归类为结构型的模式列表。

  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰器模式
  • 门面模式
  • 享元模式
  • 代理模式

{% hint style=”info” %} 有关结构型的更多信息,请参见https://en.wikipedia.org/wiki/Structural\_pattern。 {% endhint %}

适配器模式

适配器模式允许从另一个接口使用现有类的接口,基本上是通过将一个类的接口转换为另一个类所期望的接口,帮助两个不兼容的接口一起工作。

下面是一个适配器模式的实现例子。

  1. class Stripe {
  2. public function capturePayment($amount) {
  3. /* Implementation... */
  4. }
  5. public function authorizeOnlyPayment($amount) {
  6. /* Implementation... */
  7. }
  8. public function cancelAmount($amount) {
  9. /* Implementation... */
  10. }
  11. }
  12. interface PaymentService {
  13. public function capture($amount);
  14. public function authorize($amount);
  15. public function cancel($amount);
  16. }
  17. class StripePaymentServiceAdapter implements PaymentService {
  18. private $stripe;
  19. public function __construct(Stripe $stripe) {
  20. $this->stripe = $stripe;
  21. }
  22. public function capture($amount) {
  23. $this->stripe->capturePayment($amount);
  24. }
  25. public function authorize($amount) {
  26. $this->stripe->authorizeOnlyPayment($amount);
  27. }
  28. public function cancel($amount) {
  29. $this->stripe->cancelAmount($amount);
  30. }
  31. }
  32. // Client
  33. $stripe = new StripePaymentServiceAdapter(new Stripe());
  34. $stripe->authorize(49.99);
  35. $stripe->capture(19.99);
  36. $stripe->cancel(9.99);

我们首先创建一个具体的Stripe类。然后我们定义了PaymentService接口与一些基本的支付处理方法。StripePaymentServiceAdapter实现了PaymentService接口,提供了支付处理方法的具体实现。最后,客户端实例化StripePaymentServiceAdapter并执行支付处理方法。

桥接模式

当我们想把一个类或抽象从它的实现中解耦出来时,就会用到桥接模式,让它们都能独立地改变。当类和它的实现经常变化时,这很有用。

下面是一个桥模式实现的例子。

  1. interface MailerInterface {
  2. public function setSender(MessagingInterface $sender);
  3. public function send($body);
  4. }
  5. abstract class Mailer implements MailerInterface {
  6. protected $sender;
  7. public function setSender(MessagingInterface $sender) {
  8. $this->sender = $sender;
  9. }
  10. }
  11. class PHPMailer extends Mailer {
  12. public function send($body) {
  13. $body .= "\n\n Sent from a phpmailer.";
  14. return $this->sender->send($body);
  15. }
  16. }
  17. class SwiftMailer extends Mailer {
  18. public function send($body) {
  19. $body .= "\n\n Sent from a SwiftMailer.";
  20. return $this->sender->send($body);
  21. }
  22. }
  23. interface MessagingInterface {
  24. public function send($body);
  25. }
  26. class TextMessage implements MessagingInterface {
  27. public function send($body) {
  28. echo 'TextMessage > send > $body: ' . $body;
  29. }
  30. }
  31. class HtmlMessage implements MessagingInterface {
  32. public function send($body) {
  33. echo 'HtmlMessage > send > $body: ' . $body;
  34. }
  35. }
  36. // Client
  37. $phpmailer = new PHPMailer();
  38. $phpmailer->setSender(new TextMessage());
  39. $phpmailer->send('Hi!');
  40. $swiftMailer = new SwiftMailer();
  41. $swiftMailer->setSender(new HtmlMessage());
  42. $swiftMailer->send('Hello!');

我们首先创建一个MailerInterface。然后,具体的Mailer类实现了MailerInterface,为PHPMailerSwiftMailer提供了一个基类。然后我们定义MessagingInterface,它由TextMessageHtmlMessage类实现。最后,客户端实例化PHPMailerSwiftMailer,在调用发送方法之前传递TextMessageHtmlMessage的实例。

组合模式

组合模式就是将对象的层次结构作为一个对象,通过一个公共接口来处理。其中,对象组成三个结构,客户端因为只消耗公共接口,所以对底层结构的变化一无所知。

下面是一个组合模式的实现例子。

  1. interface Graphic {
  2. public function draw();
  3. }
  4. class CompositeGraphic implements Graphic {
  5. private $graphics = array();
  6. public function add($graphic) {
  7. $objId = spl_object_hash($graphic);
  8. $this->graphics[$objId] = $graphic;
  9. }
  10. public function remove($graphic) {
  11. $objId = spl_object_hash($graphic);
  12. unset($this->graphics[$objId]);
  13. }
  14. public function draw() {
  15. foreach ($this->graphics as $graphic) {
  16. $graphic->draw();
  17. }
  18. }
  19. }
  20. class Circle implements Graphic {
  21. public function draw()
  22. {
  23. echo 'draw-circle';
  24. }
  25. }
  26. class Square implements Graphic {
  27. public function draw() {
  28. echo 'draw-square';
  29. }
  30. }
  31. class Triangle implements Graphic {
  32. public function draw() {
  33. echo 'draw-triangle';
  34. }
  35. }
  36. $circle = new Circle();
  37. $square = new Square();
  38. $triangle = new Triangle();
  39. $compositeObj1 = new CompositeGraphic();
  40. $compositeObj1->add($circle);
  41. $compositeObj1->add($triangle);
  42. $compositeObj1->draw();
  43. $compositeObj2 = new CompositeGraphic();
  44. $compositeObj2->add($circle);
  45. $compositeObj2->add($square);
  46. $compositeObj2->add($triangle);
  47. $compositeObj2->remove($circle);
  48. $compositeObj2->draw();

我们首先创建了一个Graphic接口。然后我们创建了CompositeGraphicCircleSquareTriangle,它们都实现了Graphic接口。除了仅仅实现了Graphic接口中的draw方法外,CompositeGraphic还增加了两个方法,用于跟踪添加到它的图形的内部集合。然后客户端将这些Graphic类全部实例化,将它们全部添加到CompositeGraphic中,然后由CompositeGraphic调用draw方法。

装饰器模式

装饰器模式允许将行为添加到单个对象实例中,而不影响同一类的其他实例的行为。我们可以定义多个装饰器,其中每个装饰器都会增加新的功能。

下面是一个装饰器模式实现的例子。

  1. interface LoggerInterface {
  2. public function log($message);
  3. }
  4. class Logger implements LoggerInterface {
  5. public function log($message) {
  6. file_put_contents('app.log', $message, FILE_APPEND);
  7. }
  8. }
  9. abstract class LoggerDecorator implements LoggerInterface {
  10. protected $logger;
  11. public function __construct(Logger $logger) {
  12. $this->logger = $logger;
  13. }
  14. abstract public function log($message);
  15. }
  16. class ErrorLoggerDecorator extends LoggerDecorator {
  17. public function log($message) {
  18. $this->logger->log('ERROR: ' . $message);
  19. }
  20. }
  21. class WarningLoggerDecorator extends LoggerDecorator {
  22. public function log($message) {
  23. $this->logger->log('WARNING: ' . $message);
  24. }
  25. }
  26. class NoticeLoggerDecorator extends LoggerDecorator {
  27. public function log($message) {
  28. $this->logger->log('NOTICE: ' . $message);
  29. }
  30. }
  31. $logger = new Logger();
  32. $logger->log('Resource not found.');
  33. $logger = new Logger();
  34. $logger = new ErrorLoggerDecorator($logger);
  35. $logger->log('Invalid user role.');
  36. $logger = new Logger();
  37. $logger = new WarningLoggerDecorator($logger);
  38. $logger->log('Missing address parameters.');
  39. $logger = new Logger();
  40. $logger = new NoticeLoggerDecorator($logger);
  41. $logger->log('Incorrect type provided.');

我们首先创建了一个LoggerInterface,其中有一个简单的log方法,然后我们定义了LoggerLoggerDecorator,两者都实现了LoggerInterface。然后我们定义了LoggerLoggerDecorator,它们都是实现LoggerInterface的。其次是ErrorLoggerDecoratorWarningLoggerDecoratorNoticeLoggerDecorator,它们实现了LoggerDecorator。最后,客户端部分三次实例化Logger,传递给它不同的装饰器。

门面模式

当我们想通过一个更简单的接口来简化大型系统的复杂性时,就会用到门面模式。它通过为大多数常见任务提供方便的方法,通过一个客户端使用的单一封装类来实现。

下面是一个门面模式实现的例子。

  1. class Product {
  2. public function getQty() {
  3. // Implementation
  4. }
  5. }
  6. class QuickOrderFacade {
  7. private $product = null;
  8. private $orderQty = null;
  9. public function __construct($product, $orderQty) {
  10. $this->product = $product;
  11. $this->orderQty = $orderQty;
  12. }
  13. public function generateOrder() {
  14. if ($this->qtyCheck()) {
  15. $this->addToCart();
  16. $this->calculateShipping();
  17. $this->applyDiscount();
  18. $this->placeOrder();
  19. }
  20. }
  21. private function addToCart() {
  22. // Implementation...
  23. }
  24. private function qtyCheck() {
  25. if ($this->product->getQty() > $this->orderQty) {
  26. return true;
  27. } else {
  28. return true;
  29. }
  30. }
  31. private function calculateShipping() {
  32. // Implementation...
  33. }
  34. private function applyDiscount() {
  35. // Implementation...
  36. }
  37. private function placeOrder() {
  38. // Implementation...
  39. }
  40. }
  41. // Client
  42. $order = new QuickOrderFacade(new Product(), $qty);
  43. $order->generateOrder();

我们首先创建一个Product类,并提供一个单一的getQty方法。然后,我们创建了一个QuickOrderFacade类,通过构造函数接受产品实例和数量,并进一步提供generateOrder方法,聚合所有的订单生成动作。最后,客户端实例化产品,将其传递给QuickOrderFacade的实例,对其调用generateOrder

享元模式

享元模式是关于性能和资源的减少,在相似的对象之间共享尽可能多的数据。这意味着在一个实现中,一个类的相同实例被共享。当预计要创建大量相同的类实例时,这种模式效果最好。

下面是一个享元模式实现的例子。

  1. interface Shape {
  2. public function draw();
  3. }
  4. class Circle implements Shape {
  5. private $colour;
  6. private $radius;
  7. public function __construct($colour) {
  8. $this->colour = $colour;
  9. }
  10. public function draw() {
  11. echo sprintf('Colour %s, radius %s.', $this->colour, $this->radius);
  12. }
  13. public function setRadius($radius) {
  14. $this->radius = $radius;
  15. }
  16. }
  17. class ShapeFactory {
  18. private $circleMap;
  19. public function getCircle($colour) {
  20. if (!isset($this->circleMap[$colour])) {
  21. $circle = new Circle($colour);
  22. $this->circleMap[$colour] = $circle;
  23. }
  24. return $this->circleMap[$colour];
  25. }
  26. }
  27. // Client
  28. $shapeFactory = new ShapeFactory();
  29. $circle = $shapeFactory->getCircle('yellow');
  30. $circle->setRadius(10);
  31. $circle->draw();
  32. $shapeFactory = new ShapeFactory();
  33. $circle = $shapeFactory->getCircle('orange');
  34. $circle->setRadius(15);
  35. $circle->draw();
  36. $shapeFactory = new ShapeFactory();
  37. $circle = $shapeFactory->getCircle('yellow');
  38. $circle->setRadius(20);
  39. $circle->draw();

我们首先创建了一个Shape接口,有一个单一的 draw 方法。然后我们定义了实现Shape接口的Circle类,接着是ShapeFactory类。在ShapeFactory中,getCircle方法根据颜色选项返回一个新Circle的实例。最后,客户端实例化多个ShapeFactory对象,向getCircle方法调用传递不同的颜色。

代理模式

代理模式的功能是作为一个原始对象的幕后接口。它可以作为一个简单的转发包装器,甚至可以围绕它所包装的对象提供额外的功能。额外附加功能的例子可能是懒加载或缓存,这可能会补偿原始对象的资源密集操作。

下面是一个代理模式实现的例子。

  1. interface ImageInterface {
  2. public function draw();
  3. }
  4. class Image implements ImageInterface {
  5. private $file;
  6. public function __construct($file) {
  7. $this->file = $file;
  8. sleep(5); // Imagine resource intensive image load
  9. }
  10. public function draw() {
  11. echo 'image: ' . $this->file;
  12. }
  13. }
  14. class ProxyImage implements ImageInterface {
  15. private $image = null;
  16. private $file;
  17. public function __construct($file) {
  18. $this->file = $file;
  19. }
  20. public function draw() {
  21. if (is_null($this->image)) {
  22. $this->image = new Image($this->file);
  23. }
  24. $this->image->draw();
  25. }
  26. }
  27. // Client
  28. $image = new Image('image.png'); // 5 seconds
  29. $image->draw();
  30. $image = new ProxyImage('image.png'); // 0 seconds
  31. $image->draw();

我们首先创建了一个ImageInterface,有一个单一的draw方法。然后我们定义了ImageProxyImage类,这两个类都是ImageInterface的扩展。在Image类的 __construct 中,我们用sleep方法调用模拟了资源紧张的操作。最后,客户端同时实例化ImageProxyImage,显示出两者的执行时间差异。