专栏名称: 程序员的那些事
最有影响力的程序员自媒体,关注程序员相关话题:IT技术、IT职场、在线课程、学习资源等。
目录
相关文章推荐
OSC开源社区  ·  开源模型未必更先进,但会更长久 ·  2 天前  
程序员的那些事  ·  国企也中招!官网被挂上“码农的钱你也敢吞,* ... ·  22 小时前  
程序员小灰  ·  DeepSeek + IDEA!辅助编程太强了! ·  昨天  
程序员的那些事  ·  DeepSeek 下棋靠忽悠赢了 ... ·  2 天前  
OSC开源社区  ·  Gitee邀您参与SBOM行业调研:共建可信 ... ·  4 天前  
51好读  ›  专栏  ›  程序员的那些事

人人都能读懂的设计模式(1):创建型模式

程序员的那些事  · 公众号  · 程序员  · 2017-07-17 19:07

正文

(点击 上方蓝字 ,快速关注我们)


英文:kamranahmedse,编译: 伯乐在线 - Justin_YGG

如有好文章 投稿,请点击 → 这里了解详情


简介


设计模式用于解决反复出现的问题,是解决特定问题的指导方针。设计模式不是在应用中引用的类、package 或者库,而是在某些特定场景下解决特定问题的指导方针。


设计模式用于解决反复出现的问题,是解决某些特定问题的指导方针。


维基百科中这样描述设计模式:


在软件工程中,设计模式是针对软件设计中普遍存在(反复出现)的各种问题,所提出的可复用型解决方案。设计模式并不直接完成代码的编写,而是描述在不同情况下如何解决问题。


注意


  • 设计模式并非解决所有问题的银弹。

  • 不要强制使用设计模式,否则结果可能适得其反。谨记:设计模式是用来解决问题的,而不是来寻找问题的,不要过度思考。

  • 如果在对的地方对的时机使用设计模式,它会是你的救世主。反之,将会一团糟。


另注:下面的示例代码是用 PHP7 实现的,因为概念是一样的,所以语言并不会阻碍你理解设计模式。其他语言版本的实现正在进行中。


设计模式分类


  • 创建型模式

  • 结构型模式

  • 行为型模式


创建型模式


  • 概述


创建型模式专注于如何初始化对象 。


  • 维基百科


在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决这些问题。


  • 分类


  • 简单工厂模式

  • 工厂方法模式

  • 抽象工厂模式

  • 生成器模式

  • 原型模式

  • 单例模式


🏠 简单工厂模式


  • 现实生活示例


想象一下,你正在建造一座房子而且需要几扇房门,如果每次需要房门的时候,不是用工厂制造的房门,而是穿上木匠服,然后开始自己制造房门,将会搞得一团糟。


  • 概述


简单工厂模式只是为客户端创建实例,而不将任何实例化逻辑暴露给客户端。


  • 维基百科


在面向对象程序设计中,工厂通常是一个用来创建其他对象的对象。通常来讲,工厂是指某个功能或方法,此功能或方法返回不同类型的对象或者类的某个方法调用,返回的东西看起来是「新的」。


  • 程序示例


首先是房门的接口和实现


interface Door

{

public function getWidth () : float ;

public function getHeight () : float ;

}

class WoodenDoor implements Door

{

protected $width ;

protected $height ;

public function __construct ( float $width , float $height )

{

$this -> width = $width ;

$this -> height = $height ;

}

public function getWidth () : float

{

return $this -> width ;

}

public function getHeight () : float

{

return $this -> height ;

}

}


然后是生产房门的工厂


class DoorFactory

{

public static function makeDoor ($ width , $ height ) : Door

{

return new WoodenDoor ($ width , $ height );

}

}


  • 这样使用


$ door = DoorFactory :: makeDoor ( 100 , 200 );

echo 'Width: ' . $ door -> getWidth ();

echo 'Height: ' . $ door -> getHeight ();


  • 何时使用?


