【问题标题】:Interface injection and common classes接口注入和公共类
【发布时间】:2015-02-24 19:27:06
【问题描述】:

我正在努力了解 OOP 原则并编写自己的类。作为一种学习方式,我决定将我在 Wordpress 中编写的几个函数转换为 OOP 类。这些函数协同工作,以便根据 URL 中设置的引用者(其中 4 个)在单个页面上输出正确的帖子链接。

这是一个基本工作流程的设置(工作流程可以随着我的进展而改变):

根据档案页面为URL设置4个查询变量,即分类页面设置1个查询变量,作者页面设置1个查询变量等等。任何页面都不能有多个自定义查询变量。这 4 个变量由我的第一个类检索并对照给定的全局变量检查,在本例中为 $_GET。我没有对班级中的 4 个变量进行硬编码,这也适用于 $_GET,以保持班级可测试。如果 URL 中存在值,则通过 has* 方法返回键/值对。如果未找到匹配项,这些方法将返回 null。 (这是原始数据,将由将使用此数据的类进行清理/转义)

这是完整的课程

<?php
namespace PG\Single\Post\Navigation;

/**
 * Test set values against the super global given. Returns conditional properties
 * which is boolean values. true is returned on success and false on failure.
 *
 * @param $superGlobalVar Super global to test the values against
 * @param (string) $authorReferrer 
 * @param (string) $dateReferrer 
 * @param (string) $searchReferrer 
 * @param (string) $taxReferrer 
*/ 
class RequestReferrerHandler implements RequestReferrerHandlerInterface
{
    /**
     * @since 1.0.0
     * @access protected
     * @var (array) $superGlobalVar
    */
    protected $superGlobalVar;

    /**
     * @since 1.0.0
     * @access protected
     * @var (string) $authorReferrer
    */
    protected $authorReferrer;

    /**
     * @since 1.0.0
     * @access protected
     * @var (string) $dateReferrer
    */
    protected $dateReferrer;

    /**
     * @since 1.0.0
     * @access protected
     * @var (string) $searchReferrer
    */
    protected $searchReferrer;

    /**
     * @since 1.0.0
     * @access protected
     * @var (string) $taxReferrer
    */
    protected $taxReferrer;


    /**
     * Public constructor method.
     *
     * @param $superGlobalVar  Super global to get data from
     * @param $authorReferrer  Query variable from author referrer to test
     * @param $dateReferrer    Query variable from date referrer to test
     * @param $searchReferrer  Query variable from search referrer to test
     * @param $taxReferrer     Query variable from taxonomy referrer to test
    */
    public function __construct($superGlobalVar = null, $authorReferrer= null, $dateReferrer = null, $searchReferrer = null, $taxReferrer = null)
    {
        $this->superGlobalVar = $superGlobalVar;
        $this->authorReferrer = $authorReferrer;
        $this->dateReferrer   = $dateReferrer;
        $this->searchReferrer = $searchReferrer;
        $this->taxReferrer    = $taxReferrer;
    }

    /**
     * Setter setSuperGlobalVar.
     *
     * @since 1.0.0
     * @param $superGlobalVar
     * @return $this
     */
    public function setSuperGlobalVar($superGlobalVar)
    {
        $this->superGlobalVar = $superGlobalVar;
        return $this;
    }   

    /**
     * Returns an array of super global variables.
     *
     * @since 1.0.0
     * @return (array) $this->superGlobalVar
    */ 
    public function getSuperGlobalVar()
    {
        return $this->superGlobalVar;
    }

    /**
     * Setter setAuthorReferrer
     *
     * @since 1.0.0
     * @param $authorReferrer
     * @return $this
     */
    public function setAuthorReferrer($authorReferrer)
    {
        $this->authorReferrer = $authorReferrer;
        return $this;
    }   

    /**
     * Returns the value of the $authorReferrer property.
     *
     * @since 1.0.0
     * @return (array) $this->authorReferrer
    */ 
    public function getAuthorReferrer()
    {
        return $this->authorReferrer;
    }

    /**
     * Setter setDateReferrer.
     *
     * @since 1.0.0
     * @param $dateReferrer
     * @return $this
     */
    public function setDateReferrer($dateReferrer)
    {
        $this->dateReferrer = $dateReferrer;
        return $this;
    }   

    /**
     * Returns the value of the $dateReferrer property.
     *
     * @since 1.0.0
     * @return (array) $this->dateReferrer
    */ 
    public function getDateReferrer()
    {
        return $this->dateReferrer;
    }

    /**
     * Setter setSearchReferrer.
     *
     * @since 1.0.0
     * @param $searchReferrer
     * @return $this
     */
    public function setSearchReferrer($searchReferrer)
    {
        $this->searchReferrer = $searchReferrer;
        return $this;
    }   

    /**
     * Returns the value of the $searchReferrer property.
     *
     * @since 1.0.0
     * @return (array) $this->searchReferrer
    */ 
    public function getSearchReferrer()
    {
        return $this->searchReferrer;
    }

    /**
     * Setter setTaxReferrer.
     *
     * @since 1.0.0
     * @param $taxReferrer
     * @return $this
     */
    public function setTaxReferrer($taxReferrer)
    {
        $this->taxReferrer = $taxReferrer;
        return $this;
    }   

    /**
     * Returns the value of the $taxReferrer property.
     *
     * @since 1.0.0
     * @return (array) $this->taxReferrer
    */ 
    public function getTaxReferrer()
    {
        return $this->$taxReferrer;
    }

