设计模式入门指南

0 评论 3326 浏览 1 收藏 17 分钟

想知道设计模式是什么?在这篇文章中,我会解释为什么设计模式重要。我也会提供一些PHP的例子来解释什么时候什么情况下来使用设计模式。

什么是设计模式?

设计模式是针对我们日常编程问题的经过优化的可重用的方法。一种设计模式不仅仅是可以简单集成到系统中的一个类或者一个库。它是一个只能在正确的情境下使用的模板。它也不是只针对某种语言。一个好的设计模式应该可以适用于绝大多数语言中,同时也要依赖于语言的特性。最重要的是,任何设计模式如果用错地方的话,就有可能变成一把双刃剑,它可以是灾难性的而且为你造成许多问题。当然,用在合适的地方,它就是你的救世主。

有三种基本的设计模式

  • 结构型
  • 创造型
  • 行为型

结构型设计模式通常处理实体之间的关系,使这些实体之间更容易协同工作。

创造性设计模式提供了实例化机制,在合适的情境下创建对象变得更容易。

行为型设计模式用于实体之间的通讯,使得这些实体之间互相交流更容易和灵活。

我们为什么要使用设计模式?

设计模式是针对程序问题按照原则仔细思考之后的解决方法。许多程序员都碰到过这些问题,并且针对这些问题对症下药。如果你遇到这些问题,为什么不使用已经被证明过的方法而要自己重新创建一个呢?

示例

让我们设想一下,你得到了一个任务,根据情况将两个不同行为的类合并到一起。这两个类大量应用于现有系统中的不同地方,这将使得移除这两个类而且改变现有的代码非常困难。为了作出这些改变,改变现有代码同样要测试改变后的代码,因为系统中可能在不同的组件中依赖这些改变,这将引入新的bug。取而代之,你可以实现一个基于策略模式和适配器模式的变种,就可以很容易的处理这种类型的情况。

  1. class StrategyAndAdapterExampleClass {
  2. private $_class_one;
  3. private $_class_two;
  4. private $_context;
  5. public function __construct( $context ) {
  6. $this->_context = $context;
  7. }
  8. public function operation1() {
  9. if( $this->_context == “context_for_class_one” ) {
  10. $this->_class_one->operation1_in_class_one_context();
  11. } else ( $this->_context == “context_for_class_two” ) {
  12. $this->_class_two->operation1_in_class_two_context();
  13. }
  14. }
  15. }

很简单吧。现在,我们可以仔细了解一下策略模式。

策略模式

在上面的例子中,采用的策略是根据类初始化时$context变量的值决定。如果context值为class_one,将使用class_one,否则使用class_two。

聪明吧,但是我能在什么地方使用呢?

设想你现在正在设计一个可以更新或者创建新的用户记录的类。它仍然需要同样的输入(name, address, mobile number等等),但是,根据给定的情况,当更新或者创建时不得不采用不同的方法。现在,你可能只使用一个if-else来完成这个。但是,要是你在一个不同的地方需要这个类咋办?在这种情况下,你将不得不一遍又一遍地重写同样的if-else语句。在这种上下文环境中使用策略模式不是更轻松么?

  1. class User {
  2. public function CreateOrUpdate($name, $address, $mobile, $userid = null)
  3. {
  4. if( is_null($userid) ) {
  5. // it means the user doesn’t exist yet, create a new record
  6. } else {
  7. // it means the user already exists, just update based on the given userid
  8. }
  9. }
  10. }

现在,通常的策略模式包括封装你的算法在另外一个类中,但是在这种情况下,创建另外一个类可能会比较浪费。记住你并不是必须采用这种模板。在类似的情况中采用这种变化,就可以解决问题。
适配器模式

这同样可以让你改变一些从客户端类接收到的输入,使其和被适配者的功能吻合。

我能怎样使用它?

表述一个适配器类的另外一个术语是封装,表示允许你把行为封装到一个类中,并且在正确的情形下重用这些行为。一个经典的例子,当你为表创建一个领域类,你可以使用一个适配器类封装所有的方法到一个方法中,而不是调用不同的表并且一个一个的使用它们的方法。这不仅允许你重用你想使用的任何行为,如果你需要在不同的地方使用相同的行为的话,同样使你不必重写代码。