如果创建对象不仅仅是一些变量的初始化,还涉及某些逻辑,那么将其封装到一个专用工厂中取代随处使用的重复代码是有意义的。


🏭 工厂方法模式


  • 现实生活示例


考虑招聘经理的情况。一个人不可能应付所有职位的面试,对于空缺职位,招聘经理必须委派不同的人去面试。


  • 概述


工厂方法模式提供了一种将实例化逻辑委托给子类的方法。


  • 维基百科


在基于类的编程中,工厂方法模式是一种使用了工厂方法的创建型设计模式,在不指定对象具体类型的情况下,处理创建对象的问题。创建对象不是通过调用构造器而是通过调用工厂方法(在接口中指定工厂方法并在子类中实现或者在基类中实现,随意在派生类中重写)来完成。


  • 程序示例


以上述招聘经理为例,首先给出一个面试官接口及实现


interface Interviewer

{

public function askQuestions ();

}

class Developer implements Interviewer

{

public function askQuestions ()

{

echo 'Asking about design patterns!' ;

}

}

class CommunityExecutive implements Interviewer

{

public function askQuestions ()

{

echo 'Asking about community building' ;

}

}


然后创建 HiringManager


abstract class HiringManager

{

// Factory method

abstract public function makeInterviewer () : Interviewer ;

public function takeInterview ()

{

$ interviewer = $ this -> makeInterviewer ();

$ interviewer -> askQuestions ();

}

}


现在,任何子类都可以继承 HiringManager 并委派相应的面试官


class DevelopmentManager extends HiringManager

{

public function makeInterviewer () : Interviewer

{

return new Developer ();

}

}

class MarketingManager extends HiringManager

{

public function makeInterviewer () : Interviewer

{

return new CommunityExecutive ();

}

}


  • 这样使用


$ devManager = new DevelopmentManager ();

$ devManager -> takeInterview (); // Output: Asking about design patterns

$ marketingManager = new MarketingManager ();

$ marketingManager -> takeInterview (); // Output: Asking about community building.


  • 何时使用?


类中的一些常见处理需要在运行时动态决定所需的子类,换句话说,当客户端不知道可能需要的确切子类时,使用工厂方法模式。


🔨 抽象工厂模式


  • 现实生活示例


扩展一下简单工厂模式中的房门例子。基于所需,你可能需要从木门店获取木门,从铁门店获取铁门或者从相关的门店获取 PVC 门。进一步讲,你可能需要不同种类的专家来安装房门,比如木匠安装木门,焊接工安装铁门等等。正如你所料,房门有了依赖,木门需要木匠,铁门需要焊接工。


  • 概述


一组工厂的工厂:将相关或者互相依赖的单个工厂聚集在一起,而不指定这些工厂的具体类。


  • 维基百科


抽象工厂模式提供了一种方式,这种方式可以封装一组具有共同主题的个体工厂,而不指定这些工厂的具体类。


  • 编程示例


以上述房门为例,首先给出 Door 接口和一些实现


interface Door

{

public function getDescription ();

}

class WoodenDoor implements Door

{

public function getDescription ()

{

echo 'I am a wooden door' ;

}

}

class IronDoor implements Door

{

public function getDescription ()

{

echo 'I am an iron door' ;

}

}


然后根据每种房门类型给出对应的安装专家


interface DoorFittingExpert

{

public function getDescription ();

}

class Welder implements DoorFittingExpert

{

public function getDescription ()

{

echo 'I can only fit iron doors' ;

}

}

class Carpenter implements DoorFittingExpert

{

public function getDescription ()

{

echo 'I can only fit wooden doors' ;

}

}


现在抽象工厂可以将相关的对象组建在一起,也就是说,木门工厂会生成木门并提供木门安装专家,铁门工厂会生产铁门并提供铁门安装专家。


interface DoorFactory

{

public function makeDoor () : Door ;

public function makeFittingExpert () : DoorFittingExpert ;

}

// Wooden factory to return carpenter and wooden door

class WoodenDoorFactory implements DoorFactory

