第九章:生成对象
笔记
单例模式
- 使用场景:只需要一个类实例。
现实场景有很多,但是归根结底就是我们希望全局访问的对象都是同一个对象,而不是同一个类生成的多个可能相同的对象。
<?php
class Preferences{
private $props=[];
private static $instance;
/**
* 设置成 private 就可以保证用户无法实例化该类
* Preferences constructor.
*/
private function __construct(){}
/**
* 设置一个静态方法实例化自己
* @return Preferences
*/
public static function getInstance(){
if(!self::$instance){
self::$instance=new Preferences();
}
return self::$instance;
}
public function setProperty($key,$value){
$this->props[$key]=$value;
}
public function getProperty($key){
return isset($this->props[$key])?$this->props[$key]:"";
}
}
/**
* 下面来演示一下
*/
// 定义一个变量
$preference1=Preferences::getInstance();
// 设置类的属性
$preference1->setProperty("show","hello world !");
// 移除变量
unset($preference1);
// 重新定一个变量
$preference2=Preferences::getInstance();
// 获取值,值依然有效
print $preference2->getProperty("show");
工厂模式方法
- 使用场景:当有多个子类实现一个抽象类,并需要判断实例化哪个子类时,就会考虑使用工厂模式方法。
实例1
<?php
/**
* 原先理解的工厂模式
*/
class BloggsApptEncode{}
class MegeApptEncode{}
class CommsManager{
const BLOGGS=1;
const MEGA=2;
private $mode=1;
/**
* 指定要实例化的子类
* CommsManager constructor.
* @param $mode
*/
public function __construct($mode)
{
$this->mode=$mode;
}
/**
* 获取类的实例
* @return BloggsApptEncode|MegeApptEncode
*/
public function getApptEndCode(){
// 任何工厂方法,判断是不可避免的
switch ($this->mode){
case self::BLOGGS:
return new BloggsApptEncode();
break;
case self::MEGA:
return new MegeApptEncode();
break;
}
}
}
$instance=new CommsManager(CommsManager::MEGA);
$apptEncode=$instance->getApptEndCode();
上面的代码是没有问题的,但是如果又加了一些新的需求,导致你的代码成了这个样子,那么你就需要考虑别的实现方式了:
<?php
/**
* 原先理解的工厂模式
*/
class BloggsApptEncode{}
class MegeApptEncode{}
class CommsManager{
const BLOGGS=1;
const MEGA=2;
private $mode=1;
/**
* 指定要实例化的子类
* CommsManager constructor.
* @param $mode
*/
public function __construct($mode)
{
$this->mode=$mode;
}
/**
* 获取类的实例
* @return BloggsApptEncode|MegeApptEncode
*/
public function getApptEndCode(){
switch ($this->mode){
case self::BLOGGS:
return new BloggsApptEncode();
break;
case self::MEGA:
return new MegeApptEncode();
break;
}
}
/**
* 下面中将判断做了2次
*/
public function getHeaderText(){
switch ($this->mode){
case self::BLOGGS:
return "Bloggs Header";
case self::MEGA:
return "Mega Header";
}
}
public function getFooterText(){
switch ($this->mode){
case self::BLOGGS:
return "Bloggs Footer";
case self::MEGA:
return "Mega Footer";
}
}
}
$instance=new CommsManager(CommsManager::MEGA);
$apptEncode=$instance->getApptEndCode();
$headerText=$instance->getHeaderText();
$footerText=$instance->getFooterText();
上面中,我们将判断做了3次,那么这个时候你就可以考虑将工厂的类进行拆分了:
实例2
<?php
/****/
abstract class ApptEncode{
abstract function encode();
}
class BloggsApptEncode extends ApptEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
class MegaApptEncode extends ApptEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
/*****/
abstract class CommsManager{
abstract function getHeaderText();
abstract function getFooterText();
abstract function getApptEncode();
}
class BloggsCommonManager extends CommsManager{
function getHeaderText()
{
// TODO: Implement getHeaderText() method.
}
function getFooterText()
{
// TODO: Implement getFooterText() method.
}
function getApptEncode()
{
return new BloggsApptEncode();
}
}
class MegaCommsManager extends CommsManager{
function getHeaderText()
{
// TODO: Implement getHeaderText() method.
}
function getFooterText()
{
// TODO: Implement getFooterText() method.
}
function getApptEncode()
{
return new MegaApptEncode();
}
}
这里我们将原先在一个工厂模式中的判断写进了2个类中,外部做区分时,只需要一个判断条件就可以了。简单来说就是你只需要针对BloggsCommonManager
和MegaCommsManager
做一个简单工厂模式就可以了。
抽象工厂模式
- 使用场景:当返回的类不止一个时,并且这些类都是相关联的。
<?php
/**
* 需要返回的类1
* Class ApptEncode
*/
abstract class ApptEncode{
abstract function encode();
}
class BloggsApptEncode extends ApptEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
class MegaApptEncode extends ApptEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
/**
* 需要返回的类2
* Class TtdEncode
*/
abstract class TtdEncode{
abstract function encode();
}
class BloggsTtdEncode extends TtdEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
class MegaTtdEncode extends TtdEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
/**
* 需要返回的类3
* Class ContractEncode
*/
abstract class ContractEncode{
abstract function encode();
}
class BloggsContractEncode extends ContractEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
class MegaContractEncode extends ContractEncode{
function encode()
{
// TODO: Implement encode() method.
}
}
abstract class CommsManager{
abstract function getHeaderText();
abstract function getFooterText();
/**
* 定义公共返回类1的方法
* @return mixed
*/
abstract function getApptEncode();
/**
* 定义返回类2的方法
* @return mixed
*/
abstract function getTtdEncode();
/**
* 定义返回类3的方法
* @return mixed
*/
abstract function getContractEncode();
}
class BloggsCommonManager extends CommsManager{
function getHeaderText()
{
// TODO: Implement getHeaderText() method.
}
function getFooterText()
{
// TODO: Implement getFooterText() method.
}
function getApptEncode()
{
return new BloggsApptEncode();
}
function getTtdEncode()
{
return new BloggsTtdEncode();
}
function getContractEncode()
{
return new BloggsContractEncode();
}
}
class MegaCommsManager extends CommsManager{
function getHeaderText()
{
// TODO: Implement getHeaderText() method.
}
function getFooterText()
{
// TODO: Implement getFooterText() method.
}
function getApptEncode()
{
return new MegaApptEncode();
}
function getTtdEncode()
{
return new MegaTtdEncode();
}
function getContractEncode()
{
return new MegaContractEncode();
}
}
这里我们需要返回的子类不止1个,而是3个,并且这3个中还有2个类别。首先我们不想针对这3个类写3个抽象工厂模式,而只想写1个,其次,这里我们将相关联的类都写在了一个类里面,这样可以保证,你获取的都是相关联的类,比如你不会获取到一个BloggsApptEncode
的同时再获取一个MegaTtdEncode
。简单来说就是我们将判断的代码减少了到了一个地方。
原型模式
- 使用场景:需要获取多个对象实例,而且这些实例都是类似的。
实例1
<?php
/**
先讲一下前提,我现在要设计一个9*9的棋盘游戏,每个格子都对应一个场地,森林,海洋,平原。每种场地又有地球模式和火星模式2中模式混合,我现在需要类来帮我生成1行3个类,论这个类如何设计?
**/
class Sea{}
class EarthSea extends Sea{}
class MarsSea extends Sea{}
class Forest{}
class EarthForest extends Forest{}
class MarsForest extends Forest{}
class Plains{}
class EarthPlains extends Plains{}
class MarsPlains extends Plains{}
class TerrainFactory{
private $sea;
private $forest;
private $plains;
public function __construct(Sea $sea,Forest $forest,Plains $plains)
{
$this->sea=$sea;
$this->forest=$forest;
$this->plains=$plains;
}
public function getSea(){
return clone $this->sea;
}
public function getForest(){
return clone $this->forest;
}
public function getPlains(){
return clone $this->plains;
}
}
// 第一行
$row1=new TerrainFactory(new EarthSea(),new MarsForest(),new EarthPlains());
// 第二行
$row2=new TerrainFactory($row1->getSea(),new EarthForest(),$row1->getPlains());
// 第三行
$row3=new TerrainFactory($row2->getSea(),$row2->getForest(),$row1->getPlains());
这代码咋一看有点多余,因为我完全可以按照我的意愿直接new
对象,而不需要调用类的方法来创建。那是因为你没考虑到创建一个对象是需要参数的:
实例2
<?php
/**
* 海洋环境
* Class Sea
*/
class Sea{
protected $speed;
public function __construct($speed=1)
{
$this->speed=$speed;
}
}
class EarthSea extends Sea{}
class MarsSea extends Sea{}
/**
* 森林环境
* Class Forest
*/
class Forest{}
class EarthForest extends Forest{}
class MarsForest extends Forest{}
/**
* 平原环境
* Class Plains
*/
class Plains{}
class EarthPlains extends Plains{}
class MarsPlains extends Plains{}
class TerrainFactory{
private $sea;
private $forest;
private $plains;
public function __construct(Sea $sea,Forest $forest,Plains $plains)
{
$this->sea=$sea;
$this->forest=$forest;
$this->plains=$plains;
}
public function getSea(){
return clone $this->sea;
}
public function getForest(){
return clone $this->forest;
}
public function getPlains(){
return clone $this->plains;
}
}
// 第一行
$row1=new TerrainFactory(new EarthSea(2),new MarsForest(),new EarthPlains());
// 第二行
$row2=new TerrainFactory($row1->getSea(),new EarthForest(),$row1->getPlains());
// 第三行
$row3=new TerrainFactory(new MarsSea(1),$row2->getForest(),$row1->getPlains());
这里我将Sea
类增加了一个属相值$speed
,在$row
中,速度值与$row
1中一致,但是到了$row3
中,速度将下降,而我可以完全根据需要,自行配置。最重要的是,当我在代码中修改$row2
中的$speed
值时,是不会影响到$row1
的值的。
下面是一段书上的实例代码,完美的解释了什么叫单例模式:
<?php
class MegaCommsManager{}
class BloggsCommsManager{}
class Settings{
static $COMMSTYPES=2;
}
class AppConfig{
private static $instance;
private $commsManager;
private function __construct(){
$this->init();
}
private function init(){
switch (Settings::$COMMSTYPES){
case 1:
$this->commsManager=new MegaCommsManager();
break;
case 2:
$this->commsManager=new BloggsCommsManager();
break;
}
}
public static function getInstance(){
if(!self::$instance){
self::$instance=new self();
}
return self::$instance;
}
public function getCommsManager(){
return $this->commsManager;
}
}
这段代码厉害就厉害在他永远都只有一个实例,并且只有在创建自身的时候才去读配置文件,也就是说在这个类的生命周期中,即使你修改了Settings
中的$COMMSTYPES
,也不会影响到这个类的运行,因为他只会按照开始设定的值去运行。
问题
- 单例模式是什么?主要是为了解决什么问题?如何实现单例模式?
单例模式并不是说所有方法都是静态的,甚至说一个属性都保持一个值,而是无论在代码哪里调用这个类,本质上得到的都是同一个对象,而不是一个类的多个实例对象。
要解决的方面很多,与其列出所有的答案,不如保持一个观念,这个类我需要多少实例对象,一个就用"单例模式"。
将__construct
设置为私有的方法,并在__clone
方法中动点手脚。
- 工厂模式方法是什么?为什么叫工厂模式?他主要是为了解决什么问题?
简单讲就是获取类的,再细一点就是获取相关类的。他主要就是将对获取对应的类做一个整合,而这些对应的类一般就是指继承自同一个父类的多个子类,当你需要实例化对应的子类时,你可以将判断写进一个类中,这个类就是工厂类。
- 工厂模式中,实例1与实例2的区别在哪里?实例化2主要是为什么说明什么问题?
实例1返回的是1个类,功能简单。实例2则是说明当你在代码中需要多次判断时,那么你可以考虑将该抽象类升级为抽象类,并创建多个子类去实现,这样在抽象类中只需要指定使用该抽象类的哪个子类就可以了,减少了判断。
- 抽象工厂模式与工厂模式的关系是什么?
抽象工厂本质上与工厂模式一致,只是说明了当返回多个类,并且这些类相互关联时,就要采用实例2中的模式,创建多个子类,让这些子类来管理对应的关联类。
- 原型模式主要是为了解决什么问题?
当需要返回多个类似的对象时,可以使用原型模式。
- 简述以上各个模式都存在什么弊端?
单例模式:与全局变量一样存在危险,因为依赖都被封装在了单例模式中,并没有以强制类型参数的形式被程序员所认知,所以可能会造成意想不到的问题。
工厂模式:类的数量会很多,即使两个类实现完全类似的功能,你也必须创建2个类对应不同的功能模块。
抽象工厂模式:工厂模式存在的问题,抽象工厂模式也有。同时,当你要在底层作出修改时,比如修改抽象类中的代码,则需要修改多个类,这个操作是不可避免,甚至是经常会发生的。
原型模式:这个模式的核心就是使用clone
方法,但是clone
存在一个很大的问题就是,当你的克隆的类中的一个属性是对象时,这个对象并不会被复制,而是使用同一个对象,这就会为你的代码留下隐患,所以使用单例模式时,一定要注意clone
这个函数存在的先天缺陷。
总结
这一章主要目的就是为你讲解一些创建对象时可以使用的设计模式,你必须要确定要他们的前提,并且没有模式是一劳永逸的,你一定要确定你的能力是否能驾驭住你创建的这些类,以及他们自身存在的缺陷。比如你定义了一个抽象类,但是你发现你创建的子类中需要的方法与你定义的抽象类完全不一致,那么你要不只能去修改你的抽象类,要不就是在子类中定义各种特有的方法,然后再调用,这样无疑只是形式上的设计模式。所以用一句名言概括就是:不要为了设计模式而设计模式。