PHP设计模式范例 — DesignPatternsPHP(2)结构型设计模式

【搬运于GitHub开源项目DesignPatternsPHP】

项目地址: 戳我

2、结构型设计模式

在软件工程中,结构型设计模式集是用来抽象真实程序中的对象实体之间的关系,并使这种关系可被描述,概括和具体化。

2.1 适配器模式

2.1.1 目的

将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。

2.1.2 例子

  • 数据库客户端库适配器
  • 使用不同的webservices,通过适配器来标准化输出数据,从而保证不同webservice输出的数据是一致的

2.1.3 UML图

2.1.4 代码

你可以在 GitHub 上找到这些代码

BookInterface.php

Book.php

page = 1;
    }

    public function turnPage()
    {
        $this->page++;
    }

    public function getPage(): int
    {
        return $this->page;
    }
}

EBookAdapter.php

eBook = $eBook;
    }

    /**
     * This class makes the proper translation from one interface to another.
     */
    public function open()
    {
        $this->eBook->unlock();
    }

    public function turnPage()
    {
        $this->eBook->pressNext();
    }

    /**
     * notice the adapted behavior here: EBookInterface::getPage() will return two integers, but BookInterface
     * supports only a current page getter, so we adapt the behavior here
     *
     * @return int
     */
    public function getPage(): int
    {
        return $this->eBook->getPage()[0];
    }
}

EBookInterface.php

Kindle.php

page++;
    }

    public function unlock()
    {
    }

    /**
     * returns current page and total number of pages, like [10, 100] is page 10 of 100
     *
     * @return int[]
     */
    public function getPage(): array
    {
        return [$this->page, $this->totalPages];
    }
}

2.2 桥接模式

2.2.1 目的

解耦一个对象的实现与抽象,这样两者可以独立地变化。

2.2.2 例子

  • Symfony DoctrineBridge

2.2.3 UML图

2.2.4 代码

你可以在 GitHub 上找到这些代码

Formatter.php

PlainTextFormatter.php

HtmlFormatter.php

%s

', $text); } }

Service.php

implementation = $printer;
    }

    /**
     * @param Formatter $printer
     */
    public function setImplementation(Formatter $printer)
    {
        $this->implementation = $printer;
    }

    abstract public function get(): string;
}

HelloWorldService.php

implementation->format('Hello World');
    }
}

PingService.php

implementation->format('pong');
    }
}

2.3 组合模式

2.3.1 目的

以单个对象的方式来对待一组对象

2.3.2 例子

  • form类的实例包含多个子元素,而它也像单个子元素那样响应render()请求,当调用render()方法时,它会历遍所有的子元素,调用render()方法
  • Zend_Config: 配置选项树, 其每一个分支都是Zend_Config对象

2.3.3 UML图

2.3.4 代码

你可以在 GitHub 上找到这些代码

RenderableInterface.php

Form.php

';

        foreach ($this->elements as $element) {
            $formCode .= $element->render();
        }

        $formCode .= '';

        return $formCode;
    }

    /**
     * @param RenderableInterface $element
     */
    public function addElement(RenderableInterface $element)
    {
        $this->elements[] = $element;
    }
}

InputElement.php

';
    }
}

TextElement.php

text = $text;
    }

    public function render(): string
    {
        return $this->text;
    }
}

2.4 数据映射器

2.4.1 目的

数据映射器是一个数据访问层,用于将数据在持久性数据存储(通常是一个关系数据库)和内存中的数据表示(领域层)之间进行相互转换。其目的是为了将数据的内存表示、持久存储、数据访问进行分离。该层由一个或者多个映射器组成(或者数据访问对象),并且进行数据的转换。映射器的实现在范围上有所不同。通用映射器将处理许多不同领域的实体类型,而专用映射器将处理一个或几个。

此模式的主要特点是,与Active Record不同,其数据模式遵循单一职责原则(Single Responsibility Principle)。

2.4.2 例子

  • DB对象关系映射器(ORM): Doctrine2使用“EntityRepository”作为DAO