    /**
     * Test $authorReferrer against $superGlobalVar.
     *
     * @since 1.0.0
     * @return (bool) true on success or false on failure
     */
    public function isAuthorReferrer()
    {
        if ($this->authorReferrer && isset($this->superGlobalVar[$this->authorReferrer])) { 
            $isAuthorReferrer = true;
        } else {
            $isAuthorReferrer = false;
        }
        return $isAuthorReferrer;
    }

    /**
     * Test $authorReferrer against $superGlobalVar
     *
     * @since 1.0.0
     * @return (bool) true on success or false on failure
     */
    public function isDateReferrer()
    {
        if ($this->dateReferrer && isset($this->superGlobalVar[$this->dateReferrer])) { 
            $isDateReferrer = true;
        } else {
            $isDateReferrer = false;
        }
        return $isDateReferrer;
    }

    /**
     * Test $authorReferrer against $superGlobalVar.
     *
     * @since 1.0.0
     * @return (bool) true on success or false on failure
     */
    public function isSearchReferrer()
    {
        if ($this->searchReferrer && isset($this->superGlobalVar[$this->searchReferrer])) { 
            $isSearchReferrer = true;
        } else {
            $isSearchReferrer = false;
        }
        return $isSearchReferrer;
    }

    /**
     * Test $authorReferrer against $superGlobalVar.
     *
     * @since 1.0.0
     * @return (bool) true on success or false on failure
     */
    public function isTaxReferrer()
    {
        if ($this->taxReferrer && isset($this->superGlobalVar[$this->taxReferrer])) { 
            $isTaxReferrer = true;
        } else {
            $isTaxReferrer = false;
        }
        return $isTaxReferrer;
    }

    /**
     * Conditional which check if the current post is a referred post.
     *
     * @since 1.0.0
     * @return (bool) true on success or false on failure
     */
    public function isReferredPost()
    {
        if ($this->isAuthorReferrer() || $this->isDateReferrer() || $this->isSearchReferrer() || $this->isTaxReferrer()) {
            $isReferredPost = true;
        } else {
            $isReferredPost = false;
        }
        return $isReferredPost;
    }

    /**
     * Return the value from the super global when the current post is a post referred from
     * an author archive page.
     *
     * @since 1.0.0
     * @return (array) $authorReferrerValue
     */ 
    public function hasAuthorReferrerValue()
    {
        if ($this->isAuthorReferrer()) {
            $authorReferrerValue = [$this->authorReferrer => $this->superGlobalVar[$this->authorReferrer]];
        } else {
            $authorReferrerValue = null;
        }
        return $authorReferrerValue;
    }

    /**
     * Return the value from the super global when the current post is a post referred from
     * a date archive page.
     *
     * @since 1.0.0
     * @return (array) $dateReferrerValue
     */ 
    public function hasDateReferrerValue()
    {
        if ($this->isDateReferrer()) {
            $dateReferrerValue = [$this->dateReferrer => $this->superGlobalVar[$this->dateReferrer]];
        } else {
            $dateReferrerValue = null;
        }
        return $dateReferrerValue;
    }

    /**
     * Return the value from the super global when the current post is a post referred from
     * a search page.
     *
     * @since 1.0.0
     * @return (array) $searchReferrerValue
     */ 
    public function hasSearchReferrerValue()
    {
        if ($this->isSearchReferrer()) {
            $searchReferrerValue = [$this->searchReferrer => $this->superGlobalVar[$this->searchReferrer]];
        } else {
            $searchReferrerValue = null;
        }
        return $searchReferrerValue;
    }

    /**
     * Return the value from the super global when the current post is a post referred from
     * a taxonomy archive page.
     *
     * @since 1.0.0
     * @return (array) $taxReferrerValue
     */ 
    public function hasTaxReferrerValue()
    {
        if ($this->isTaxReferrer()) {
            $taxReferrerValue = [$this->taxReferrer => $this->superGlobalVar[$this->taxReferrer]];
        } else {
            $taxReferrerValue = null;
        }
        return $taxReferrerValue;
    }

}

这就是我使用这个类的方式

$b = new RequestReferrerHandler($_GET, 'aq', 'dq', 'sq', 'tq');
?><pre><?php var_dump($b->hasAuthorReferrerValue()); ?></pre><?php
?><pre><?php var_dump($b->hasDateReferrerValue()); ?></pre><?php
?><pre><?php var_dump($b->hasSearchReferrerValue()); ?></pre><?php
?><pre><?php var_dump($b->hasTaxReferrerValue()); ?></pre><?php

出于测试目的,您可以将 ['aq' =&gt; '1'] 之类的东西注入到类中,而不是 $_GET

这就是我现在陷入困境并且不知道如何继续前进的地方。我需要构造两个类,它们都将使用上述类中的相同方法,一个类将从上述类的 has* 方法构造查询参数,一个类还将从 has* 创建 query_vars上述类中的方法将用于构建新的帖子链接

因此,简而言之,两个类都将使用与上述类中完全相同的方法

hasAuthorReferrerValue();
hasDateReferrerValue();
hasSearchReferrerValue();
hasTaxReferrerValue();

仅作为示例,这里是两个类的外观示例。 (为了让代码更易管理,这里省略了一些方法)

A 类

<?php
namespace PG\Single\Post\Navigation;

class ClassA //Just a generic name for testing purposes. Will also implement ClassAInterface
{
    protected $handler;

    public function __construct(RequestReferrerHandlerInterface $handler)
    {
        $this->handler = $handler;
    }