比较着两个实现,

非适配器方法

  1. $user = new User();
  2. $user->CreateOrUpdate( //inputs );
  3. $profile = new Profile();
  4. $profile->CreateOrUpdate( //inputs );

如果我们需要在不同的地方这么做,或者甚至在不同的项目中重用这些代码,我们将不得不重新写下这些东西。

更好的

相反我们可以这样做:

  1. $account_domain = new Account();
  2. $account_domain->NewAccount( //inputs );

在这种情况下,我们有一个封装类作为我们的账号(Account)类:

  1. class Account()
  2. {
  3. public function NewAccount( //inputs )
  4. {
  5. $user = new User();
  6. $user->CreateOrUpdate( //subset of inputs );
  7. $profile = new Profile();
  8. $profile->CreateOrUpdate( //subset of inputs );
  9. }
  10. }

这样,每当你需要账户类的时候你就能使用它。此外,你也可以在领域类中封装其他类。

工厂方法模式

这个模式的主要目标是把不同类的创建过程封装到一个单独的方法中。通过为工厂方法提供正确的上下文环境,它能够返回正确的对象。

何时能使用它?

使用工厂方法模式的最佳时机是当你有各种各样的不同的独立实体的时候。比如说你有个按钮类,这个类有很多不同的变种,如图片按钮,输入按钮和Flash按钮。根据需要,你可能要创建不同的按钮——这就是你能使用工厂为你创建按钮的地方。

  1. abstract class Button {
  2. protected $_html;
  3. public function getHtml()
  4. {
  5. return $this->_html;
  6. }
  7. }
  8. class ImageButton extends Button {
  9. protected $_html = “…”; //This should be whatever HTML you want for your image-based button
  10. }
  11. class InputButton extends Button {
  12. protected $_html = “…”; //This should be whatever HTML you want for your normal button ();
  13. }
  14. class FlashButton extends Button {
  15. protected $_html = “…”; //This should be whatever HTML you want for your flash-based button
  16. }

现在,我们能创建我们的工厂类:

  1. class ButtonFactory
  2. {
  3. public static function createButton($type)
  4. {
  5. $baseClass = ‘Button’;
  6. $targetClass = ucfirst($type).$baseClass;
  7. if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) {
  8. return new $targetClass;
  9. } else {
  10. throw new Exception(“The button type ’$type’ is not recognized.”);
  11. }
  12. }
  13. }

我们能像这样使用这段代码:

  1. $buttons = array(‘image’,‘input’,‘flash’);
  2. foreach($buttons as $b) {
  3. echo ButtonFactory::createButton($b)->getHtml()
  4. }

输出的应该是所有的HTML按钮类型。这样,你将能够根据情况说明该创建哪个按钮并且重用这些条件。

装饰者模式

装饰者模式的目标就是扩展的功能可以被适用在一个特定的实例,而且同时可以能够创建一个不具备这个扩展功能的原始实例。装饰者模式同时允许为一个实例使用多个装饰者类,这样你就不必纠缠于为每个实例创建一个装饰者类。这个模式在继承时是可选择的,继承指的是你可以从一个父类中继承父类的功能。不同于继承在编译时添加行为,在情况允许下,装饰允许你在运行时添加一个新的行为。

我们可以根据以下几步实现装饰者模式:

1. 创建一个装饰者类继承原始组件。

2. 在装饰者类中,添加一个组件域。

3. 在装饰者类的构造函数中初始化这个组件。

4. 在装饰者类中,重新将所有的调用新组件的方法。

5. 在装饰者类中,重写所有需要改变行为的组件方法。

我该何时使用?

当你拥有一个实体,这个实体仅在环境需要的时候拥有新的行为,这就是使用装饰者模式的地方。比如你有一个HTML连接元素,一个退出连接,你想根据当前的页面做一些稍微不同的事情。为了达到那个目标,我们可以使用装饰者模式。

首先,我们根据需要建立不同封装者。

1. 如果在首页并且已经登入了,我们希望这个连接被

标签封装起来。

2. 如果我们在一个不同的页面并且已经登入,我们希望这个连接被underline标签封装起来。

3. 如果我们登入了,我们希望这个连接字体被加粗。

一旦我们建立好我们的封装类,我们可以开始编写了。

  1. class HtmlLinks {
  2. //some methods which is available to all html links
  3. }
  4. class LogoutLink extends HtmlLinks {
  5. protected $_html;
  6. public function __construct() {
  7. $this->_html = ”Logout”;
  8. }
  9. public function setHtml($html)
  10. {
  11. $this->_html = $html;
  12. }
  13. public function render()
  14. {
  15. echo $this->_html;
  16. }
  17. }
  18. class LogoutLinkH2Decorator extends HtmlLinks {
  19. protected $_logout_link;
  20. public function __construct( $logout_link )
  21. {
  22. $this->_logout_link = $logout_link;
  23. $this->setHtml(“

    “ . $this->_html . ”

    “);

  24. }
  25. public function __call( $name, $args )
  26. {
  27. $this->_logout_link->$name($args[0]);
  28. }
  29. }
  30. class LogoutLinkUnderlineDecorator extends HtmlLinks {
  31. protected $_logout_link;
  32. public function __construct( $logout_link )
  33. {
  34. $this->_logout_link = $logout_link;
  35. $this->setHtml(““ . $this->_html . ”“);
  36. }
  37. public function __call( $name, $args )
  38. {
  39. $this->_logout_link->$name($args[0]);
  40. }
  41. }
  42. class LogoutLinkStrongDecorator extends HtmlLinks {
  43. protected $_logout_link;
  44. public function __construct( $logout_link )
  45. {
  46. $this->_logout_link = $logout_link;
  47. $this->setHtml(“” . $this->_html . ””);
  48. }
  49. public function __call( $name, $args )
  50. {
  51. $this->_logout_link->$name($args[0]);
  52. }
  53. }

我们可以这么使用它们:

  1. $logout_link = new LogoutLink();
  2. if( $is_logged_in ) {
  3. $logout_link = new LogoutLinkStrongDecorator($logout_link);
  4. }
  5. if( $in_home_page ) {
  6. $logout_link = new LogoutLinkH2Decorator($logout_link);
  7. } else {
  8. $logout_link = new LogoutLinkUnderlineDecorator($logout_link);
  9. }
  10. $logout_link->render();

这里我们能够看到我们是如何在需要的时候结合多个装饰者类的。既然所有的装饰者类使用__call方法,我们仍人可以调用原始的方法。如果我们假设我们现在在首页并且已经登入了,HTML输出应该是:

  1. <strong><h2><a href=”logout.php”>Logouta>h2>strong>

单件模式

因为单件变量对于所有的调用都是一样的,这使得其他对象使用单件实例更简单。

我该何时使用?

如果你需要把一个特定的实例从一个类传递到另外一个类,你能够使用单件模式来避免不得不通过构造函数或者参数传递这个实例。设想你已经创建了一个会话(Session)类,模仿了$_SESSION全局数组。既然这个类仅需要被实例化一次,我们可以这样实现一个单件模式:

  1. php
  2. class Session
  3. {
  4. private static $instance;
  5. public static function getInstance()
  6. {
  7. if( is_null(self::$instance) ) {
  8. self::$instance = new self();
  9. }
  10. return self::$instance;
  11. }
  12. private function __construct() { }
  13. private function __clone() { }
  14. //  any other session methods we might use
  15. }
  16. // get a session instance
  17. $session = Session::getInstance();

通过这样,我们可以在代码中不同的部分访问我们的会话类,即使在不同的类中。这个类将存在于所有调用getInstance方法中。

结论

其实还有更多的设计模式需要学习;在这篇文章中,我仅列举了在我编程过程中使用的其中一些著名的模式。如果你对其他设计模式感兴趣,Wikipedia的设计模式页面有足够的信息。如果那还不够,你可以参阅设计模式:可复用面向对象软件基础,这是一本最棒的设计模式书籍之一。

最后:当你使用这些设计模式时,一定要明确你正在解决正确的问题。如我前面所提到的,这些设计模式是一把双刃剑:如果在错误的环境下使用,它们可以使事情变得更糟:但是如果正确的使用,它们就是不可或缺的。

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!