【问题标题】:Php, running code depending on type of object causes dependenciesPhp,根据对象类型运行代码会导致依赖关系
【发布时间】:2017-08-04 23:57:17
【问题描述】:

让我们创建一个动物类型列表:

abstract class Item
{
    public function run()
    {
        echo __FUNCTION__.'<br>';
    }
}

class Reptile extends Item
{
    public function putEgg()
    {
        echo __FUNCTION__.'<br>';
    }
}

class Mammal extends Item
{
    public function born()
    {
        echo __FUNCTION__.'<br>';
    }
}

$list = [];
for ($i = 1; $i <= 10; $i++)
{
    switch(mt_rand(1,2))
    {
        case 1 :
            $o = new Reptile();
            break;
        case 2 :
            $o = new Mammal();
            break;
    }
    $list[] = $o;
}

现在我想在其他地方列出它们:

class Test
{
    public function dump(array $list)
    {
        foreach ($list as $o)
        {
            /**
             * @var Item $o
             */
            echo '<hr>';
            echo get_class($o).':<br>';
            $o->run();
            if ($o instanceof Mammal)
            {
                $o->born();
            }
            if ($o instanceof Reptile)
            {
                $o->putEgg();
            }
        }
    }
}

(new Test())->dump($list);

现在我的问题是 Test 类与 Item 及其所有后代耦合。如果我这样重构整体:

abstract class Item
{
    public function run()
    {
        echo __FUNCTION__.'<br>';
    }

    public function isReptile()
    {
        return $this instanceof Reptile;
    }

    public function isMammal()
    {
        return $this instanceof Mammal;
    }
}

class Reptile extends Item
{
    public function putEgg()
    {
        echo __FUNCTION__.'<br>';
    }
}

class Mammal extends Item
{
    public function born()
    {
        echo __FUNCTION__.'<br>';
    }
}

$list = [];
for ($i = 1; $i <= 10; $i++)
{
    switch(mt_rand(1,2))
    {
        case 1 :
            $o = new Reptile();
            break;
        case 2 :
            $o = new Mammal();
            break;
    }
    $list[] = $o;
}

//
class Test
{
    public function dump(array $list)
    {
        foreach ($list as $o)
        {
            /**
             * @var Item $o
             */
            echo '<hr>';
            echo get_class($o).':<br>';
            $o->run();
            if ($o->isMammal())
            {
                $o->born();
            }
            if ($o->isReptile())
            {
                $o->putEgg();
            }
        }
    }
}

(new Test())->dump($list);

现在看起来更好一点,因为现在消除了 TestItem 依赖项。还是因为isMammal()isReptile()而闻起来很臭……这意味着每次产生新类型时,都应该更新Item。然而,基类知道其后代是一种不好的做法。优雅的方式是什么?

【问题讨论】:

  • 您需要一些具有相同名称的方法,该方法在两个子类中都可用。像makeChildren 这样的东西。更严格地说,您可以创建一个接口makeChildren,并且两个类都应该实现它。
  • 只需删除辅助方法...不是吗?在Test::dump() 中改用instanceof。最终源应该只知道它实际使用的类。您也可以考虑通过接口进行抽象。
  • 如果你不喜欢使用类耦合,那么我相信只有接口是正确的方式。
  • 因为您不需要使用精确的类。接口是一种抽象类型,需要按结构匹配,而不是精确的特定类。这意味着,无论你有多少类,它们都必须与你需要的动作兼容。它更灵活。许多框架核心组件都基于此功能构建,以提高可扩展性。
  • 如果您通过instanceof 检查类 - 只有在变量包含确切的类实例的情况下才会给出true。如果您使用instanceof 检查接口 - 它会将true 提供给任何实现精确接口的类。可能有很多类可以使用一个接口。所以它最终允许更大的能力。

标签: php dependencies


【解决方案1】:

使用界面

定义一个接口并确保所有动物都实现它。

interface Animal {
    public function born();
}

现在所有的动物都必须实现这个并实现接口中定义的功能。

class Reptile implement Animal {
  public function born()
  {
     return 'new baby reptile';
  }
}

class Mammal implement Animal {
   public function born()
   {
     return 'new baby mammal';
   }
}

class Test {
   public function makeBaby(Animal $animal)
   {
      echo $animal->born();
   }
}

