结构型设计模式

结构型设计模式概念

结构型设计模式是一种关注怎样组合类和对象以形成更大的结构,同时简化结构间的关系的设计模式。这些模式帮助确保系统部件间的独立性和可维护性。

适配器模式概念

适配器模式(Adapter Pattern)是一种结构型设计模式,用于解决两个不兼容接口之间的工作问题,使得原本因接口不匹配不能一起工作的类可以协同工作。

主要要点

  • 接口转换:适配器模式可以将一个类的接口转化成客户端所期望的另一个接口。
  • 提高类的复用性:适配器使得原有的类在不修改原有代码的情况下可以在新的环境中使用。
  • 增加类的透明性和灵活性:客户端代码可以透过适配器调用不同接口的函数。

示例:PHP中的适配器

假设我们需要整合一个旧系统,其中数据以欧洲标准(公制)提供,而新系统使用的是美国标准(英制)。我们将创建一个适配器来转换这些度量标准。

// 公制系统类,使用公里作为距离单位
class MetricSystem {
    private $kilometers;

    // 构造函数设定距离(公里)
    public function __construct($kilometers) {
        $this->kilometers = $kilometers;
    }

    // 获取距离(公里)
    public function getDistance() {
        return $this->kilometers;
    }
}

// 英制系统类,使用英里作为距离单位
class ImperialSystem {
    private $miles;

    // 构造函数设定距离(英里)
    public function __construct($miles) {
        $this->miles = $miles;
    }

    // 处理和显示英里
    public function processDistance() {
        echo "Processing distance: " . $this->miles . " miles\n";
    }
}

// 适配器将公制系统的公里转换为英制系统的英里
class MetricToImperialAdapter {
    private $imperialSystem;

    // 构造函数接受一个公制系统对象,并进行单位转换:1公里 ≈ 0.621371英里
    public function __construct(MetricSystem $metric) {
        $miles = $metric->getDistance() * 0.621371;
        $this->imperialSystem = new ImperialSystem($miles);
    }

    // 转发调用到英制系统类的方法
    public function processDistance() {
        $this->imperialSystem->processDistance();
    }
}

// 客户端代码
$metricSystem = new MetricSystem(100); // 客户提供100公里
$adapter = new MetricToImperialAdapter($metricSystem);
$adapter->processDistance();  // 输出转换后的英里并处理

使用场景

适配器模式适用于以下场景:

  • 系统集成:当希望使用现有的类,而其接口与其他代码不兼容时可以使用适配器模式。
  • 接口转换:当需要转换一些现有类的接口以便新的系统可以使用它们时。
  • 封装变化:当想要隐藏功能的具体实现,只暴露简单的接口到外部系统时。

适用适配器模式可以实现上述功能而不需要修改现有代码,增加系统的可维护性和扩展性。

装饰器模式概念

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许用户在不修改现有对象的结构的情况下,向对象添加新的功能。这是通过创建一个包含该对象的装饰类来实现的,从而扩展其行为。

主要要点

  • 动态添加功能:在运行时添加对象的新功能,而不影响其他对象。
  • 替代子类化:提供了一种灵活的替代方案来扩展功能,比继承更加灵活。
  • 功能组合:可以组合多个装饰来添加多重功能。

优点

  • 增加对象功能弹性:同一个对象可以装饰多种功能。
  • 动态扩展:可以在运行时通过应用不同的装饰来改变对象的行为。
  • 遵循开闭原则:可以在不修改现有代码的情况下引入新的行为。

缺点

  • 高复杂性:多层装饰会增加系统的复杂性。
  • 难以维护:由于增加了许多小对象,调试和维护可能变得比较困难。

使用场景

  • 在不影响其他对象的情况下,为单个对象添加职责。
  • 需要动态地给对象添加功能,这些功能也可以动态地撤销。
  • 当子类化的实现方式不可行时,可以考虑使用装饰器模式来扩展功能。

PHP代码示例

// 定义组件接口
interface Coffee {
    public function getCost();
    public function getDescription();
}

// 基础对象实现
class SimpleCoffee implements Coffee {
    // 返回基础咖啡的成本
    public function getCost() {
        return 10;
    }

    // 返回描述
    public function getDescription() {
        return 'Simple coffee';
    }
}

// 装饰器基类
abstract class CoffeeDecorator implements Coffee {
    protected $coffee;

    // 构造函数注入一个Coffee对象
    public function __construct(Coffee $coffee) {
        $this->coffee = $coffee;
    }
}

// 具体的装饰器:加奶
class MilkCoffee extends CoffeeDecorator {
    // 添加奶的成本
    public function getCost() {
        return $this->coffee->getCost() + 2;
    }

    // 更新描述,加上奶
    public function getDescription() {
        return $this->coffee->getDescription() . ', milk';
    }
}

