目录
按照 设计模式简介 一文的“如何描述模式”来描述:
1. 组合模式
1.1 问题
现实编程中往往会有自身包含自身的情境,例如:目录、导航、分类等,那么该如何抽象这类结构捏?
换种说法就是:如何组织那些具有相似功能的类组成的层级机构(树模型,树的每个节点具有相似功能)?
1.2 解决方案
建立一个单根继承体系,使具有截然不同职责的集合可以并肩工作。
示例代码:
// 定义相似类型家族的统一接口
abstract class Component
{
/* @var Component[] $nodes */
protected $nodes = [];
// 添加节点
abstract public function addNode(Component $component);
// 删除节点
abstract public function delNode(Component $component);
// 这个方法表示相似功能
abstract function sayHello();
}
// 组合对象,用于储存叶子节点
class Composite extends Component
{
public function addNode(Component $component)
{
if (! in_array($component, $this->nodes, true)) {
$this->nodes[] = $component;
}
}
public function delNode(Component $component)
{
$this->nodes = array_udiff($this->nodes, [$component], function ($a, $b) { return ($a === $b?0:1); });
}
function sayHello()
{
foreach ($this->nodes as $node) {
$node->sayHello();
}
}
}
// 叶子节点对象,叶子没有子节点
// 访问单个叶子对象与访问组合对象是一样的,因为他们都实现了统一接口,这里表现为都有sayHello()方法
class Leaf extends Component
{
// 叶子节点对象不能添加子节点
public function addNode(Component $component)
{
throw new Exception('I am a leaf!!!');
}
// 叶子节点对象不能删除子节点
public function delNode(Component $component)
{
throw new Exception('I am a leaf!!!');
}
function sayHello()
{
echo "hello world!!! \n";
}
}
1.3 效果
- 组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以像处理简单元素一样来处理复杂元素;
- 在大部分叶子节点对象与组合对象可以互换的情况下,组合模式才最适应;
- 当组合成一个庞大的根系,一个简单的调用对于系统都是很大的开销,调用组合对象的sayHello()方法是遍历调用的;
- 组合模式又依赖于组成部分的简单性,随着引入复杂的规则,代码会越来越难以维护;
1.4 示例代码的优化版本,指的借鉴,因为叶子节点对象不应该实现添加节点与删除节点方法
// 定义相似类型家族的统一接口
abstract class Component
{
// 这个方法表示相似功能
abstract function sayHello();
/**
* 调用这个方法判断是否是叶子节点对象,而组合对象会返回$this
* @return null|Composite
*/
public function getComposite()
{
return null;
}
}
// 抽象的组合对象,用于储存叶子节点
abstract class Composite extends Component
{
/* @var Component[] $nodes */
protected $nodes = [];
// 组合对象返回本身,表示可以添加子节点
public function getComposite()
{
return $this;
}
// 添加节点
public function addNode(Component $component)
{
if (! in_array($component, $this->nodes, true)) {
$this->nodes[] = $component;
}
}
// 删除节点
public function delNode(Component $component)
{
$this->nodes = array_udiff($this->nodes, [$component], function ($a, $b) { return ($a === $b?0:1); });
}
function sayHello()
{
foreach ($this->nodes as $node) {
$node->sayHello();
}
}
}
// 具体的组合对象
class CompositeOne extends Composite
{
// do some thing in here
}
// 叶子节点对象,叶子没有子节点
// 访问单个叶子对象与访问组合对象是一样的,因为他们都实现了统一接口
class Leaf extends Component
{
function sayHello()
{
echo "hello world!!! \n";
}
}
// 提供一个组手类
class CompositeHelper
{
public static function joinComposite(Component $newNode, Component $existsNode)
{
if (is_null($com = $existsNode->getComposite())) {
$com = new CompositeOne();
$com->addNode($existsNode);
$com->addNode($newNode);
} else {
$com->addNode($newNode);
}
return $com;
}
}
// 简单使用
$leaf_1 = new Leaf();
$leaf_2 = new Leaf();
$composite = CompositeHelper::joinComposite($leaf_1, $leaf_2);
$composite->sayHello();
2. 装饰模式
2.1 问题
当系统中的类爆炸式的增长、继承体系很复杂的时候,为了扩展一个功能而改变类文件或继承体系,代价会是非常大的,那么该如何解决捏?
2.2 解决方案
创建一个包装对象,也就是装饰来包装原对象,包装对象持有原对象的引用。
装饰对象接受所有来自客户端的请求,因为持有原对象的引用,就可以把这些请求转发给原对象,在转发前或者后装饰对象可以实现功能
示例代码:
// 抽象的原对象
abstract class Component
{
abstract public function doSomeThing();
}
// 真实的原对象
class subComponent extends Component
{
public function doSomeThing()
{
echo "Hi I am lucy \n";
}
}
// 抽象的装饰对象,继承至原对象,与原对象一样的接口
abstract class ComponentDecorator extends Component
{
// 持有原对象的引用
protected $component;
public function __construct(Component $component)
{
$this->component = $component;
}
}
// 具体的装饰对象
class HelloDecorator extends ComponentDecorator
{
public function doSomeThing()
{
// 转发前先实现功能
echo "Hello I am lily";
// 转发给真实对象
$this->component->doSomeThing();
}
}
// 简单使用
$obj = new HelloDecorator(new subComponent());
$obj->doSomeThing();
2.3 效果
- 灵活的扩展一个类的功能,或给一个类添加附加功能;
- Decorator可以提供比继承更多的灵活性,当然更多的灵活性也带了更多的复杂性;
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合;
- 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂;
3. 外观模式
3.1 问题
在系统中总会形成大量的仅在系统自身内部有用的方法,并且这些方法又不太想要外部系统访问,那么该如何处理捏?
3.2 解决方案
系统也应该跟类一样,提供定义清晰的公共接口,而对外部影响其内部结构。为系统提供一个接口类,专门负责客户端的访问。
示例代码:
// 系统类,实际上是一个复杂的系统
class subSystem
{
public function sayHello()
{
echo "hello world !! \n";
}
}
// 接口类
class Facade
{
// 定义的接口,本身不做任何事情,只调用系统的方法来处理
public static function doSomeThing()
{
$obj = new subSystem();
$obj->sayHello();
}
}
3.3 效果
- 对于客户端来讲,访问会变得比较简单,非常方便,而且只在一个地方调用也会变得很容易维护;
- 对于系统来讲,对外部隐藏了具体实现,当实现需要变更时并不会对外部产生任何影响;
- 实现了客户端与系统之间的松耦合;
3.4 适用场景
- 设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式;
- 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口;
- 维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。