oop — Как PHP-код может быть осведомлен о конкретных подтипах класса, передаваемых ему, при использовании полиморфизма?

У меня есть класс под названием Assembly что скрывает реализацию основных продуктов. ProductA может иметь один набор добытчиков, ProductB может иметь другое.

Хотя PHP довольно простителен, и если я не перепутаю Продукты и получатели, мой код будет работать, но не будет никакой защиты, когда я все перепутываю и заполняю Assembly с ProductA, но затем использовать геттер из ProductB,

Кроме того, моя среда IDE знает методы любого из классов, поэтому автоматическое заполнение не предлагается, когда я работаю внутри Assembly с получателями от любого Product,

Я хочу больше пуленепробиваемого кода, где Assembly знает или же знает о который Product подтип он в настоящее время хостов. Есть ли способ сделать это?

Образец ниже

class Assembly
{
private $product;

public function setProduct(Product $product) {
$this->product = $product;
}

public function getA() {
return $this->product->getA();
}

public function getB() {
return $this->product->getB();
}
}

class Product { }

class ProductA extends Product
{
public function getA() {
return "A";
}
}

class ProductB extends Product
{
public function getB() {
return "B";
}
}

//set assembly
$assembly = new Assembly();

//define product sub-type
$product = new ProductB();

//place product into assembly
$assembly->setProduct($product);

//assembly has no clue which subtype it has
print $assembly->getB(); // "B"print $assembly->getA(); // prints nothing

Предложение интерфейса

interface CompositeProductInterface
{
public function getA();
public function getB();
}

//update code in Assembly to:
public function setProduct(CompositeProductInterface $product)
{
$this->product = $product;
}

Это дает мне автозаполнение в моей IDE, что приятно, но не решает другие мои проблемы. Кроме того, я немного жутко собираю вещи в единый «составной» продукт… Это больше похоже на обходной путь. В моем конкретном случае у меня есть два очень похожих продукта, которые отличаются по некоторым мелочам, таким как несколько свойств.

Пример из реального мира

Обе модели продукта A и B имеют технический чертеж, в котором перечислены переменные для определения различных размеров продукта. Названные размеры одинаковы для обоих продуктов. Например, M1 для продукта A означает тот же физический размер, что и для продукта B. Однако продукт A имеет характеристики, отсутствующие в продукте B, и наоборот.

Если это все еще кажется слишком гипотетическим, фактические размеры на фактическом чертеже будут перечислены как Би 2, а также B3. Эти размеры (B2, B3) отсутствуют в другой модели продукта. Я хочу быть в состоянии пойти использовать конструкцию сборки, чтобы перечислить переменные на чертеже, в том числе M1, B2, B3 и так далее. Я мог бы сделать это с

print $assembly->getM1(); //works for both products
print $assembly->getB2(); //works for one product only
print $assembly->getB3(); //same as above

Цель

Цель состоит в том, чтобы иметь один класс (сборку), отвечающий за перечисление именованных переменных измерения, и каждый продукт знает только себя. Так

  • Ассамблея говорит: «Я знаю все об именованных переменных, которые хочу перечислить (M1, B2, B3), независимо от модели продукта»
  • Продукт А говорит: «Я знаю только то, что в себе содержится. У меня есть именованное измерение А. Я не знаю, что такое Б, и мне наплевать
  • Продукт B говорит: «Я знаю только B» …

5

Решение

Я не думаю, что это правильное решение, но то, что вы просите, может быть достигнуто следующим образом.

get_class($product);

Скажет вам, какой класс $product является. Что приведет к следующему коду:

private $product;
private $productClass;

public function setProduct(Product $product) {
$this->product = $product;
$this->productClass = get_class($product);
}

Или же:

public function getProductClass(){
return get_class($this->$product);
}

Что может привести к проверке, аналогичной:

public function getA() {
if($this->productClass === "ProductA")
return $this->product->getA();
}

Как хорошо документировано в:

http://php.net/manual/en/function.get-class.php

1

Другие решения

Я знаю, что уже есть принятый ответ, но я хотел бы дать более правильный подход ООП.

Фактическое решение будет работать только потому, что PHP использует реальный тип и не ограничивает область объекта от типа сигнатуры метода.

Чтобы избежать расхождений в сложности, я бы не рекомендовал код, в котором вы должны добавлять какой-то код в класс (свою сборку) каждый раз, когда добавляете другой (новый тип продукта).


В вашем случае я бы использовал шаблонный адаптер.

Вот ваши адаптеры:

interface AssemblyInterface
{
function getA();
function getB();
}

class AssemblyA implements AssemblyInterface
{
private $product;

public function setProduct(ProductA $product) {
$this->product = $product;
}

public function getA() {
return $this->product->getA();
}

public function getB() {
return;
}
}

class AssemblyB implements AssemblyInterface
{
private $product;

public function setProduct(ProductB $product) {
$this->product = $product;
}

public function getA() {
return;
}

public function getB() {
return $this->product->getB();
}
}

Вот ваши приспешники:

class ProductA
{
public function getA() {
return "A";
}
}

class ProductB
{
public function getB() {
return "B";
}
}

Теперь вы можете использовать фабрика узоров чтобы получить ваши сборки.

Вот ваши создатели:

interface AssemblyCreatorInterface
{
function isSupportingProduct($product);
function createAssembly($product);
}

class AssemblyCreatorA implements AssemblyCreatorInterface
{
public function isSupportingProduct($product) {
return $product instanceof ProductA;
}

public function createAssembly($product) {
return new AssemblyA($product);
}
}

class AssemblyCreatorB implements AssemblyCreatorInterface
{
public function isSupportingProduct($product) {
return $product instanceof ProductB;
}

public function createAssembly($product) {
return new AssemblyB($product);
}
}

Вот вам сборочный завод:

class AssemblyFactory
{
private $assemblyCreators = array();

public function addCreator(AssemblyCreatorInterface $assemblyCreator) {
$this->assemblyCreators = $assemblyCreator;
}

public function get($product) {
// Find the good creator for your product.
foreach ($this->assemblyCreators as $assemblyCreator) {
if ($assemblyCreator->isSupportingProduct($product)) {
// Create and return the assembly for your product.
return $assemblyCreator->createAssembly($product);
}
}

throw new \InvalidArgumentException(sprintf(
'No available assembly creator for product "".',
get_class($product);
));
}
}

Наконец, вот пример программы:

// Process dependency injection.
// (you should be able to do it only once in your code)
$assemblyCreatorA = new AssemblyCreatorA();
$assemblyCreatorB = new AssemblyCreatorB();

$assemblyFactory = new AssemblyFactory();
$assemblyFactory->addCreator($assemblyCreatorA);
$assemblyFactory->addCreator($assemblyCreatorB);

// Retrieve assemblies from products.
$productA = new ProductA();
$productB = new ProductB();

$assemblyA = $assemblyFactory->get($productA);
$assemblyB = $assemblyFactory->get($productB);

$assemblyA->getA(); // 'A'
$assemblyA->getB(); // null
$assemblyB->getA(); // null
$assemblyB->getB(); // 'B'

Это немного больше кода, но он учитывает сигнатуры ваших методов и будет обрабатывать растущую сложность вашего приложения. С этим решением, если у вас есть 100 различных типов продуктов, каждый из них будет независим от других (один класс Assembly, один урок Product и один класс Creator). Представьте свой класс сборки, если вы должны кодировать специфику каждого продукта для каждого получателя в нем!

0

По вопросам рекламы [email protected]