【问题标题】:Encapsulate functionalities into classes将功能封装到类中
【发布时间】:2015-04-15 04:09:57
【问题描述】:

我有一个相当大的类XYZ,它具有不同的行为,给出了不同的参数。每个参数都应该有一个帮助文本。

由于XYZ 类非常大,我想将所有参数函数封装到argumentManager 类中,并将所有帮助相关函数封装到helpManager 类中。这是可能的,因为它们具有独立于 XYZ 的功能。结果是这样的:

<?php

class ArgumentManager {

    // DO NOT CALL
    public function addArgument($option, $argumentCount) {
        ...
    }

    // Other functions MAY be called by the user
    public function isArgumentInList($option) {
        ...
    }

    ...

}

class HelpManager {

    // DO NOT CALL
    public function addHelpEntry($option, $helptext) {
        ...
    }

    // Other functions MAY be called by the user
    public function printHelpPage() {
        ...
    }

    ...

}

class XYZ {

    private $helpManager;
    private $argumentManager;

    // The user may call everything in there, EXCEPT HelpManager::addHelpEntry, since it must be a created pair by XYZ::addArgumentWithHelp
    public function getHelpManager() {
        return $this->helpManager;
    }

    // The user may call everything in there, EXCEPT ArgumentManager::addArgument, since it must be a created pair by XYZ::addArgumentWithHelp
    public function getArgumentManager() {
        return $this->argumentManager;
    }

    // Create classes which contain independent functionalities
    public function __construct() {
        $this->helpManager = new HelpManager();
        $this->argumentManager = new ArgumentManager();
    }

    // Intended usage for creating an argument with help (they should always be a couple)
    public function addArgumentWithHelp($option, $helptext, $argumentCount) {
        $this->argumentManager->addArgument($option, $argumentCount);
        $this->helpManager->addHelpEntry($option, $helptext);
    }

    // Many other functions of the big class XYZ
    .....

}

XYZ 类现在要小得多。

可以通过调用$XYZ-&gt;addArgumentWithHelp() 来添加带有帮助文本的参数。

帮助相关的功能相关的功能可以调用e.g.通过$XYZ-&gt;getHelpManager()-&gt;printHelpPage()。 Argument 相关函数也是如此。

问题是我不希望 $XYZ-&gt;getHelpManager()-&gt;addHelpEntry()$XYZ-&gt;getArgumentManager-&gt;addArgument() 被除 XYZ 之外的任何其他人调用,因为我想强制 argumentManagerhelpManager 都有关于选项。

【问题讨论】:

  • 我有点不清楚你想要的结果是什么。您是说要将 argumentManager/helpManager 的调用限制为 XYZ,还是要从类外部隐藏 argumentManager/helpManager 但仍允许 addArgument 方法?还是别的什么?
  • 可以调用 $argumentManager 和 $helpManager 中的函数,以便用户可以使用它们。唯一不应调用的函数是我用“DO NOT CALL”标记的函数。它们只能由 XYZ 类调用。在 Delphi 等其他编程语言中,我会将可见性设置为“仅单元”(单元 = 脚本文件)

标签: php oop


【解决方案1】:

在您最初提出的问题中:

由于 XYZ 类很大,我想将所有参数函数封装到一个argumentManager 类中,并将所有与帮助相关的函数封装到一个helpManager 类中。

因此,您真正需要实现的目标似乎可以只使用特征来完成,而无需任何额外的类。以下是我的结构,不包括所有代码(您可以从原始问题中剪切和粘贴):

<?php

trait ArgumentManagerTrait {

    // DO NOT CALL
    protected function addArgument($option, $argumentCount) {
        ...
    }

    // Other functions MAY be called by the user
    public function isArgumentInList($option) {
        ...
    }

    ...

}

trait HelpManagerTrait {

    // DO NOT CALL
    protected function addHelpEntry($option, $helptext) {
        ...
    }

    // Other functions MAY be called by the user
    public function printHelpPage() {
        ...
    }

    ...

}

class XYZ {

    use HelpManagerTrait, ArgumentManagerTrait;

    // Intended usage for creating an argument with help (they should always be a couple)
    public function addArgumentWithHelp($option, $helptext, $argumentCount) {
        $this->addArgument($option, $argumentCount);
        $this->addHelpEntry($option, $helptext);
    }