(new Test())->makeBaby(new Reptile());
(new Test())->makeBaby(new Mammal());

【讨论】:

    【解决方案2】:

    我相信,您不希望 end 方法在垂直大小上增长。我建议使用接口来分析Item 结构,而不是它的类。

    <?php
    /*
     * Interfaces first.
     * They will allow us to build a "contract" between calling class and
     * actual implementations. Also, only interfaces MUST be used in end class.
     */
    interface ViviparousInterface
    {
        public function giveBirth();
    }
    
    interface OviparousInterface
    {
        public function layEgg();
    }
    
    interface SpawningInterface
    {
        public function layCaviar();
    }
    
    /*
     * Now implemetation classes:
     */
    abstract class Item
    {
        public function run()
        {
            echo __FUNCTION__ . '<br>';
        }
    }
    
    class Reptile extends Item implements OviparousInterface
    {
        public function layEgg()
        {
            echo __FUNCTION__ . '<br>';
        }
    }
    
    class Mammal extends Item implements ViviparousInterface
    {
        public function giveBirth()
        {
            echo __FUNCTION__ . '<br>';
        }
    }
    
    class Fish extends Item implements SpawningInterface
    {
        public function layCaviar()
        {
            echo __FUNCTION__ . '<br>';
        }
    }
    
    class ShomethingElse extends Item implements ViviparousInterface
    {
        public function giveBirth()
        {
            echo __FUNCTION__ . '<br>';
        }
    }
    
    /**
     * Test class:
     */
    class Test
    {
        public function dump(array $list)
        {
            foreach ($list as $o)
            {
                /**
                 * @var Item $o
                 */
                echo '<hr>', get_class($o) . ':<br>';
    
                $o->run();
    
                /*
                 * Here we do not care about actual classes.
                 * We do know, that if they implement one of the interfaces,
                 * then they will have required methods.
                 */
                if ($o instanceof ViviparousInterface) {
                    $o->giveBirth();
                } elseif ($o instanceof OviparousInterface) {
                    $o->layEgg();
                } elseif ($o instanceof SpawningInterface) {
                    $o->layCaviar();
                }
            }
        }
    }
    
    /*
     * Test case:
     */
    $list = [];
    
    for ($i = 1; $i <= 10; $i++)
    {
        switch(mt_rand(1, 4))
        {
            case 1:
                $o = new Reptile();
                break;
    
            case 2:
                $o = new Mammal();
                break;
    
            case 3:
                $o = new Fish();
                break;
    
            case 4:
                $o = new ShomethingElse();
                break;
        }
    
        $list[] = $o;
    }
    
    (new Test())->dump($list);
    

    最后,不管你将来有多少实际的Item后代,你的Test:dump()方法将只使用类结构分析。它将大大减少进一步的尺寸增长。

    进一步阅读:

    1. What is the point of interfaces in PHP?
    2. Build seven good object-oriented habits in PHP

    【讨论】:

    • 但它仍然有依赖(instanceof Interface)
    • @JohnSmith 好吧,无论如何你都会在这里拥有它们,因为你根据类或它的结构执行某种动作。在这种情况下,您将拥有更少的数量。您可以将此部分移动到其他类方法以提高透明度,但您仍然会拥有它们。 Interfaces 不是类,因此您不必增加方法的大小,当新的Item 后代将出现时。所以我认为它更适合和接受。没有?
    【解决方案3】:

    我找到了一个很好的解决方法。我将有同质列表:

    $listReptiles = [];
    $listMammals = [];
    for ($i = 1; $i <= 10; $i++)
    {
        switch(mt_rand(1,2))
        {
            case 1 :
                $listReptiles[] = new Reptile();
                break;
            case 2 :
                $listMammals[] = new Mammal();
                break;
        }
    }
    

    你怎么看?

    【讨论】:

    • 我认为您反应过度需要删除依赖项。减少依赖是好的,但最终代码依赖于其他代码。所以很明显,依赖关系无论如何都会存在。如果此解决方案符合您的要求,那么您应该按照开发人员的建议进行操作。尽管如此,在您的情况下,接口是正确的决策之一,只有您可以选择决策,这确实是根据情况需要的。祝你好运。此外,无论如何都要准备好接受后果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-25
    • 1970-01-01
    • 2011-03-22
    • 1970-01-01
    相关资源
    最近更新 更多