接口注入和通用类

Interface injection and common classes

我正在努力了解 OOP 原则并编写自己的 classes。作为学习的一种方式,我决定将我在 Wordpress 中编写的几个函数转换为 OOP classes。这些功能协同工作,以便根据 URL.

中设置的引荐来源网址(其中 4 个)在单个页面上输出正确的 post 链接

这是具有基本工作流程的设置(工作流程可以随着我的进行而改变):

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

这是完整的class

<?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;
    }

}

我就是这样使用的class

$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' => '1'] 之类的内容注入 class 而不是 $_GET

这就是我现在被困的地方,不知道如何继续前进。我需要构造两个 classes,它们都将使用上面 class 中的相同方法,一个 class 将从上面 has* 方法构造查询参数 class,一个 class 也将从上述 class 中的 has* 方法创建 query_vars,将用于构建新的 post 链接

所以,简而言之,两个 classes 将对上述 class

中的方法使用完全相同的方法
hasAuthorReferrerValue();
hasDateReferrerValue();
hasSearchReferrerValue();
hasTaxReferrerValue();

举个例子,下面是两个 class 应该是什么样子的例子。 (为了代码更易管理,这里省略了一些方法

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
    }

}

来自ClassAqueryArguments()方法和来自ClassBqueryVars()方法将用于其他classes(或一个控制器class)

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

有没有我可以优化上面的。我不要求重写任何类型的代码,我所需要的只是适当的指导和想法来优化它以使其达到标准(如果不是)。如果有人能提供代码示例,比如大纲骨架,那将是一个真正的优势

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

如果你想为 ClassA 和 ClassB 创建单独的 RequestReferrerHandler class 对象,你可以扩展 RequestReferrerHandler class 到 ClassA 和 ClassB 而无需定义构造函数。所以,当你创建ClassA的对象时,它会自动继承RequestReferrerHandlerclass的构造方法,你可以通过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 中分配其基数 class 的构造函数参数,并使用 return 的值 ClassA.queryArguments()ClassB.queryVars() 来自他们的对象。

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

所以在你的问题的核心,你想要有两个 classes,ClassAClassB,它们都使用 RequestReferrerHandler 中的常用方法。您已经为您的界面 RequestReferrerHandlerInterface 做好了准备。所以我们会说你有一个看起来像这样的界面:

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

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

有两件事特别让我印象深刻,因为它们可能会让人大跌眼镜。首先,既然你希望你的classes的职责很小,你就应该承担起为RequestReferrerHandler提供数据的责任,远离它自己,交给你的Controller。换句话说,不要将 $_GET 注入到你的 class 中。确保您的 Controller 拥有正确创建 RequestReferrerHandler 所需的所有信息 让我们来看看您的 RequestReferrerHandler class,其中包含了它需要的所有方法。

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() 方法。您看到它们在 ClassAClassB 中是如何重复的了吗? sanitizeAuthor() 在两个 class 中是不同的,但其余的呢?在这种情况下,DRY (Don't Repeat Yourself) 原则可以提供帮助。由于多个 classes 可能必须以类似的方式清理数据,因此将其从 classes 中抽象出来是有意义的。

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

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

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

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 的实例。为什么会有这个?大多数情况下方便。由于我们已经采取了清理行为并将其封装到它自己的 class 中,如果我们为清理程序提供了一种方法来更新它正在使用的具体 RequestReferrerHandler 以及来自清理方法的更新输出,那就太好了。

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

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

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);
        }   
    }
}

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

那么继续,让我们看看 ClassAClassB 的规范。我们知道 ClassA 需要方法 queryArguments()ClassB 需要方法 queryVars() 并且两个 class 都需要允许 RequestReferrerHandlerInterfaceSanitizerInterface 在它们的构造函数中。我们将使用一个接口处理构造函数需求,然后另外两个接口将扩展该接口以提供 ClassAClassB 所需的所有方法需求。

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

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

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