    // Many other functions of the big class XYZ
    .....

}

在这个解决方案中,您不需要任何额外的类或内部对象,只需使用特征封装所有内容。它更干净整洁,这是我会选择的解决方案。因为 HelpManagerTrait 和 ArgumentManagerTrait 是父类的特征,所以它们的方法(受保护的和公共的)成为父类的一部分。

【讨论】:

    【解决方案2】:

    因此您需要设置属性privateprotected,以便它们只能通过XYZ 访问:

    class XYZ 
    {
        protected $helpManager;
        protected $argumentManager;
    
        public function __construct(ArgumentManager $argumentManager, HelpManager $helpManager) {
             $this->helpManager = $helpManager;
             $this->argumentManager = $argumentManager
        }
    
        // CORRECT USAGE
        public function addArgument($option, $helptext) {
            $this->argumentManager->addArgument($option, $argumentCount);
            $this->helpManager->addHelpEntry($option, $helptext);
    
            return $this;
    
        }
    

    我觉得你的设计在这里可能会更好,但除此之外我不能说,因为它不清楚这 3 个类如何真正交互。如果我有更多信息,我可能会有其他建议/解决方案。

    【讨论】:

    • 保护 $helpManager 和 $argumentManager 并不能解决问题。用户应该在这些实例中调用函数,例如$XYZ-&gt;helpManager-&gt;printHelpPage() 。唯一不应调用的函数是我用“DO NOT CALL”标记的函数。它们只能由 XYZ 类调用。在 Delphi 等其他编程语言中,我会将可见性设置为“仅单元”(单元 = 脚本文件)
    【解决方案3】:

    听起来你在谈论 C++ 风格的友谊,其中某些类有权访问所谓的朋友类的私有和受保护成员。不幸的是,友谊并没有在 PHP 中实现。如果您正在寻找使用命名空间来改变可见性的复杂解决方法,您可以阅读这篇文章:Nested Or Inner Classes in PHP

    【讨论】:

      【解决方案4】:

      你想要的可以用特质来完成。

      将参数管理器和帮助管理器的主要部分声明为特征,将您不希望从外部调用的函数声明为受保护。

      声明真正的参数管理器并帮助管理器类使用这些特征。

      在 XYZ 内部声明了实参管理器和帮助管理器类的变体,但将受保护函数的可见性覆盖为公共。然后在 XYZ 内部,您可以将类型从真实类强制转换为变体类,并调用您需要在内部副本上调用的方法。

      抱歉,缺少代码示例,我正在平板电脑上输入此内容。如果你想要一些我可以稍后提供它们,但同时阅读关于特性的 php 手册部分。

      【讨论】:

      • 感谢您提供此信息!对不起,我不太明白。这是初始场景(演示非常简单):pastebin.com/NugSQFUj。我尝试过这种方式,但不知何故我做错了:pastebin.com/K1KJq2yJ
      • 我会给你几个使用特质的替代答案,你可以随意选择一个。
      • 请注意,在您的第二个 pastebin 中,您提供的代码将不起作用。此时: $test = (HelpManagerTrait)$test; 您正在尝试将对象类型转换为无法完成的特征。更详细地阅读特性中的手册部分:php.net/manual/en/language.oop5.traits.php
      【解决方案5】:

      我想我找到了解决方案/解决方法。使用反射,我通过在运行时更改可见性来强制调用 addArgument,这只会在 XYZ 类中发生。用户不会意外访问这些受保护的方法。

      <?php
      
      class ArgumentManager {
      
          // Will be called by "friend" class XYZ
          protected function addArgument($option, $argumentCount) {
              ...
          }
      
          // Other functions MAY be called by the user
          public function isArgumentInList($option) {
              ...
          }
      
          ...
      
      }
      
      class HelpManager {
      
          // Will be called by "friend" class XYZ
          protected function addHelpEntry($option, $helptext) {
              ...
          }
      
          // Other functions MAY be called by the user
          public function printHelpPage() {
              ...
          }
      
          ...
      
      }
      
      class XYZ {
      
          private $helpManager;
          private $argumentManager;
      
          public function getHelpManager() {
              return $this->helpManager;
          }
      
          public function getArgumentManager() {
              return $this->argumentManager;
          }
      
          // Create classes which contain independent functionalities
          public function __construct() {
              $this->helpManager = new HelpManager();
              $this->argumentManager = new ArgumentManager();
          }
      
          // Intended usage for creating an argument with help (they should always be a couple)
          public function addArgumentWithHelp($option, $helptext, $argumentCount) {
              $argMethod = new ReflectionMethod($this->argumentManager, 'addArgument');
              $argMethod->setAccessible(true);
              $argMethod->invoke($this->argumentManager, $option, $argumentCount);
      
              $helpMethod = new ReflectionMethod($this->helpManager, 'addHelpEntry');
              $helpMethod->setAccessible(true);
              $helpMethod->invoke($this->helpManager, $option, $helptext);
          }
      
          // Many other functions of the big class XYZ
          .....
      
      }
      

      【讨论】:

        【解决方案6】:

        这是另一个答案,对此我必须有点哲学。你可以像这样声明你的类和特征:

        <?php
        
        trait ArgumentManagerTrait {
        
            // DO NOT CALL
            protected function addArgument($option, $argumentCount) {
                ...
            }
        
            // Other functions MAY be called by the user
            public function isArgumentInList($option) {
                ...
            }
        
            ...
        
        }
        
        trait HelpManagerTrait {
        
            // DO NOT CALL
            protected function addHelpEntry($option, $helptext) {
                ...
            }
        
            // Other functions MAY be called by the user
            public function printHelpPage() {
                ...
            }
        
            ...
        
        }
        
        class HelpManager { use HelpManagerTrait; }
        class ArgumentManager { use ArgumentManagerTrait; }
        class HelpManagerInternal { use HelpManagerTrait { addHelpEntry as public; }
        class ArgumentManagerInternal { use ArgumentManagerTrait { addArgument as public; }
        
        class XYZ {
        
            protected $helpManager; // of class HelpManagerInternal
            protected $argumentManager; // of class ArgumentManagerInternal
        
            public function __construct(ArgumentManager $argumentManager, HelpManager $helpManager) {
                 $this->helpManager = $this->helpManagerCastToInternal($helpManager);
                 $this->argumentManager = $this->argumentManagerCastToInternal($argumentManager);
            }
        
            // Intended usage for creating an argument with help (they should always be a couple)
            public function addArgumentWithHelp($option, $helptext, $argumentCount) {
                $this->argumentManager->addArgument($option, $argumentCount);
                $this->helpManager->addHelpEntry($option, $helptext);
            }
        
            public function getHelpManager() {
                return $this->helpManagerCastToExternal($this->helpManager);
            }
        
            public function getArgumentManager() {
                return $this->argumentManagerCastToExternal($this->argumentManager);
            }
        
            // Many other functions of the big class XYZ
            .....
        
        }
        

        你会注意到我在这里省略了 4 个函数——helpManagerCastToInternal、helpManagerCastToExternal 和同样的 2 个参数管理器。您如何实现这些取决于您在问题中没有给出的 argumentManager 和 helpManager 特征/类的内部属性结构。

        我要做的就是让这两个类都继承自 Laravel\Fluent 或 Symfony\Component\HttpFoundation\ParameterBag 之类的东西。这些是框架中的类,您可以使用 composer 引入它们并为您提供一些基本功能,例如使用 setter/getter 以逻辑方式内部存储属性等。然后您的 cast 函数可以只获取一个对象的所有属性,创建另一个类型的新对象,然后将属性从第一个对象推送到第二个对象—— castToInternal 和 castToExternal 基本上是在做彼此的工作,但相反。请注意,这实际上是一个对象 copy 而不是对象 cast 因此,如果您在 helpManager 和 argumentManager 中的其他函数与内部属性混淆,您需要在某个时候将它们复制回来,这可能不是你想要的。

        或者,您可以使用此处建议的反射方法:How to Cast Objects in PHP -- 无论您的内部属性结构如何,它都可以工作。

        这实际上取决于您希望如何构建和使用类的内部属性(因此有点哲学性)。

        【讨论】:

          猜你喜欢
          • 2016-03-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-14
          • 1970-01-01
          相关资源
          最近更新 更多