    public function santizeAuthor() 
    {
        $author = $this->handler->hasAuthorReferrerValue(); // Value will be either null or single key/value pair array. Example ['aq' => '1']

        if ($author) {
            $author = array_values($author);
            $author = ['author' => (int)htmlspecialchars($author[0])]; //Will output ['author' => 1]
        }

        return $author; //Returns null or the array ['author' => 1]
    }

    public function santizeDate() 
    {
        $date = $this->handler->hasDateReferrerValue();

        if ($date) {
            // @TODO Still to work out
        }

        return $date;
    }

    //etc

    public function queryArguments() // Will be used in the controller class ClassC
    {
        $queryArgs = null;

        if ($this->santizeAuthor()) {

            $queryArgs = $this->santizeAuthor();

        } elseif ($this->santizeDate) {

            $queryArgs = $this->santizeDate();

        } // etc
        return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }

}

B 类

<?php
namespace PG\Single\Post\Navigation;

class ClassB //Just a generic name for testing purposes. Will also implement ClassBInterface
{
    protected $handler;

    public function __construct(RequestReferrerHandlerInterface $handler)
    {
        $this->handler = $handler;
    }

    public function santizeAuthor() 
    {
        $author = $this->handler->hasAuthorReferrerValue(); // Value will be either null or single key/value pair array. Example ['aq' => '1']

        if ($author) {
            foreach ($author as $k=>$v)
                $author[htmlspecialchars($k)] = (int)htmlspecialchars($v);
        }

        return $author; //Returns null or the array ['aq' => 1]
    }

    public function santizeDate() 
    {
        $date = $this->handler->hasDateReferrerValue();

        if ($date) {
            // @TODO Still to work out
        }

        return $date;
    }

    //etc

    public function queryVars() // Will be used in the controller class ClassC
    {
        $queryVars = null;

        if ($this->santizeAuthor()) {

            $queryVars = $this->santizeAuthor();

        } elseif ($this->santizeDate) {

            $queryVars = $this->santizeDate();

        } // etc
        return $queryVars; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }

}

来自ClassA 的queryArguments() 方法和来自ClassB 的queryVars() 方法将用于其他类(或一个控制器类)

我完全缺乏进入 OOP 的适当知识,对关注点分离、封装、SOLID 原则和保持类可测试感到困惑,这让我再次猜测我的代码,我确实觉得我错过了一些东西。

无论如何我可以优化上述内容。我不要求任何类型的代码重写,我所需要的只是适当的指针和想法来优化它以使其达到标准,如果不是的话。如果任何人都可以提供代码示例,例如大纲骨架,那将是一个真正的优势

【问题讨论】:

  • IMO 该帖子与 SO 无关。在 codereview.stackexchange.com 上提问,即使没有 500 赏金,您也可能得到答案
  • 当我发布这个问题时,我会更感激这个评论,为什么要等待提供赏金然后发布这样的 cmets。
  • 我没有等到。当我第一次看到这个问题时,我发表了评论。赏金已申请。
  • 我认为如果您可以将重复的函数从ClassA 和ClassB 移动到一个抽象类并从它继承,例如santizeDate()、santizeAuthor 会更好...

标签: php wordpress oop


【解决方案1】:

查看您的代码,您肯定有了一个良好的开端。在 OOP 中编程时,您已经在使用一个很好的经验法则 - 编程到接口,而不是实现。 术语 interface 我不仅仅指实际的接口,但也有抽象类。

因此,您的问题的核心是您希望有两个类,ClassA 和 ClassB,它们都使用来自 RequestReferrerHandler 的常用方法。您已经为使用您的界面RequestReferrerHandlerInterface 做好了准备工作。所以我们会说你有一个如下所示的界面:

interface RequestReferrerHandlerInterface
{
    public function hasAuthorReferrerValue();
    public function hasDateReferrerValue();
    public function hasSearchReferrerValue();
    public function hasTaxReferrerValue();
}

只要此接口由RequestReferrerHandler 实现,您就可以键入提示接口作为ClassA 和ClassB 的构造函数要求。但这并不是什么新鲜事,因为您已经在这样做了。

有两件事特别突出了我潜在的痛点。首先,由于您希望您的类的职责较小,您应该承担将数据提供给RequestReferrerHandler 的责任,并将其交给您的Controller。换句话说,不要将$_GET 注入你的班级。确保您的Controller 拥有正确创建RequestReferrerHandler 所需的所有信息让我们看看您的RequestReferrerHandler 类,其中包含它需要的所有方法。

class RequestReferrerHandler implements RequestReferrerHandlerInterface
{
    private $author;
    private $date;
    private $search;
    private $tax;

    public function __construct($author = null, $date = null, $search = null, $tax = null)
    {
        $this->setAuthorReferrer($author);
        $this->setDateReferrer($date);
        $this->setSearchReferrer($search);
        $this->setTaxReferrer($tax);
    }

    public function hasAuthorReferrerValue()
    {
        return $this->author !== null ? true : false;
    }

    public function hasDateReferrerValue()
    {
        return $this->date !== null ? true : false;
    }

    public function hasSearchReferrerValue()
    {
        return $this->search !== null ? true : false;
    }

    public function hasTaxReferrerValue()
    {
        return $this->tax !== null ? true : false;
    }

    public function getAuthorReferrer()
    {
        return $this->author;
    }

    public function getDateReferrer()
    {
        return $this->date;
    }

    public function getSearchReferrer()
    {
        return $this->search;
    }

    public function getTaxReferrer()
    {
        return $this->tax;
    }

    public function setAuthorReferrer($author)
    {
        $this->author = $author;
    }