// 其他装饰器可类似定义

// 客户端使用
$coffee = new SimpleCoffee(); // 最初的咖啡
echo $coffee->getDescription() . ": $" . $coffee->getCost() . "\n";

$coffeeWithMilk = new MilkCoffee($coffee); // 加奶
echo $coffeeWithMilk->getDescription() . ": $" . $coffeeWithMilk->getCost() . "\n";

这个例子展示了如何通过装饰器模式扩展简单咖啡的功能,例如添加牛奶,并在每个装饰器中动态增加价格和描述,以实现功能组合的灵活性。

注册树模式

注册数模式(Registry Pattern)是一种常用的设计模式,用于在全局的容器中存储对象,这样可以在整个应用中方便地访问这些对象。这种模式主要用于解决全局访问和避免不断传递对象到那些深层嵌套的函数或类中的问题。

要点:

  • 中央存储: 提供中央位置来存放和检索核心部件,例如服务或重要对象。
  • 全局访问点: 允许从应用任何部分访问存储的对象。

概念:

注册数模式通过提供一个全局可访问的容器使得应用中的组件访问变得简单。在大型项目中尤为有用,帮助管理和访问如数据库连接、日志记录器等共享资源。

优缺点:

优点:
  • 减少全局变量的使用: 提供更结构化的方式来存放全局对象。
  • 易于访问: 直接通过注册名访问对象,而无需引用或继承。
  • 集中管理: 所有重要的服务和组件可以集中管理。
缺点:
  • 增加依赖: 程序中的不同部分可能过度依赖于注册数中的对象。
  • 测试难度: 由于依赖全局状态,可能使得单元测试变得较为困难。
  • 过度使用导致耦合: 过度使用可能导致代码之间耦合度提高,难以维护和扩展。

PHP 代码示例:

下面的示例展示了如何实现一个简单的注册数模式。

<?php

// 定义一个全局可用的注册数类
class Registry {
    private static $instance = null;    // 存放类的单例
    private $storage = [];              // 用于存储对象

    // 保护构造函数以防止外部创建类的多个实例
    protected function __construct() { }

    // 禁止外部克障
    private function __clone() { }

    // 获取单例对象的静态方法
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();  // 若尚未创建实例,则创建之
        }
        return self::$instance;
    }

    // 设置值的方法
    public function set($key, $value) {
        $this->storage[$key] = $value;
    }

    // 获取值的方法
    public function get($key) {
        if (array_key_exists($key, $this->storage)) {
            return $this->storage[$key];
        }
        return null;
    }

    // 检查是否存在键的方法
    public function has($key) {
        return array_key_exists($key, $this->storage);
    }
}

// 示例用法

// 获取注册数的实例
$registry = Registry::getInstance();

// 设置一些全局值
$registry->set('name', 'John Doe');
$registry->set('age', 30);

// 获取并打印这些全局值
echo $registry->get('name');  // 输出:John Doe
echo "\n";
echo $registry->get('age');   // 输出:30

?>

解释:

  • Registry 类: 作为一个单例,确保全局只有一个注册数实例。
  • set 方法: 通过键来存储对象或任意值。
  • get 方法: 通过键检索存储的对象。
  • has 方法: 检查某个键是否已经在注册表中。

通过这种方式,Registry类可以在不同位置被访问,并提供对同一资源的访问,这对于维护如配置设置、数据库连接等通用资源非常有用。使用单例模式还确保资源不会被重复创建,保持一致性。

门面模式(Facade Pattern)

门面模式是一种常用的设计模式,通过创建一个统一的高级接口来隐藏系统的复杂性,使得系统更易于使用。

要点

  • 简化接口:为复杂的子系统提供一个简化的接口。
  • 解耦:客户端与复杂子系统之间的解耦,客户端只需要与门面交互。
  • 更好的分层:通过门面将客户端与深层次的业务逻辑分开。

门面模式(外观模式)

概念

门面模式中,“门面”(Facade)角色为子系统中的一群接口提供一个统一的高级接口,使得子系统更容易使用。

优点

  • 简化了客户端实现:客户端只需与门面对象打交道,无需知道子系统的复杂性。
  • 提高了子系统的独立性和可移植性:只暴露必要的部分给客户端,其他内部逻辑修改不影响客户端。
  • 减少系统间的依赖:隐藏了系统的内部组件,减少了组件间的依赖关系。

缺点

  • 不符合开闭原则:如果子系统发生修改,可能需要修改门面的代码。
  • 可能成为系统中所有类的通信中心:如果过度使用,门面可能会吸收太多的职责,变得复杂。

PHP代码示例

以下是一个例子,使用门面模式简化对一个电脑系统进行启动和关闭的操作。

<?php

