设计模式之组合对象的模式

目录

1. 组合模式

2. 装饰模式

3. 外观模式


按照 设计模式简介 一文的“如何描述模式”来描述:

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 适用场景

  • 设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式;
  • 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口;
  • 维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值