    public function setDateReferrer($date)
    {
        $this->date = $date;
    }

    public function setSearchReferrer($search)
    {
        $this->search = $search;
    }

    public function setTaxReferrer($tax)
    {
        $this->tax = $tax;
    }
}

第二个突出的是santize() 方法。您是否看到它们在ClassA 和ClassB 中是如何重复的? sanitizeAuthor() 在两个类中是不同的,但是其余的呢?在这种情况下,DRY (不要重复自己) 原则可以提供帮助。由于多个类可能必须以类似的方式清理数据,因此将其从您的类中抽象出来是有意义的。

让我们看看如何做到这一点,然后我们将回到您的具体课程。首先创建一个新接口,该接口将指定必须由可以清理数据的对象公开的方法。

interface SanitizerInterface
{
    public function sanitizeAuthor();
    public function sanitizeDate();
    public function sanitizeSearch();
    public function sanitizeTaxonomy();
}

现在,如果您拥有的 ClassX 的每个对象都以不同的方式实现这四种方法,您就可以开始在不同的类中实现它,从而简单地清理数据。但是,对于这个例子,我们会说情况并非如此。让我们假设sanitizeAuthor() 在ClassA 和ClassB(它在您的代码中)之间可能不同,并且所有其他方法都将实现完全相同。在这种情况下,我们可以使用一个抽象类来实现 sanitizer 方法。

abstract class AbstractSanitizer implements SanitizerInterface
{
    protected $handler;

    public function __construct() {}

    public function setHandler(RequestReferrerHandlerInterface $handler)
    {
        $this->handler = $handler;
    }   

    /* For this example we are saying that sanitizeDate(), sanitizeTaxonomy() and
     * sanitizeSearch() will be the same no matter what.  So let's implement them 
     * and leave the child classes to implement sanitizeAuthor(). 
     * 
     * Implement the details of the sanitizer function to fit your needs.
     */

    public function sanitizeDate()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the date
            $sanitized = strtoupper($this->handler->getDateReferrer());
            echo "Sanitize date -> switch to uppercase letters.\n";
            $this->handler->setDateReferrer($sanitized);
        }
    }

    public function sanitizeSearch()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the search
            $sanitized = strtolower($this->handler->getSearchReferrer());
            echo "Sanitize search -> switch to lowercase letters.\n";
            $this->handler->setSearchReferrer($sanitized);
        }
    }

    public function sanitizeTaxonomy()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the taxonomy
            $sanitized = str_replace(" ", "_", $this->handler->getTaxReferrer());
            echo "Sanitize Taxonomy -> convert spaces to underscores.\n";
            $this->handler->setTaxReferrer($sanitized);
        }
    }

}

一些事情要马上注意。首先,您会注意到setHandler() 方法接受RequestReferrerHandlerInterface 的实例。为什么会有这个?大部分方便。由于我们已经采用了清理行为并将其封装到自己的类中,因此我们为清理器提供了一种方法来更新它正在使用的具体 RequestReferrerHandler 以及来自清理方法的更新输出。

接下来,我们将使用RequestReferrerHandler 类中未在RequestReferrerHandlerInterface 中指定的方法。这不是一个直接的问题本身,因为我们知道像getter 和setter 这样的方法在类中。但是,仅对接口进行类型提示并不能保证如果您决定使用不同的具体对象实现该接口,这些方法将可用。因此,我们需要更新RequestReferrerHandlerInterface 以确保其可用性。

interface RequestReferrerHandlerInterface
{
    public function hasAuthorReferrerValue();
    public function hasDateReferrerValue();
    public function hasSearchReferrerValue();
    public function hasTaxReferrerValue();
    public function getAuthorReferrer();
    public function getDateReferrer();
    public function getSearchReferrer();
    public function getTaxReferrer();
    public function setAuthorReferrer($author);
    public function setDateReferrer($date);
    public function setSearchReferrer($search);
    public function setTaxReferrer($tax);
}

现在,回到那些消毒剂。我们知道ClassA 和ClassB 会以不同的方式实现它们的sanitizeAuthor() 方法。抽象类AbstractSanitizer 之所以如此,是因为SanitizerInteface 中的sanitizeAuthor() 方法未在AbstractSanitizer 中实现,因此我们必须对其进行扩展以提供功能。我们将需要以下两个类来执行此操作:

class SanitizerForClassA extends AbstractSanitizer
{
    /* This class must provide an implementation for how ClassA will
     * handle the sanitizeAuthor() method.
     */

    public function sanitizeAuthor()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the for ClassA
            $sanitized = array("author" => $this->handler->getAuthorReferrer());
            echo "Sanitize author -> ClassA makes author an array.\n";
            $this->handler->setAuthorReferrer($sanitized);
        }   
    }
}

class SanitizerForClassB extends AbstractSanitizer
{
    /* This class must provide an implementation for how ClassB will
     * handle the sanitizeAuthor() method.
     */

    public function sanitizeAuthor()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the for ClassB
            $sanitized = new stdClass();
            $sanitized->author = $this->handler->getAuthorReferrer();
            echo "Sanitize author -> ClassB makes author an object property. \n";
            $this->handler->setAuthorReferrer($sanitized);
        }   
    }
}

这两个具体类可以与ClassA 和ClassB 一起使用,以清理将传递给它们的具体RequestReferrerHandler 方法中的数据。