{

public function makeDoor () : Door

{

return new WoodenDoor ();

}

public function makeFittingExpert () : DoorFittingExpert

{

return new Carpenter ();

}

}

// Iron door factory to get iron door and the relevant fitting expert

class IronDoorFactory implements DoorFactory

{

public function makeDoor () : Door

{

return new IronDoor ();

}

public function makeFittingExpert () : DoorFittingExpert

{

return new Welder ();

}

}


  • 这样使用


$ woodenFactory = new WoodenDoorFactory ();

$ door = $ woodenFactory -> makeDoor ();

$ expert = $ woodenFactory -> makeFittingExpert ();

$ door -> getDescription (); // Output: I am a wooden door

$ expert -> getDescription (); // Output: I can only fit wooden doors

// Same for Iron Factory

$ ironFactory = new IronDoorFactory ();

$ door = $ ironFactory -> makeDoor ();

$ expert = $ ironFactory -> makeFittingExpert ();

$ door -> getDescription (); // Output: I am an iron door

$ expert -> getDescription (); // Output: I can only fit iron doors


正如你看到的,木门工厂将木匠和木门封装在一起,同样地,铁门工厂将铁门和焊接工封装在一起。这样就可以帮助我们确保,对于每一扇生产出来的门,都能搭配正确的安装工。


  • 何时使用?


当存在相关的依赖并涉及到稍复杂的创建逻辑时,使用抽象工厂模式。


👷 生成器模式


  • 现实生活示例


想象一下你在 Hardee’s 餐厅点了某个套餐,比如「大 Hardee 套餐」,然后工作人员会正常出餐,这是简单工厂模式。但是在很多情况下,创建逻辑可能涉及到更多步骤。比如,你想要一个定制的 Subway 套餐,对于你的汉堡如何制作有几个选项可供选择,比如你想要什么类型的酱汁?你想要什么奶酪? 在这种情况下,建造者模式便可以派上用场。


  • 概述


允许创建不同风格的对象,同时避免构造器污染。当创建多种风格的对象时或者创建对象时涉及很多步骤,可以使用生成器模式。


  • 维基百科


生成器模式是一种对象创建软件设计模式,其目的是找到重叠构造器反面模式的解决方案。


既然提到了,那我就补充一下什么是重叠构造器反面模式。 我们时不时地会看到如下构造函数:


public function __construct ($ size , $ cheese = true , $ pepperoni = true , $ tomato = false , $ lettuce = true )

{

}


正如你看到的,构造器参数的数量可能会迅速失控,并且参数的排列可能让人难以理解。 如果将来要添加更多选项,此参数列表可能会不断增长,这被称为重叠构造器反面模式。


程序示例


理想之选是使用生成器模式,首先给出汉堡类


class Burger

{

protected $ size ;

protected $ cheese = false ;

protected $ pepperoni = false ;

protected $ lettuce = false ;

protected $ tomato = false ;

public function __construct ( BurgerBuilder $ builder )

{

$ this -> size = $ builder -> size ;

$ this -> cheese = $ builder -> cheese ;

$ this -> pepperoni = $ builder -> pepperoni ;

$ this -> lettuce = $ builder -> lettuce ;

$ this -> tomato = $ builder -> tomato ;

}

}


然后是 builder


class BurgerBuilder

{

public $ size ;

public $ cheese = false ;

public $ pepperoni = false ;

public $ lettuce = false ;

public $ tomato = false ;

public function __construct ( int $ size )

{

$ this -> size = $ size ;

}

public function addPepperoni ()

{

$ this -> pepperoni = true ;

return $ this ;

}

public function addLettuce ()

{

$ this -> lettuce = true ;

return $ this ;

}

public function addCheese ()

{

$ this -> cheese = true ;

return $ this ;

}

public function addTomato ()

{

$ this -> tomato = true ;

return $ this ;

}

public function build () : Burger

{

return new Burger ($ this );

}

}


  • 这样使用


$ burger = ( new BurgerBuilder ( 14 ))







请到「今天看啥」查看全文