// 子系统 1:处理 CPU 的启动和关闭
class CPU {
    public function start() {
        echo "CPU started.\n";
    }
    public function shutDown() {
        echo "CPU shut down.\n";
    }
}

// 子系统 2:处理内存的启动和关闭
class Memory {
    public function load() {
        echo "Memory loaded.\n";
    }
    public function free() {
        echo "Memory freed.\n";
    }
}

// 子系统 3:处理硬盘的启动和关闭
class HardDrive {
    public function read() {
        echo "Hard drive read.\n";
    }
    public function write() {
        echo "Hard drive write.\n";
    }
}

// 门面类,为三个子系统提供统一的界面
class Computer {
    protected $cpu;
    protected $memory;
    protected $hardDrive;

    public function __construct() {
        $this->cpu = new CPU();
        $this->memory = new Memory();
        $this->hardDrive = new HardDrive();
    }

    // 启动计算机的门面方法
    public function startComputer() {
        $this->cpu->start();
        $this->memory->load();
        $this->hardDrive->read();
        echo "Computer started.\n";
    }

    // 关闭计算机的门面方法
    public function shutDownComputer() {
        $this->cpu->shutDown();
        $this->memory->free();
        $this->hardDrive->write();
        echo "Computer shut down.\n";
    }
}

// 客户端代码
$computer = new Computer();
$computer->startComputer();  // 开启电脑
$computer->shutDownComputer();  // 关闭电脑

?>

解释

  • 子系统类 (CPU, Memory, HardDrive):实现了电脑的各种底层操作。
  • 门面类 (Computer):为客户端提供了一个简单的接口来操作这些复杂的子系统,使得启动和关闭计算机变得非常简单。

此代码显示了如何使用门面模式来简化与多个子系统的交互,使得客户端从复杂的子系统实现中解耦,通过门面执行常见任务。这样的设计可以让系统有更好的模块化和易用性,同时也易于维护和扩展。

代理模式(Proxy Pattern)

代理模式(Proxy Pattern)是一种设计模式,它通过提供一个代替对象(代理)来控制对其他对象的访问。它常用来控制对资源密集或远程资源的访问,或添加额外的功能。下面是代理模式的关键要点、概念、优缺点以及一个用 PHP 实现的简单例子。

要点

  1. 结构: 包含四个主要组件:

    • 主题(Subject)接口: 定义了 RealSubject 和 Proxy 共同的接口。
    • 真实主题(RealSubject)类: 实现主题接口的类,定义了真实的对象。
    • 代理(Proxy)类: 也实现了主题接口,用于控制对真实主题的访问,可能会进行额外的处理(如访问控制、懒加载、智能引用等)。
    • 客户(Client): 使用主题接口来操作真实主题或代理。
  2. 概念: 代理模式通过创建一个提供与真实对象相同接口的代理对象,并控制对真实对象的访问或执行额外操作,从而间接访问真实对象。

优点

  • 隔离性: 客户端与真实对象之间的隔离可以保护真实对象不受直接访问的影响。
  • 功能扩展: 在不修改真实对象代码的情况下增加额外功能。
  • 控制访问: 可以控制对真实对象的访问,实现权限控制或资源优化等。

缺点

  • 性能问题: 引入代理对象可能会引起一定的性能开销。
  • 复杂性增加: 增加系统设计的复杂性,可能导致系统维护较为困难。

PHP代码示例

下面是一个简单的代理模式实现,演示如何控制对某个资源的访问。

<?php

// 1. 创建 Subject 接口
interface Subject {
    public function request();
}

// 2. 创建 RealSubject 类,实现 Subject 接口
class RealSubject implements Subject {
    public function request() {
        echo "Requesting the real service.\n";
    }
}

// 3. 创建 Proxy 类,同样实现 Subject 接口
class Proxy implements Subject {
    private $realSubject;

    // 在构造函数中创建 RealSubject 实例
    public function __construct() {
        $this->realSubject = new RealSubject();
    }

    // 代理方法,其中可以添加控制逻辑
    public function request() {
        // 示例:添加访问控制
        if ($this->accessControl()) {
            $this->realSubject->request();
        } else {
            echo "Proxy: Access denied.\n";
        }
    }

    // 访问控制函数
    private function accessControl() {
        // 只是示例逻辑
        return true; // 假设这里控制权限
    }
}

// 4. 客户端代码使用 Proxy
function clientCode(Subject $subject) {
    $subject->request();
}

// 创建一个代理对象并调用
$proxy = new Proxy();
clientCode($proxy);

?>

在这个例子中,代理(Proxy)和真实对象(RealSubject)都实现了相同的接口(Subject),客户端(通过函数clientCode)通过代理来访问真实对象,而代理内部提供了一个访问控制。(例如检查权限)以决定是否可以访问真实对象。这样可以在不改变客户端代码的情况下为系统添加访问控制功能。

  • 40
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值