继续前进,让我们看看ClassA 和ClassB 的规范。我们知道ClassA 需要queryArguments() 方法,ClassB 需要queryVars() 方法,并且两个类都需要在它们的构造函数中允许RequestReferrerHandlerInterface 和SanitizerInterface 的实例。我们将使用一个接口处理构造函数要求,然后其他两个接口将扩展该接口以提供ClassA 和ClassB 所需的所有方法要求。

interface SanitizableHandlerInterface
{       
    public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer);
}

interface QueryVarsInterface extends SanitizableHandlerInterface
{
    public function queryVars();
}

interface QueryArgumentsInterface extends SanitizableHandlerInterface
{
    public function queryArguments();
}

既然我们现在开始着手,让我们来看看那些将使用这些的类。

class ClassA implements QueryArgumentsInterface
{
    private $handler;
    private $sanitizer;

    public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
    {
        $this->handler = $handler;
        $this->sanitizer = $sanitizer;
        $this->sanitizer->setHandler($this->handler);
    }

    public function queryArguments() // Will be used in the controller class ClassC
    {
        $queryArgs = null;
        if($this->handler->hasAuthorReferrerValue())
        {
            $this->sanitizer->sanitizeAuthor();
            $queryArgs = $this->handler->getAuthorReferrer();
        }
        if($this->handler->hasDateReferrerValue())
        {
            $this->sanitizer->sanitizeDate();
            $queryArgs = $this->handler->getDateReferrer();
        }
        if($this->handler->hasSearchReferrerValue())
        {
            $this->sanitizer->sanitizeSearch();
            $queryArgs = $this->handler->getSearchReferrer();
        }
        if($this->handler->hasTaxReferrerValue())
        {
            $this->sanitizer->sanitizeTaxonomy();
            $queryArgs = $this->handler->getTaxReferrer();
        }
        return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }

}

class ClassB implements QueryVarsInterface
{
    private $handler;
    private $sanitizer;

    public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
    {
        $this->handler = $handler;
        $this->sanitizer = $sanitizer;
        $this->sanitizer->setHandler($this->handler);       
    }

    public function queryVars() // Will be used in the controller class ClassC
    {
        $queryVars = null;
        if($this->handler->hasAuthorReferrerValue())
        {
            $this->sanitizer->sanitizeAuthor();
            $queryVars = $this->handler->getAuthorReferrer();
        }
        if($this->handler->hasDateReferrerValue())
        {
            $this->sanitizer->sanitizeDate();
            $queryVars = $this->handler->getDateReferrer();
        }
        if($this->handler->hasSearchReferrerValue())
        {
            $this->sanitizer->sanitizeSearch();
            $queryVars = $this->handler->getSearchReferrer();
        }
        if($this->handler->hasTaxReferrerValue())
        {
            $this->sanitizer->sanitizeTaxonomy();
            $queryVars = $this->handler->getTaxReferrer();
        }
        return $queryVars; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }
}

你有它,基础工作已经建成。您会注意到,在构造函数中为给定的处理程序和消毒剂类设置了属性,然后为消毒剂提供了对处理程序的引用。 (请记住,清理程序对处理程序有一个引用,因此处理程序中的已清理属性会自动更新。各个类现在不需要担心。)

所以现在百万美元的问题是如何使用它。那么,您需要一个可以接受ClassA 和ClassB 的控制器。我们也将通过它们各自的接口输入提示。

class Controller
{
    public function __construct() {}

    public function doStuff(QueryArgumentsInterface $argsClass, QueryVarsInterface $varsClass)
    {
        var_dump($argsClass->queryArguments());
        var_dump($varsClass->queryVars());
    }
}

在您的queryArguments() 和queryVars() 版本中,您期望经过净化的数据作为返回值。让我们插入一些数据,看看我们得到了什么。 (注意:您已经发现我使用的任何消毒方法都没有做您正在做的事情,它们只是说明性的。)

//TEST DRIVE

//Create a controller that will use the classes
$controller = new Controller();

//Now make use of your new shiny handlers and sanitizers
$controller->doStuff(
    new ClassA(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassB())
);

$controller->doStuff(
    new ClassA(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassB())
);

$controller->doStuff(
    new ClassA(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassB())
);

$controller->doStuff(
    new ClassA(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassB())
);

这是输出:

Sanitize author -> ClassA makes author an array.
array (size=1)
  'author' => string 'Mark Twain' (length=10)
Sanitize date -> switch to uppercase letters.
string 'JANUARY 1ST, 1999' (length=17)
Sanitize search -> switch to lowercase letters.
string 'ok google now!' (length=14)
Sanitize Taxonomy -> convert spaces to underscores.
string 'Super_Awesome_Taxonomy_Tables' (length=29)
Sanitize date -> switch to uppercase letters.
string 'JANUARY 1ST, 1999' (length=17)
Sanitize author -> ClassB makes author an object property.
object(stdClass)[15]
  public 'author' => string 'Mark Twain' (length=10)
Sanitize Taxonomy -> convert spaces to underscores.
string 'Super_Awesome_Taxonomy_Tables' (length=29)
Sanitize search -> switch to lowercase letters.
string 'ok google now!' (length=14)

那么所有这些花费了您什么?简短的回答 - 复杂性。需要 4 个接口、1 个抽象类和一些具体类才能将一点点数据输出到屏幕上。

