/ 设计模式 / 享元模式 / PHP PHP 享元模式讲解和代码示例 享元是一种结构型设计模式, 它允许你在消耗少量内存的情况下支持大量对象。 模式通过共享多个对象的部分状态来实现上述功能。 换句话来说, 享元会将不同对象的相同数据进行缓存以节省内存。 进一步了解享元模式 导航 简介 概念示例 index Output 真实世界示例 index Output 复杂度: 流行度: 使用示例: 由于 PHP 语言的特性, 享元模式很少在 PHP 中使用。 PHP 脚本通常仅需要应用的一部分数据, 从不会同时将所有数据载入内存。 识别方法: 享元可以通过构建方法来识别, 它会返回缓存对象而不是创建新的对象。 概念示例 真实世界示例 概念示例 本例说明了享元设计模式的结构并重点回答了下面的问题: 它由哪些类组成? 这些类扮演了哪些角色? 模式中的各个元素会以何种方式相互关联? 了解该模式的结构后, 你可以更轻松地理解下面基于真实世界的 PHP 应用案例。 index.php: 概念示例 <?php namespace RefactoringGuru\Flyweight\Conceptual; /** * The Flyweight stores a common portion of the state (also called intrinsic * state) that belongs to multiple real business entities. The Flyweight accepts * the rest of the state (extrinsic state, unique for each entity) via its * method parameters. */ class Flyweight { private $sharedState; public function __construct($sharedState) { $this->sharedState = $sharedState; } public function operation($uniqueState): void { $s = json_encode($this->sharedState); $u = json_encode($uniqueState); echo "Flyweight: Displaying shared ($s) and unique ($u) state.\n"; } } /** * The Flyweight Factory creates and manages the Flyweight objects. It ensures * that flyweights are shared correctly. When the client requests a flyweight, * the factory either returns an existing instance or creates a new one, if it * doesn't exist yet. */ class FlyweightFactory { /** * @var Flyweight[] */ private $flyweights = []; public function __construct(array $initialFlyweights) { foreach ($initialFlyweights as $state) { $this->flyweights[$this->getKey($state)] = new Flyweight($state); } } /** * Returns a Flyweight's string hash for a given state. */ private function getKey(array $state): string { ksort($state); return implode("_", $state); } /** * Returns an existing Flyweight with a given state or creates a new one. */ public function getFlyweight(array $sharedState): Flyweight { $key = $this->getKey($sharedState); if (!isset($this->flyweights[$key])) { echo "FlyweightFactory: Can't find a flyweight, creating new one.\n"; $this->flyweights[$key] = new Flyweight($sharedState); } else { echo "FlyweightFactory: Reusing existing flyweight.\n"; } return $this->flyweights[$key]; } public function listFlyweights(): void { $count = count($this->flyweights); echo "\nFlyweightFactory: I have $count flyweights:\n"; foreach ($this->flyweights as $key => $flyweight) { echo $key . "\n"; } } } /** * The client code usually creates a bunch of pre-populated flyweights in the * initialization stage of the application. */ $factory = new FlyweightFactory([ ["Chevrolet", "Camaro2018", "pink"], ["Mercedes Benz", "C300", "black"], ["Mercedes Benz", "C500", "red"], ["BMW", "M5", "red"], ["BMW", "X6", "white"], // ... ]); $factory->listFlyweights(); // ... function addCarToPoliceDatabase( FlyweightFactory $ff, $plates, $owner, $brand, $model, $color ) { echo "\nClient: Adding a car to database.\n"; $flyweight = $ff->getFlyweight([$brand, $model, $color]); // The client code either stores or calculates extrinsic state and passes it // to the flyweight's methods. $flyweight->operation([$plates, $owner]); } addCarToPoliceDatabase($factory, "CL234IR", "James Doe", "BMW", "M5", "red", ); addCarToPoliceDatabase($factory, "CL234IR", "James Doe", "BMW", "X1", "red", ); $factory->listFlyweights(); Output.txt: 执行结果 FlyweightFactory: I have 5 flyweights: Chevrolet_Camaro2018_pink Mercedes Benz_C300_black Mercedes Benz_C500_red BMW_M5_red BMW_X6_white Client: Adding a car to database. FlyweightFactory: Reusing existing flyweight. Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state. Client: Adding a car to database. FlyweightFactory: Can't find a flyweight, creating new one. Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state. FlyweightFactory: I have 6 flyweights: Chevrolet_Camaro2018_pink Mercedes Benz_C300_black Mercedes Benz_C500_red BMW_M5_red BMW_X6_white BMW_X1_red 真实世界示例 在我们开始前, 请注意享元模式在 PHP 中的真实应用情况非常罕见。 这源于 PHP 的单线程特性, 你不应该在同一线程中同时将应用中的所有对象载入内存。 尽管本例并非完全严谨, 且内存问题可以通过调整应用结构来解决, 但是它仍说明了在真实世界中使用该模式时的概念。 好了, 我已经做完了免责声明。 现在让我们开始吧。 在本例中, 享元模式被用于将猫类专科兽医诊所动物数据库中的对象内存用量最小化。 数据库中的每条记录都将由对象 猫来表示, 其数据由两个部分组成: 特殊 (外在) 数据 (例如宠物名字、 年龄和主人的信息)。 共享 (内在) 数据 (例如品种名称、 颜色和纹理等)。 第一部分直接存储在 猫类中作为上下文。 但第二部分则会单独存储且由好几只猫共享。 共享数据存储在 猫品种 (CatVariation) 类中。 所有包含类似特征的猫都会链接到同一个 猫品种类, 而不是在其自身对象中存储重复的数据。 index.php: 真实世界示例 <?php namespace RefactoringGuru\Flyweight\RealWorld; /** * Flyweight objects represent the data shared by multiple Cat objects. This is * the combination of breed, color, texture, etc. */ class CatVariation { /** * The so-called "intrinsic" state. */ public $breed; public $image; public $color; public $texture; public $fur; public $size; public function __construct( string $breed, string $image, string $color, string $texture, string $fur, string $size ) { $this->breed = $breed; $this->image = $image; $this->color = $color; $this->texture = $texture; $this->fur = $fur; $this->size = $size; } /** * This method displays the cat information. The method accepts the * extrinsic state as arguments. The rest of the state is stored inside * Flyweight's fields. * * You might be wondering why we had put the primary cat's logic into the * CatVariation class instead of keeping it in the Cat class. I agree, it * does sound confusing. * * Keep in mind that in the real world, the Flyweight pattern can either be * implemented from the start or forced onto an existing application * whenever the developers realize they've hit upon a RAM problem. * * In the latter case, you end up with such classes as we have here. We kind * of "refactored" an ideal app where all the data was initially inside the * Cat class. If we had implemented the Flyweight from the start, our class * names might be different and less confusing. For example, Cat and * CatContext. * * However, the actual reason why the primary behavior should live in the * Flyweight class is that you might not have the Context class declared at * all. The context data might be stored in an array or some other more * efficient data structure. You won't have another place to put your * methods in, except the Flyweight class. */ public function renderProfile(string $name, string $age, string $owner) { echo "= $name =\n"; echo "Age: $age\n"; echo "Owner: $owner\n"; echo "Breed: $this->breed\n"; echo "Image: $this->image\n"; echo "Color: $this->color\n"; echo "Texture: $this->texture\n"; } } /** * The context stores the data unique for each cat. * * A designated class for storing context is optional and not always viable. The * context may be stored inside a massive data structure within the Client code * and passed to the flyweight methods when needed. */ class Cat { /** * The so-called "extrinsic" state. */ public $name; public $age; public $owner; /** * @var CatVariation */ private $variation; public function __construct(string $name, string $age, string $owner, CatVariation $variation) { $this->name = $name; $this->age = $age; $this->owner = $owner; $this->variation = $variation; } /** * Since the Context objects don't own all of their state, sometimes, for * the sake of convenience, you may need to implement some helper methods * (for example, for comparing several Context objects.) */ public function matches(array $query): bool { foreach ($query as $key => $value) { if (property_exists($this, $key)) { if ($this->$key != $value) { return false; } } elseif (property_exists($this->variation, $key)) { if ($this->variation->$key != $value) { return false; } } else { return false; } } return true; } /** * The Context might also define several shortcut methods, that delegate * execution to the Flyweight object. These methods might be remnants of * real methods, extracted to the Flyweight class during a massive * refactoring to the Flyweight pattern. */ public function render(): string { $this->variation->renderProfile($this->name, $this->age, $this->owner); } } /** * The Flyweight Factory stores both the Context and Flyweight objects, * effectively hiding any notion of the Flyweight pattern from the client. */ class CatDataBase { /** * The list of cat objects (Contexts). */ private $cats = []; /** * The list of cat variations (Flyweights). */ private $variations = []; /** * When adding a cat to the database, we look for an existing cat variation * first. */ public function addCat( string $name, string $age, string $owner, string $breed, string $image, string $color, string $texture, string $fur, string $size ) { $variation = $this->getVariation($breed, $image, $color, $texture, $fur, $size); $this->cats[] = new Cat($name, $age, $owner, $variation); echo "CatDataBase: Added a cat ($name, $breed).\n"; } /** * Return an existing variation (Flyweight) by given data or create a new * one if it doesn't exist yet. */ public function getVariation( string $breed, string $image, $color, string $texture, string $fur, string $size ): CatVariation { $key = $this->getKey(get_defined_vars()); if (!isset($this->variations[$key])) { $this->variations[$key] = new CatVariation($breed, $image, $color, $texture, $fur, $size); } return $this->variations[$key]; } /** * This function helps to generate unique array keys. */ private function getKey(array $data): string { return md5(implode("_", $data)); } /** * Look for a cat in the database using the given query parameters. */ public function findCat(array $query) { foreach ($this->cats as $cat) { if ($cat->matches($query)) { return $cat; } } echo "CatDataBase: Sorry, your query does not yield any results."; } } /** * The client code. */ $db = new CatDataBase(); echo "Client: Let's see what we have in \"cats.csv\".\n"; // To see the real effect of the pattern, you should have a large database with // several millions of records. Feel free to experiment with code to see the // real extent of the pattern. $handle = fopen(__DIR__ . "/cats.csv", "r"); $row = 0; $columns = []; while (($data = fgetcsv($handle)) !== false) { if ($row == 0) { for ($c = 0; $c < count($data); $c++) { $columnIndex = $c; $columnKey = strtolower($data[$c]); $columns[$columnKey] = $columnIndex; } $row++; continue; } $db->addCat( $data[$columns['name']], $data[$columns['age']], $data[$columns['owner']], $data[$columns['breed']], $data[$columns['image']], $data[$columns['color']], $data[$columns['texture']], $data[$columns['fur']], $data[$columns['size']], ); $row++; } fclose($handle); // ... echo "\nClient: Let's look for a cat named \"Siri\".\n"; $cat = $db->findCat(['name' => "Siri"]); if ($cat) { $cat->render(); } echo "\nClient: Let's look for a cat named \"Bob\".\n"; $cat = $db->findCat(['name' => "Bob"]); if ($cat) { $cat->render(); } Output.txt: 执行结果 Client: Let's see what we have in "cats.csv". CatDataBase: Added a cat (Steve, Bengal). CatDataBase: Added a cat (Siri, Domestic short-haired). CatDataBase: Added a cat (Fluffy, Maine Coon). Client: Let's look for a cat named "Siri". = Siri = Age: 2 Owner: Alexander Shvets Breed: Domestic short-haired Image: /cats/domestic-sh.jpg Color: Black Texture: Solid Client: Let's look for a cat named "Bob". CatDataBase: Sorry, your query does not yield any results. 概念示例 真实世界示例