【摘】设计模式入门指南

设计模式入门指南

想知道设计模式是什么?在这篇文章中,我会解释为什么设计模式重要。我也会提供一些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. ...

  17. ...

  18. }
  19. // get a session instance

  20. $session = Session::getInstance();

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


结论

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

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

注:本文转载自译言网

【摘】设计模式入门指南,码迷,mamicode.com

时间: 2024-10-12 20:01:08

【摘】设计模式入门指南的相关文章

iOS 应用开发入门指南

前言:http://www.guomii.com/posts/20250安装工具:http://www.guomii.com/posts/20255工具:http://www.guomii.com/posts/20273语言:http://www.guomii.com/posts/20284基本任务:http://www.guomii.com/posts/20291框架:http://www.guomii.com/posts/20294设计模式:http://www.guomii.com/pos

OpenCASCADE入门指南

OpenCASCADE入门指南 [email protected] 一.概述 荀子说“君子性非异也,善假于物也”.当你会用英语,就可以与世界各国的人交流:当你会用编程语言,就可以与计算机交流:当你会用数学语言,就可以精确的描述世界.当你会用OpenCASCADE这个开发包(SDK),就可以在计算机中创造一个三维世界.大学期间,偶然在图书馆翻到一本发黄的书,讲的是用Turbo C2.0 C语言graphics.h头文件来画图的计算机图形学的书,随即对计算机图形学有了兴趣.后来一直在图书馆里面看计算

Quartz.NET简介及入门指南

Quartz.NET简介 Quartz.NET是一个功能完备的开源调度系统,从最小的应用到大规模的企业系统皆可适用. Quartz.NET是一个纯净的用C#语言编写的.NET类库,是对非常流行的JAVA开源调度框架 Quartz 的移植. 入门指南 本入门指南包括以下内容: 下载 Quartz.NET 安装 Quartz.NET 根据你的特定项目配置 Quartz 启动一个样例程序 下载和安装 你可以下载 zip 文件或使用 Nuget 程序包.Nuget 程序包只包含 Quartz.NET 运

Java程序员的Golang入门指南(上)

Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵.如果你是某种语言老手,你就是那门语言的"传道者",下意识地会保护它.无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的.<肖申克的救赎>对此有很好的注脚: [Red] These walls are funny.

【翻译Autofac的帮助文档】1.入门指南

[写在前面]尝试做完一件工作之外自我觉得有意义的一件事,那就从翻译Autofac的帮助文档吧. 入门指南 将Autofac集成你的应用程序的步骤通常很简单,一般是: 时刻以IOC(控制反转)的思想来规划你的应用程序 在你的Porject中添加Autofac引用 按照如下步骤设计应用程序的启动环节 创建一个ContainerBuilder 向ContainerBuilder注册组件 通过ContainerBuilder的Build()方法获得Container(后续需用到) 在应用程序运行环节时,

Markdown——入门指南

导语: Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用.看到这里请不要被「标记」.「语言」所迷惑,Markdown 的语法十分简单.常用的标记符号也不超过十个,这种相对于更为复杂的 HTML 标记语言来说,Markdown 可谓是十分轻量的,学习成本也不需要太多,且一旦熟悉这种语法规则,会有一劳永逸的效果. Ulysses for Mac 一,认识 Markdown 在刚才的导语里提到,Markdown 是一种用来写作的轻量级「标记语言」

Win32编程API 基础篇 -- 1.入门指南 根据英文教程翻译

入门指南 本教程是关于什么的 本教程的目的是向你介绍使用win32 API编写程序的基础知识(和通用的写法).使用的语言是C,但大多数C++编译器也能成功编译,事实上,教程中的绝大多数内容都适用于任何可以连接API的语言,包括Java.Assembly和Visual Basic:我不会向你呈现任何跟这些语言相关的代码,这需要你在本教程的指导下自己去完成,有一些人在本API的基础上使用其他语言进行编程取得了相当的成功. 本教程不会教你C语言,也不会告诉你怎样去运行你特定的编译器(Borland C

[转载]TFS入门指南

[原文发表地址] Tutorial: Getting Started with TFS in VS2010 [原文发表时间] Wednesday, October 21, 2009 1:00 PM 本月初,我们发布了TFS新基础配置.该配置为建立支持源码管理,工作项和生成(builds)的TFS版本提供了便利. 这是一个好机会将你在VSS(Visual Source Safe)上的资源迁移到TFS,并且还可以选用一些新的特性.现在VS2010 Beta2的正式版已经发布了,下面是该系统的入门指南

编程入门指南

前言 如今编程成为了一个越来越重要的「技能」:作为设计师,懂一些编程可能会帮你更好地理解自己的工作内容:作为创业者,技术创始人的身份则会让你的很多工作显得更容易.而作为刚想入门的新手,面对眼前海量的信息,或许根本不知道从哪里开始:入门轻松度过初级材料的学习后,发现学习越来越困难,陡峭的学习曲线又让你望而却步:你知道如何在页面上打印输出一些文本行,但是你不知道何时该进行一个真正的有用的项目:你不清楚自己还有哪些不知道的东西,你甚至搞不清下一步该学什么. 这篇文章的内容对此不仅会有一些方向性的建议,