您有什么收获?简短的回答 - 灵活性。将来您可能希望添加更多实现QueryVarsInterface 或QueryArgumentsInterface 的类。考虑这些类ClassC、ClassD 和ClassE。所有这些类都需要一个 sanitizer 类来配合它们(也就是说,如果 SanitizerForClassA 或 SanitizerForClassB 不符合要求)并且继续编写 sanitizer 类会很乏味。嗯,对你来说是件好事,因为你一直在为接口编程,你不会有这个问题。您可以使用sanitizeAuthor() 方法的默认实现轻松创建GenericSanitizer。在您不需要专门的消毒剂类的任何情况下,都可以将此类与Controller::doStuff() 一起使用。您可以轻松实现 QueryArgumentInterface 或 QueryVarsInterface 的不同具体类,以测试您想要添加的实验性功能,而不会篡改您当前的类。

希望这能让您对一些 OOP 原则有所了解。这是上面所有代码的完整副本。将其放入一个空的 PHP 文件并运行它以查看所有操作。编程愉快!

    <?php

/*
 * INTERFACES
 */

interface RequestReferrerHandlerInterface
{
    public function hasAuthorReferrerValue();
    public function hasDateReferrerValue();
    public function hasSearchReferrerValue();
    public function hasTaxReferrerValue();
    public function getAuthorReferrer();
    public function getDateReferrer();
    public function getSearchReferrer();
    public function getTaxReferrer();
    public function setAuthorReferrer($author);
    public function setDateReferrer($date);
    public function setSearchReferrer($search);
    public function setTaxReferrer($tax);
}

interface SanitizerInterface
{
    public function sanitizeAuthor();
    public function sanitizeDate();
    public function sanitizeSearch();
    public function sanitizeTaxonomy();
}

interface SanitizableHandlerInterface
{       
    public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer);
}

interface QueryVarsInterface extends SanitizableHandlerInterface
{
    public function queryVars();
}

interface QueryArgumentsInterface extends SanitizableHandlerInterface
{
    public function queryArguments();
}

/*
 * ABSTRACT CLASSES
 */

abstract class AbstractSanitizer implements SanitizerInterface
{
    protected $handler;

    public function __construct() {}

    public function setHandler(RequestReferrerHandlerInterface $handler)
    {
        $this->handler = $handler;
    }   

    /* For this example we are saying that sanitizeDate(), sanitizeTaxonomy() and
     * sanitizeSearch() will be the same no matter what.  So let's implement them 
     * and leave the child classes to implement sanitizeAuthor(). 
     * 
     * Implement the details of the sanitizer function to fit your needs.
     */

    public function sanitizeDate()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the date
            $sanitized = strtoupper($this->handler->getDateReferrer());
            echo "Sanitize date -> switch to uppercase letters.\n";
            $this->handler->setDateReferrer($sanitized);
        }
    }

    public function sanitizeSearch()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the search
            $sanitized = strtolower($this->handler->getSearchReferrer());
            echo "Sanitize search -> switch to lowercase letters.\n";
            $this->handler->setSearchReferrer($sanitized);
        }
    }

    public function sanitizeTaxonomy()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the taxonomy
            $sanitized = str_replace(" ", "_", $this->handler->getTaxReferrer());
            echo "Sanitize Taxonomy -> convert spaces to underscores.\n";
            $this->handler->setTaxReferrer($sanitized);
        }
    }

}

/*
 * CONCRETE CLASSES
 */

class RequestReferrerHandler implements RequestReferrerHandlerInterface
{
    private $author;
    private $date;
    private $search;
    private $tax;

    public function __construct($author = null, $date = null, $search = null, $tax = null)
    {
        $this->setAuthorReferrer($author);
        $this->setDateReferrer($date);
        $this->setSearchReferrer($search);
        $this->setTaxReferrer($tax);
    }

    public function hasAuthorReferrerValue()
    {
        return $this->author !== null ? true : false;
    }

    public function hasDateReferrerValue()
    {
        return $this->date !== null ? true : false;
    }

    public function hasSearchReferrerValue()
    {
        return $this->search !== null ? true : false;
    }

    public function hasTaxReferrerValue()
    {
        return $this->tax !== null ? true : false;
    }

    public function getAuthorReferrer()
    {
        return $this->author;
    }

    public function getDateReferrer()
    {
        return $this->date;
    }

    public function getSearchReferrer()
    {
        return $this->search;
    }

    public function getTaxReferrer()
    {
        return $this->tax;
    }

    public function setAuthorReferrer($author)
    {
        $this->author = $author;
    }

    public function setDateReferrer($date)
    {
        $this->date = $date;
    }

    public function setSearchReferrer($search)
    {
        $this->search = $search;
    }

    public function setTaxReferrer($tax)
    {
        $this->tax = $tax;
    }
}

class SanitizerForClassA extends AbstractSanitizer
{
    /* This class must provide an implementation for how ClassA will
     * handle the sanitizeAuthor() method.
     */

    public function sanitizeAuthor()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the for ClassA
            $sanitized = array("author" => $this->handler->getAuthorReferrer());
            echo "Sanitize author -> ClassA makes author an array.\n";
            $this->handler->setAuthorReferrer($sanitized);
        }   
    }
}

class SanitizerForClassB extends AbstractSanitizer
{
    /* This class must provide an implementation for how ClassB will
     * handle the sanitizeAuthor() method.
     */

    public function sanitizeAuthor()
    {
        if($this->handler !== null)
        {
            //Perform whatever tasks to sanitize the for ClassB
            $sanitized = new stdClass();
            $sanitized->author = $this->handler->getAuthorReferrer();
            echo "Sanitize author -> ClassB makes author an object property. \n";
            $this->handler->setAuthorReferrer($sanitized);
        }   
    }
}

class ClassA implements QueryArgumentsInterface
{
    private $handler;
    private $sanitizer;