2.4.3 UML图

2.4.4 代码

你可以在 GitHub 上找到这些代码

User.php

username = $username;
        $this->email = $email;
    }

    /**
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }
}

UserMapper.php

adapter = $storage;
    }

    /**
     * finds a user from storage based on ID and returns a User object located
     * in memory. Normally this kind of logic will be implemented using the Repository pattern.
     * However the important part is in mapRowToUser() below, that will create a business object from the
     * data fetched from storage
     *
     * @param int $id
     *
     * @return User
     */
    public function findById(int $id): User
    {
        $result = $this->adapter->find($id);

        if ($result === null) {
            throw new \InvalidArgumentException("User #$id not found");
        }

        return $this->mapRowToUser($result);
    }

    private function mapRowToUser(array $row): User
    {
        return User::fromState($row);
    }
}

StorageAdapter.php

data = $data;
    }

    /**
     * @param int $id
     *
     * @return array|null
     */
    public function find(int $id)
    {
        if (isset($this->data[$id])) {
            return $this->data[$id];
        }

        return null;
    }
}

2.5 装饰器

2.5.1 目的

动态地为类的实例添加功能

2.5.2 例子

  • Zend Framework: Zend_Form_Element 实例的装饰器
  • Web Service层:REST服务的JSON与XML装饰器(当然,在此只能使用其中的一种)

2.5.3 UML图

2.5.4 代码

你可以在 GitHub 上找到这些代码

Booking.php

BookingDecorator.php

booking = $booking;
    }
}

DoubleRoomBooking.php

ExtraBed.php

booking->calculatePrice() + self::PRICE;
    }

    public function getDescription(): string
    {
        return $this->booking->getDescription() . ' with extra bed';
    }
}

WiFi.php

booking->calculatePrice() + self::PRICE;
    }

    public function getDescription(): string
    {
        return $this->booking->getDescription() . ' with wifi';
    }
}

2.6 依赖注入

2.6.1 目的

实现了松耦合的软件架构,可得到更好的测试,管理和扩展的代码

2.6.2 用例

注入DatabaseConfiguration, DatabaseConnection将从$config获得所需的所有内容。没有DI(依赖注入),配置将直接在DatabaseConnection中创建,这不利于测试和扩展它。

2.6.3 例子

  • Doctrine2 ORM 使用了依赖注入,它通过配置注入了 Connection 对象。为了达到方便测试的目的,可以很容易的通过配置创建一个mock的 Connection 对象。
  • Symfony 和 Zend Framework 2 也有了专门的依赖注入容器,用来通过配置数据创建需要的对象(比如在控制器中使用依赖注入容器获取所需的对象)

2.6.4 UML图

2.6.5 代码

你可以在 GitHub 上找到这些代码

DatabaseConfiguration.php

host = $host;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
    }

    public function getHost(): string
    {
        return $this->host;
    }

    public function getPort(): int
    {
        return $this->port;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getPassword(): string
    {
        return $this->password;
    }
}

DatabaseConnection.php


configuration = $config;
    }

    public function getDsn(): string
    {
        // this is just for the sake of demonstration, not a real DSN
        // notice that only the injected config is used here, so there is
        // a real separation of concerns here

        return sprintf(
            '%s:%s@%s:%d',
            $this->configuration->getUsername(),
            $this->configuration->getPassword(),
            $this->configuration->getHost(),
            $this->configuration->getPort()
        );
    }
}

2.7 外观模式

2.7.1 目的

Facade模式的主要目标不是避免您必须阅读复杂API的手册。这只是副作用。主要目的是减少耦合并遵循Demeter定律。

Facade通过嵌入多个(当然,有时只有一个)接口来解耦访客与子系统,当然也降低复杂度。

Facade不会禁止你访问子系统
你可以为一个子系统提供多个 Facade
因此一个好的 Facade 里面不会有 new 。如果每个方法里都要构造多个对象,那么它就不是 Facade,而是生成器或者 [ 抽象 | 静态 | 简单 ] 工厂方法。