既然我们现在要开始了,让我们来看看那些将使用这些的 classes。

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 设置了属性,然后向消毒程序提供了对处理程序的引用。 (请记住,清理程序有一个对处理程序的引用,因此处理程序中经过清理的属性会自动更新。个人 class 现在不需要担心这个。)

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

class Controller
{
    public function __construct() {}

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

在您的 queryArguments()queryVars() 版本中,您期望 return 值的净化数据。让我们插入一些数据,看看我们得到了什么。 (注意:正如您已经了解到我使用的 none 消毒方法正在做您正在做的事情,它们只是说明性的。)

//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 个抽象 class 和一些具体的 classes 将一点点数据输出到屏幕。

您获得了什么? 简短回答 - 灵活性。将来您可能希望添加更多实现 QueryVarsInterfaceQueryArgumentsInterface 的 classes。考虑这些 classes ClassCClassDClassE。所有这些 classes 都需要一个消毒剂 class 来搭配它们(也就是说,如果 SanitizerForClassASanitizerForClassB 不符合要求)并且保留它们会很乏味必须编写消毒剂 classes。好吧,对你来说是件好事,因为你一直都是针对接口编程,所以你不会有这个问题。您可以使用 sanitizeAuthor() 方法的默认实现轻松创建 GenericSanitizer。在不需要专门的消毒剂 class 的任何情况下,都可以将此 class 与 Controller::doStuff() 一起使用。您可以轻松实现 QueryArgumentInterfaceQueryVarsInterface 的不同具体 classes 来测试您想要添加的实验性功能,而不会篡改您当前的 classes。

希望这能让您对某些 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())
);

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

1. SRP 和构成

正如我在您的问题中看到的那样,您试图将 class 的责任分开。这在 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。继承只能用于具有相同职责的 classes 的分解代码。由于 class 你应该只有一个责任,你不应该使用多重继承。其他可能性是不好的,因为你不能告诉我 Car 比 DriveVehicle.

MotorVehicle

In your case, you have some request referrer handlers and some sanitizer.

2。接口和低耦合

正如我在你们 class 之间的 your do right when using interfaces to make a low coupling 中告诉你们的那样。这为您提供了更易于维护、可扩展和可测试的代码。拿前面的例子来说:

class Car implements VehicleInterface
{
    private $motor;
}

class PetrolMotor implements MotorInterface

class DieselMotor implements MotorInterface

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

这里应该驱使你的想法是,class 不应该直接使用另一个 class,而是一个描述行为的接口。

In your case, class A and class B should implement an interface 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);

In your case, you have to inject the request referrer handler in your sanitizer.

4. SOA

应用于 OOP 的面向服务的体系结构 (SOA) 是使您的代码合理化的好方法。

// 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 中,您有一个 "service class"(此处为 SaleHandler)(基本上实现为 singleton),它处理 "data classes"(此处为 CarPersonStore)。您的数据 classes 中没有智能(您通常只有属性的 getter 和 setter)。这样,您就知道您的销售代码在哪里了!

In your case, it seems that your request referrer handlers and sanitizers are some kind of services, so it's ok.

结论

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

但是,我强烈建议您尝试使用像 Symfony2. It will provide you with a strong base for you PHP development and a really nice dependency injection component 这样的框架,它允许您在配置文件中定义 classes 的所有依赖项,以获得真正的动态代码。它还将帮助您使用其服务进行 SOA。

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

如果你专注于数据类型而不是对象的含义,这个问题可以得到简化他们已经在简化数据结构方面做得很好。所以你只需要考虑 post、post 元和分类法(索引)的热量,而不是像(出版、税收、图像、图书馆、post、产品)这样的含义。

即使在woo commerce中你也可以看到它非常简单。

你会看到的;产品是 post 的类型,产品的数据在 post 元中。图像是 post 的类型,该图像的详细信息在 post_meta 中,新闻是 post 并且该新闻的详细信息在 post 元中。

一个结构不同的意思。因此,要访问所有内容或存储所有内容的数据,您只需要这些元素。如果您能够以 restful 的方式访问此元素,则实施起来非常容易。

我希望这对您有所帮助,您可以不这么想。但我认为与您分享他的想法很重要。