    public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
    {
        $this->handler = $handler;
        $this->sanitizer = $sanitizer;
        $this->sanitizer->setHandler($this->handler);
    }

    public function queryArguments() // Will be used in the controller class ClassC
    {
        $queryArgs = null;
        if($this->handler->hasAuthorReferrerValue())
        {
            $this->sanitizer->sanitizeAuthor();
            $queryArgs = $this->handler->getAuthorReferrer();
        }
        if($this->handler->hasDateReferrerValue())
        {
            $this->sanitizer->sanitizeDate();
            $queryArgs = $this->handler->getDateReferrer();
        }
        if($this->handler->hasSearchReferrerValue())
        {
            $this->sanitizer->sanitizeSearch();
            $queryArgs = $this->handler->getSearchReferrer();
        }
        if($this->handler->hasTaxReferrerValue())
        {
            $this->sanitizer->sanitizeTaxonomy();
            $queryArgs = $this->handler->getTaxReferrer();
        }
        return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }

}

class ClassB implements QueryVarsInterface
{
    private $handler;
    private $sanitizer;

    public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
    {
        $this->handler = $handler;
        $this->sanitizer = $sanitizer;
        $this->sanitizer->setHandler($this->handler);       
    }

    public function queryVars() // Will be used in the controller class ClassC
    {
        $queryVars = null;
        if($this->handler->hasAuthorReferrerValue())
        {
            $this->sanitizer->sanitizeAuthor();
            $queryVars = $this->handler->getAuthorReferrer();
        }
        if($this->handler->hasDateReferrerValue())
        {
            $this->sanitizer->sanitizeDate();
            $queryVars = $this->handler->getDateReferrer();
        }
        if($this->handler->hasSearchReferrerValue())
        {
            $this->sanitizer->sanitizeSearch();
            $queryVars = $this->handler->getSearchReferrer();
        }
        if($this->handler->hasTaxReferrerValue())
        {
            $this->sanitizer->sanitizeTaxonomy();
            $queryVars = $this->handler->getTaxReferrer();
        }
        return $queryVars; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }
}

class Controller
{
    public function __construct() {}

    public function doStuff(QueryArgumentsInterface $argsClass, QueryVarsInterface $varsClass)
    {
        var_dump($argsClass->queryArguments());
        var_dump($varsClass->queryVars());
    }
}

/*
 * TEST DRIVE
 */

//Create a controller that will use the classes
$controller = new Controller();

//Now make use of your new shiny handlers and sanitizers
$controller->doStuff(
    new ClassA(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassB())
);

$controller->doStuff(
    new ClassA(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassB())
);

$controller->doStuff(
    new ClassA(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassB())
);

$controller->doStuff(
    new ClassA(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassA()), 
    new ClassB(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassB())
);

【讨论】:

  • 感谢您花时间写出如此详细的答案,感谢:-)。我会在周末调查这个。从表面上看,这看起来像我正在寻找的东西
  • 很高兴为您提供帮助!如果您需要澄清任何事情,请随时提出!
  • First, you are injecting the $_GET super global into your RequestReferrerHandler class. The problem with that is if you modify the contents of that array at some point the changes are made throughout all objects that are using it. 这是不正确的。默认情况下,将数组传递给函数总是作为值而不是引用来完成。因此,您不会更改超全局 $_GET。
  • 很好,@alfallouji。尽管如果您直接操作像 $_GET 这样的全局变量,可能会发生这种情况,但您对进入函数时发生的按值传递是正确的。我已经编辑了我的答案,感谢您指出这一点。
【解决方案2】:

正如我在您的previous questions 中看到的,您正在寻找一种方法来合理化您的 OOP 开发。这就是为什么我不会给你一条鱼,但我会帮助你自己钓鱼。这意味着我将(尝试)提供你应该知道的基础来编写强大的 OOP 代码。

1. SRP 和组成

正如我在您的问题中看到的那样,您尝试将班级的责任分开。当然,这在 OOP 中是一件好事。你所做的被称为Single Responsability Principle (SRP)。这个原则意味着你更喜欢组合而不是继承。

// Composition
class Car implements VehicleInterface
{
    private $motor;
}

class Motor implements MotorInterface

在这种情况下,汽车和电机有 2 个不同的职责。

// Inheritance
class Car extends MotorVehicle
{

}

在继承的情况下,您需要在车辆和电机概念之间进行高度整合。

想象一下,我想有一个新的概念,例如运动:

// Composition
class Car implements VehicleInterface
{
    private $motor;
    private $movement;
}

class Motor implements MotorInterface

class Drive implements MovementInterface

构图没问题。

// Inheritance
class Car extends MotorVehicle, DriveVehicle
{

}

多重继承是不好的(在 PHP 中甚至是不可能的),因为你破坏了 SRP。继承只能用于分解具有相同责任的类的代码。由于您应该只按类承担一项责任,因此您不应该使用多重继承。其他可能性很糟糕,因为你不能告诉我 Car 更像是 MotorVehicle 而不是 DriveVehicle。

在您的情况下,您有一些 request referrer handlers 和一些 sanitizer。

2。接口和低耦合

正如我在previous answer 中告诉您的那样,您在使用接口在您的类之间创建low coupling 时做得对。这为您提供了更可维护、可扩展和可测试的代码。以前面的例子为例:

class Car implements VehicleInterface
{
    private $motor;
}

class PetrolMotor implements MotorInterface

class DieselMotor implements MotorInterface

您的汽车现在可以轻松搭载不同类型的电机了!

你应该想到的想法是,一个类不应该直接使用另一个类,而是一个描述行为的接口。