优秀的 Facade 不会有 new,并且构造函数参数是接口类型的。如果你需要创建一个新实例,则在参数中传入一个工厂对象。

2.7.2 UML图

2.7.3 代码

你可以在 GitHub 上找到这些代码

Facade.php

bios = $bios;
        $this->os = $os;
    }

    public function turnOn()
    {
        $this->bios->execute();
        $this->bios->waitForKeyPress();
        $this->bios->launch($this->os);
    }

    public function turnOff()
    {
        $this->os->halt();
        $this->bios->powerDown();
    }
}

OsInterface.php

BiosInterface.php

2.8 连贯接口

2.8.1 目的

用来编写易于阅读的代码,就像自然语言一样(如英语)

2.8.2 例子

  • Doctrine2 的 QueryBuilder,就像下面例子中类似
  • PHPUnit 使用连贯接口来创建 mock 对象
  • Yii 框架:CDbCommandCActiveRecord 也使用此模式

2.8.3 UML图

2.8.4 代码

你可以在 GitHub 上找到这些代码

Sql.php

fields = $fields;

        return $this;
    }

    public function from(string $table, string $alias): Sql
    {
        $this->from[] = $table.' AS '.$alias;

        return $this;
    }

    public function where(string $condition): Sql
    {
        $this->where[] = $condition;

        return $this;
    }

    public function __toString(): string
    {
        return sprintf(
            'SELECT %s FROM %s WHERE %s',
            join(', ', $this->fields),
            join(', ', $this->from),
            join(' AND ', $this->where)
        );
    }
}

2.9 享元

2.9.1 目的

为了尽可能减少内存使用,Flyweight与类似的对象共享尽可能多的内存。当使用大量状态相差不大的对象时,就需要它。通常的做法是保持外部数据结构中的状态,并在需要时将其传递给flyweight对象。

2.9.2 UML图

2.9.3 代码

你可以在 GitHub 上找到这些代码

FlyweightInterface.php

CharacterFlyweight.php

name = $name;
    }

    public function render(string $font): string
    {
         // Clients supply the context-dependent information that the flyweight needs to draw itself
         // For flyweights representing characters, extrinsic state usually contains e.g. the font.

        return sprintf('Character %s with font %s', $this->name, $font);
    }
}

FlyweightFactory.php

pool[$name])) {
            $this->pool[$name] = new CharacterFlyweight($name);
        }

        return $this->pool[$name];
    }

    public function count(): int
    {
        return count($this->pool);
    }
}

2.10 代理模式

2.10.1 目的

为昂贵或者无法复制的资源提供接口。

2.10.2 例子

  • Doctrine2 使用代理来实现框架特性(如延迟初始化),同时用户还是使用自己的实体类并且不会使用或者接触到代理

2.10.3 UML图

2.10.4 代码

你可以在 GitHub 上找到这些代码

BankAccount.php

HeavyBankAccount.php

transactions[] = $amount;
    }

    public function getBalance(): int
    {
        // this is the heavy part, imagine all the transactions even from
        // years and decades ago must be fetched from a database or web service
        // and the balance must be calculated from it

        return array_sum($this->transactions);
    }
}

BankAccountProxy.php

balance === null) {
            $this->balance = parent::getBalance();
        }

        return $this->balance;
    }
}

2.11 注册模式

2.11.1 目的

要为整个应用程序中经常使用的对象实现中央存储,通常只使用静态方法(或使用单例模式)的抽象类来实现。请记住,这将引入全局状态,这在任何时候都应该避免!而是使用依赖注入来实现它!

2.11.2 例子

  • Zend Framework 1: Zend_Registry 持有应用的logger对象,前端控制器等。
  • Yii 框架: CWebApplication 持有所有的应用组件,如 CWebUser, CUrlManager, 等。

2.11.3 UML图

2.11.4 代码

你可以在 GitHub 上找到这些代码

Registry.php

相关文章:
PHP设计模式范例 — DesignPatternsPHP(1)创建型设计模式

你可能感兴趣的:(php,设计模式,装饰器,依赖注入)