创建型设计模式
单例模式(Singleton Pattern)
概念
单例模式是一种创建型设计模式,确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。其思想是控制对象的创建,只让对象创建一次,从而限制资源的过度消耗或者避免对共享资源的多重占用。
作用
- 保证唯一性:确保某个类只有一个实例存在,避免实例间的数据冲突。
- 全局访问:提供一个全局访问点,整个应用程序中任何时刻都能访问这个实例。
- 控制资源使用:通过限制实例的数量,有助于节约系统资源(如数据库连接或文件系统的访问)。
- 延迟初始化:对象只在需要时被创建,有利于资源管理和提高应用效率。
常用应用场景
单例模式通常应用于以下场合:
- 配置文件的读取器:应用程序中共享一个配置读取实例,整个系统使用同一个配置资源。
- 数据库连接池:管理数据库连接,分享一个连接池实例以重复使用数据库连接,减少连接开销。
- 日志记录器:应用程序共用一个日志记录器实例,统一管理日志的写入。
- 设备管理器:如打印机管理器或文件管理器,系统中统一管理,避免重复创建和销毁。
- 缓存:应用程序中的缓存通常也是单例实现,单个实例管理全局的缓存数据。
单例模式的要点
- 私有构造函数:确保不能从外部调用构造函数,防止外部创建多个实例。
- 私有静态实例变量:持有类的唯一实例,确保不被外部访问和修改。
- 公共静态方法:提供全局访问点,并在需要时创建实例,这种方法通常采用“懒汉式”初始化(延迟初始化)。
- 线程安全:在多线程环境下要保证实例的创建过程是线程安全的。
- 防止实例被复制:通过隐藏或删去拷贝构造函数和赋值操作符来防止复制。
PHP 实现示例
<?php
class Singleton {
private static $instance; // 单例对象存储
// 私有构造函数,防止外部创建实例
private function __construct() {}
// 提供一个获取实例的方法
public static function getInstance() {
if (!self::$instance) {
self::$instance = new Singleton();
}
return self::$instance;
}
// 禁止克隆实例,防止复制单例
private function __clone() {}
// 防止反序列化单例
private function __wakeup() {}
}
// 使用单例
$singleton = Singleton::getInstance();
在这个PHP示例中,Singleton
类通过将构造函数、克隆方法和反序列化方法定义为私有的,确保了类的单一实例性。利用一个静态方法 getInstance
来检测实例是否已被创建,并相应地返回实例或创建它。这保证了整个应用中只存在一个此类实例。
工厂模式(Factory Pattern)
概念
工厂模式是一种创建型设计模式,用于在软件工程中创建对象,而不暴露创建对象的具体逻辑给客户端。通过调用一个共同的接口或方法来获取新创建的对象,客户端从中解耦了对象的创建过程。
好处
- 封装性:工厂模式封装了创建实例的过程,使得客户端不需要知道对象是如何被创建和组装的。
- 扩展性:新增一个产品类时,只需扩展一个相应的工厂类,无需修改现有逻辑。
- 解耦:减少了系统中各个类之间的依赖关系,使得修改具体的实现类不会影响调用者。
- 复用性:创建好的对象可以被多处复用,无需在每个需要的地方重新创建。
分类
工厂模式主要有三种变体:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式:
简单工厂模式并不是一个真正的设计模式,更多是一种编程习惯。它有一个中心化的工厂类,决定哪一个产品类应当被实例化。这种模式适用于产品类数量较少且不会扩展的情况。
- 集中管理:将创建对象的逻辑集中到一个单一的工厂类中,便于修改和维护。
- 封装创建过程:客户端不需要知道具体的类实现细节,只需通过工厂接口请求所需的对象。
- 客户端简化:简化了客户端的代码,因为它只需要调用工厂类的方法而不需要直接实例化类。
PHP代码实现
<?php
// 抽象产品类
abstract class Document {
abstract public function open();
abstract public function close();
}
// 具体产品类 - TextDocument
class TextDocument extends Document {
public function open() {
echo "Opening a text document.\n";
}
public function close() {
echo "Closing the text document.\n";
}
}
// 具体产品类 - Spreadsheet
class Spreadsheet extends Document {
public function open() {
echo "Opening a spreadsheet.\n";
}
public function close() {
echo "Closing the spreadsheet.\n";
}
}
// 简单工厂类
class DocumentFactory {
public static function createDocument($type) {
switch ($type) {
case 'text':
return new TextDocument();
case 'spreadsheet':
return new Spreadsheet();
default:
throw new Exception("Document type $type is not supported.");
}
}
}
// 客户端代码
try {
$textDoc = DocumentFactory::createDocument('text');
$textDoc->open();
$textDoc->close();
$spreadsheet = DocumentFactory::createDocument('spreadsheet');
$spreadsheet->open();
$spreadsheet->close();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
/*> 在这个例子中,DocumentFactory 类有一个静态方法 createDocument,
它决定基于提供的类型创建并返回哪一种文档对象。
这使得添加新的文档类型变得简单,只需修改工厂类的方法即可,
同时也使得客户端代码从具体的产品实现中解耦。*/
这种模式适用于那些对象创建相对简单,且不会经常更改的情况。当系统中的具体产品类不经常变化时,使用简单工厂模式可以减少系统的复杂性。
简单工厂模式会有一个问题,就是当你要增加一个C类产品你就需要修改工厂类里面的代码,也就是说要增加:switch—case。对于这个问题,接下来的工厂方法模式可以解决这个问题。
工厂方法模式(Factory Method Pattern)
工厂方法模式属于创建型设计模式之一,它主要用于创建对象,同时隐藏创建逻辑,不直接使用 new 关键字实例化对象。这样,程序中的调用者需要某个产品时,通过调用一个工厂方法来获取,而无需知道具体的实现细节。
概念
工厂方法模式通过定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法让类的实例化推迟到其子类。
优点
- 提高灵活性:工厂方法模式允许类在不修改客户端代码的情况下引进新的产品类型。
- 扩展性好:新增产品类型时,只需添加具体产品并实现工厂方法即可,遵循开闭原则。
- 解耦框架:客户端无需知道它所使用的具体产品的类,只需关心产品的接口,减少系统组件之间的依赖关系。
- 单一职责:每个具体工厂类只负责创建对应的产品。
#要点
- 产品接口:所有产品都应实现这个通用接口,该接口定义了产品应有的方法。
- 具体产品:实现或继承产品接口的类。
- 创建者(抽象工厂)接口:声明工厂方法,该方法应返回一个产品接口。
- 具体创建者:重写工厂方法以返回一个具体产品实例。
实例详情介绍
假设我们有一个软件,需要支持不同类型的日志记录:文件日志记录和数据库日志记录。
产品接口(Logger):
interface Logger {
public function log($message);
}
具体产品(FileLogger 和 DatabaseLogger):
class FileLogger implements Logger {
public function log($message) {
echo "Log message to a file: $message";
}
}
class DatabaseLogger implements Logger {
public function log($message) {
echo "Log message to a database: $message";
}
}
创建者接口(LoggerCreator):
abstract class LoggerCreator {
// 工厂方法声明
abstract protected function createLogger(): Logger;
public function log($message) {
// 获取 Logger 对象
$logger = $this->createLogger();
$logger->log($message);
}
}
具体创建者(FileLoggerCreator 和 DatabaseLoggerCreator):
class FileLoggerCreator extends LoggerCreator {
protected function createLogger(): Logger {
return new FileLogger();
}
}
class DatabaseLoggerCreator extends LoggerCreator {
protected function createLogger(): Logger {
return new DatabaseLogger();
}
}
使用:
function clientCode(LoggerCreator $creator) {
$creator->log("This is an error message.");
}
$creator = new FileLoggerCreator();
clientCode($creator);
$creator = new DatabaseLoggerCreator();
clientCode($creator);
在这个示例中,根据传入不同的
LoggerCreator
的子类,客户端代码可以记录消息到不同的媒介(文件或数据库),而不需要修改现有代码。这展示了工厂方法模式的力量:可以通过扩展来添加新功能,而无须修改依赖于抽象类型的代码。
抽象工厂模式:
抽象工厂模式是一种创建型设计模式,用于创建一系列相关或互相依赖的对象。它允许你使用对象组成的接口来创建家族产品,而不需要指明具体类。
要点
- 抽象工厂:一个接口,用来声明一系列创建方法,每个方法对应一个产品类型。
- 具体工厂:实现抽象工厂接口的类,每个具体工厂负责生成特定的产品家族。
- 抽象产品:一个产品家族中的单一产品类型的接口。
- 具体产品:抽象产品的具体实现,由具体工厂创建。
- 客户端:使用抽象工厂和抽象产品的接口,从具体工厂获取具体产品的实例。
优点
- 隔离具体类的生成:客户端不需要知道它使用的具体产品的类名,只关心产品的接口。
- 易于交换产品系列:因为具体工厂类在一个应用仅出现一次,所以改变一个应用的产品家族很容易。
- 增加新产品族比较方便:无需修改现有代码,只需要新增具体工厂和产品即可。
缺点
- 难以支持新种类的产品:如果需要添加新的产品(非家族产品),则涉及修改抽象工厂和所有具体工厂的接口,导致系统复杂度提高。
示例代码(以创建不同类型的界面组件为例)
每行注释:
<?php
interface GUIFactory { // 创建一个界面组件工厂的接口
public function createButton(); // 用于创建按钮的方法
public function createCheckbox(); // 用于创建复选框的方法
}
class WinFactory implements GUIFactory { // Windows风格界面的具体工厂
public function createButton() { // 创建Windows风格的按钮
return new WinButton();
}
public function createCheckbox() { // 创建Windows风格的复选框
return new WinCheckbox();
}
}
class MacFactory implements GUIFactory { // MacOS风格界面的具体工厂
public function createButton() { // 创建MacOS风格的按钮
return new MacButton();
}
public function createCheckbox() { // 创建MacOS风格的复选框
return new MacCheckbox();
}
}
interface Button { // 按钮产品的接口
public function paint(); // 按钮的绘制方法
}
interface Checkbox { // 复选框产品的接口
public function paint(); // 复选框的绘制方法
}
class WinButton implements Button { // 具体产品:Windows按钮
public function paint() {
echo "Rendering a button in Windows style.\n";
}
}
class WinCheckbox implements Checkbox { // 具体产品:Windows复选框
public function paint() {
echo "Rendering a checkbox in Windows style.\n";
}
}
class MacButton implements Button { // 具体产品:Mac按钮
public function paint() {
echo "Rendering a button in MacOS style.\n";
}
}
class MacCheckbox implements Checkbox { // 具体产品:Mac复选框
public function paint() {
echo "Rendering a checkbox in MacOS style.\n";
}
}
function clientCode(GUIFactory $factory) { // 客户端代码,接受一个工厂对象作为参数
$button = $factory->createButton(); // 使用工厂创建按钮
$button->paint(); // 绘制按钮
$checkbox = $factory->createCheckbox(); // 使用工厂创建复选框
$checkbox->paint(); // 绘制复选框
}
// 示例用法
clientCode(new WinFactory()); // 使用Windows工厂和组件
clientCode(new MacFactory()); // 使用Mac工厂和组件
此代码示例展示了如何实现抽象工厂模式,且通过具体的工厂(Windows和Mac风格工厂)来创建界面组件。客户端代码通过抽象工厂接口与具体实现分离,使得可以轻松切换不同的产品族而不需修改客户端代码。