У меня есть класс под названием 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
Цель
Цель состоит в том, чтобы иметь один класс (сборку), отвечающий за перечисление именованных переменных измерения, и каждый продукт знает только себя. Так
Я не думаю, что это правильное решение, но то, что вы просите, может быть достигнуто следующим образом.
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();
}
Как хорошо документировано в:
Я знаю, что уже есть принятый ответ, но я хотел бы дать более правильный подход ООП.
Фактическое решение будет работать только потому, что 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
). Представьте свой класс сборки, если вы должны кодировать специфику каждого продукта для каждого получателя в нем!