在您的情况下,class A 和 class B 应该实现一个接口 SanitizerInterface。

3.依赖注入

此时,您希望在消毒剂中设置处理程序。更好的方法是使用dependency injection。这真的很简单!

class Car implements VehicleInterface
{
    private $motor;

    public function __construct(MotorInterface $motor)
    {
        $this->motor = $motor;
    }
}

class PetrolMotor implements MotorInterface
{
}

class DieselMotor implements MotorInterface
{
}

$motor = new PetrolMotor();
$car = new Car($motor);

在您的情况下,您必须在 sanitizer 中注入 request referrer 处理程序。

4. SOA

将面向服务的架构 (SOA) 应用于 OOP 是合理化代码的好方法。

// Standard OOP
$car = new Car();
$buyer = new Person();
$store = new Store();

// Many solutions to buy a car!
$buyer->buy($car, $store);
// or
$store->sell($car, $buyer);
// or
// ...

在标准 OOP 中,因此您经常会遇到重复的代码。我应该在哪里编码这个方法?我现在可以在哪里(或其他人)已经编写过这种方法?这个问题以前对我来说太无聊了!我无法合理化我的发展。

// SOA
$car = new Car();
$buyer = new Person();
$store = new Store();

$saleHandler = new SaleHandler();
$saleHandler->sell($car, $buyer, $store);

在 SOA 中,您有一个“服务类”(此处为 SaleHandler)(基本上实现为 singleton),它处理“数据类”(此处为 Car、Person 和 Store )。您的数据类中没有智能(您通常只有属性的 getter 和 setter)。这样,您就知道您的销售代码在哪里!

在您的情况下,您的 request referrer handlers 和 sanitizers 似乎是某种服务,所以没关系。

结论

总之,您直观地使用了一些非常好的 OOP 实践。现在,您可以应用它们并知道为什么!

但是,我强烈建议您尝试像 Symfony2 这样的框架。它将为您提供强大的 PHP 开发基础和一个非常好的dependency injection component,允许您在配置文件中定义类的所有依赖项,以获得真正的动态代码。它还将帮助您通过其服务进行 SOA。

使用框架是促进您的发展和职业生涯的一件好事(了解框架的开发人员更受追捧)。由于 PHP 框架主要是开源的,您也可以参与其中,并为招聘人员提供良好的知名度。

【讨论】:

    【解决方案3】:

    如果你想将RequestReferrerHandler类的同一个对象用于ClassA和ClassB,那么你的策略是正确的。只需要使用RequestReferrerHandler类的对象来实例化ClassA和ClassB。然后您可以访问特定的方法。即ClassA.queryArguments() 或ClassB.queryVars()

    如果要为 ClassA 和 ClassB 创建单独的 RequestReferrerHandler 类对象,可以在不定义构造函数的情况下将 RequestReferrerHandler 类扩展为 ClassA 和 ClassB。因此,当您创建 ClassA 的对象时,它会自动继承 RequestReferrerHandler 类的构造方法,您可以通过 parent: 关键字访问该属性和方法。例如:

    class ClassA extends RequestReferrerHandler
    {
    
    public function santizeAuthor()
    {
    
        $author = parent::hasAuthorReferrerValue();  // access the base class method
    
        if ($author) {
            $author = array_values($author);
            $author = ['author' => (int)htmlspecialchars($author[0])]; //Will output ['author' => 1]
        }
    
        return $author; //Returns null or the array ['author' => 1]
    }
    
    public function santizeDate()
    {
        $date = parent::hasDateReferrerValue();
    
        if ($date) {
            // @TODO Still to work out
        }
    
        return $date;
    }
    
    //etc
    
    public function queryArguments() // Will be used in the controller class ClassC
    {
        $queryArgs = null;
    
        if ($this->santizeAuthor()) {
    
            $queryArgs = $this->santizeAuthor();
    
        } elseif ($this->santizeDate) {
    
            $queryArgs = $this->santizeDate();
    
        } // etc
        return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
    }
    
    }
    

    您可以执行与 ClassB 相同的操作。现在您可以为 ClassA 和 ClassB 创建对象,并在 Class C 中分配其基类的构造函数参数,并使用它们的对象的返回值 ClassA.queryArguments() 或 ClassB.queryVars()。

    【讨论】:

    • 感谢您的回答。我会在周末看看这个:-)
    【解决方案4】:

    如果您专注于数据类型而不是我在能够从 Wordpress 结构中操作和获取信息的 api 中工作的对象的含义,则可以简化此问题,我知道他们已经做了很好地简化了他们的数据结构。所以你只需要考虑帖子、帖子元和分类法(索引),而不是像(出版物、税收、图像、图书馆、帖子、产品)这样的含义。

    即使在 woocommerce 中,您也可以看到它非常简单。

    你会看到的;产品是一种帖子,产品的数据在帖子元中。图像是一种帖子,该图像的详细信息在 post_meta 中,新闻是帖子,该新闻的详细信息在帖子元中。

    一个结构不同的含义。因此,要访问所有内容或存储所有内容的数据,您只需要这些元素。如果您可以轻松地访问这些元素,则实现非常容易。

    我希望这可以帮助您了解我的观点,您可以不这么想。但我认为与您分享他的内容很重要。

    【讨论】:

      猜你喜欢
      • 2017-07-25
      • 2023-03-06
      • 1970-01-01
      • 2014-12-03
      • 2017-06-08
      • 1970-01-01
      • 2020-10-14
      • 1970-01-01
      • 2013-09-17
      相关资源
      最近更新 更多