【问题标题】:simplify nested if statements简化嵌套 if 语句
【发布时间】:2020-10-07 15:17:02
【问题描述】:

我正在实现搜索功能,并根据查询参数使用不同的类进行搜索。

class Search { 

    public function getResults()
    {
        if (request('type') == 'thread') {
                $results = app(SearchThreads::class)->query();
        } elseif (request('type') == 'profile_post') {
                $results = app(SearchProfilePosts::class)->query();
        } elseif (request()->missing('type')) {
                $results = app(SearchAllPosts::class)->query();
     }

}

现在当我想搜索线程时,我有以下代码。

class SearchThreads{

        public function query()
        {
            $searchQuery = request('q');
            $onlyTitle = request()->boolean('only_title');

            if (isset($searchQuery)) {
                if ($onlyTitle) {
                    $query = Thread::search($searchQuery);
                } else {
                    $query = Threads::search($searchQuery);
                }
            } else {
                if ($onlyTitle) {
                    $query = Activity::ofThreads();
                } else {
                    $query = Activity::ofThreadsAndReplies();
                }
            }
        }

}

解释代码。

如果用户输入搜索词($searchQuery)则使用Algolia进行搜索,否则直接进行数据库查询。

  • 如果用户输入搜索词

    1. 如果用户选中onlyTitle复选框,则使用Thread索引
    2. 如果用户没有选中onlyTitle复选框,请使用Threads索引
  • 如果用户没有输入搜索词

    1. 如果用户选中onlyTitle复选框,则获取所有线程
    2. 如果用户没有选中onlyTitle复选框,则获取所有话题和回复

是否有一种模式可以简化嵌套的 if 语句,或者我应该为以下情况创建一个单独的类

  1. 用户输入了搜索词
  2. 用户尚未输入搜索词

在每个类中检查用户是否选中了onlyTitle复选框

【问题讨论】:

  • 你能分享更多细节吗?毕竟这看起来不像是有效的 PHP 代码。另外,看看stackoverflow.com/questions/1804192/…
  • 奇怪。这篇文章的第一个版本没有包含任何功能部分 - class Search { 紧随其后的是 if 声明
  • 我不清楚问题是什么;听起来您对代码有外观问题?
  • 其实是的,我想避免使用所有这些 if else 语句并让代码更简洁

标签: php laravel


【解决方案1】:

我会将这段代码重构为:

保留请求参数,统一接口中的搜索方式。

interface SearchInterface
{
    public function search(\Illuminate\Http\Request $request);
}

class Search {

    protected $strategy;

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

    public function getResults(\Illuminate\Http\Request $request)
    {
        return $this->strategy->search($request);
    }
}

class SearchFactory
{
    private \Illuminate\Contracts\Container\Container $container;

    public function __construct(\Illuminate\Contracts\Container\Container $container)
    {
        $this->container = $container;
    }

    public function algoliaFromRequest(\Illuminate\Http\Request  $request): Search
    {
        $type = $request['type'];
        $onlyTitle = $request->boolean('only_title');
        if ($type === 'thread' && !$onlyTitle) {
            return $this->container->get(Threads::class);
        }

        if ($type === 'profile_post' && !$onlyTitle) {
            return $this->container->get(ProfilePosts::class);
        }

        if (empty($type) && !$onlyTitle) {
            return $this->container->get(AllPosts::class);
        }

        if ($onlyTitle) {
            return $this->container->get(Thread::class);
        }

        throw new UnexpectedValueException();
    }

    public function fromRequest(\Illuminate\Http\Request $request): Search
    {
        if ($request->missing('q')) {
            return $this->databaseFromRequest($request);
        }
        return $this->algoliaFromRequest($request);
    }

    public function databaseFromRequest(\Illuminate\Http\Request $request): Search
    {
        $type = $request['type'];
        $onlyTitle = $request->boolean('only_title');
        if ($type === 'thread' && !$onlyTitle) {
            return $this->container->get(DatabaseSearchThreads::class);
        }

        if ($type === 'profile_post' && !$onlyTitle) {
            return $this->container->get(DatabaseSearchProfilePosts::class);
        }

        if ($type === 'thread' && $onlyTitle) {
            return $this->container->get(DatabaseSearchThread::class);
        }

        if ($request->missing('type')) {
            return $this->container->get(DatabaseSearchAllPosts::class);
        }

        throw new InvalidArgumentException();
    }
}


final class SearchController
{
    private SearchFactory $factory;

    public function __construct(SearchFactory $factory)
    {
        $this->factory = $factory;
    }

    public function listResults(\Illuminate\Http\Request $request)
    {
        return $this->factory->fromRequest($request)->getResults($request);
    }
}

从中得出的结论是,在构造函数中不涉及请求是非常重要的。通过这种方式,您可以在应用程序生命周期中创建无需请求的实例。这有利于缓存、可测试性和模块化。我也不喜欢 app 和 request 方法,因为它们会凭空提取变量,降低可测试性和性能。

【讨论】:

  • 非常感谢。最后的评论。因此,每个 SearchStrategy ( DatabaseSearchThread::class, Threads::class ) 都会接受请求参数,并且每个人都必须决定是否使用它(因此所有 DatabaseSearchStrategies 根本不会使用它,但他们会接受它作为参数)对吗?最后,为什么不在 Search 类中传递 SearchFactory,所以在 SearchController 中,您只需使用 Search 类并直接调用 $search->getResults($request)。 ?
  • 是的,到处都提供请求参数。这是抽象的成本。关于搜索类中的 SearchFactory:这是一个品味问题。在我看来,控制器只负责链接逻辑操作,而不是实现它们。在这种观点中,搜索是如何搜索某物的一种实现;控制器将链接构造逻辑并启动搜索请求。在您的反建议中,这两个操作都将在 Search 类中处理。如果您对我的回答感到满意,请不要忘记将其标记为解决方案。这对我帮助很大。
【解决方案2】:
class Search 
{
     public function __construct(){
             $this->strategy = app(SearchFactory::class)->create();
         }

        public function getResults()
        {
             return $this->strategy->search();
         }
}
class SearchFactory
{
    public function create()
    {
        if (request()->missing('q')) {
            return app(DatabaseSearch::class);
        } else {
            return app(AlgoliaSearch::class);
        }

    }
}
class AlgoliaSearch implements SearchInterface
{

    
    public function __construct()
    {
        $this->strategy = app(AlgoliaSearchFactory::class)->create();
    }
    public function search()
    {
        $this->strategy->search();
    }
}
class AlgoliaSearchFactory
{

    public function create()
    {
        if (request('type') == 'thread') {
            return app(Threads::class);
        } elseif (request('type') == 'profile_post') {
            return app(ProfilePosts::class);
        } elseif (request()->missing('type')) {
            return app(AllPosts::class);
        } elseif (request()->boolean('only_title')) {
            return app(Thread::class);
        }
    }
}

AlgoliaSearchFactory 中创建的类是 algolia 聚合器,因此可以在任何这些类上调用 search 方法。

这样的东西会让它更干净甚至更糟吗?

现在我的策略听起来太多了。

【讨论】:

    【解决方案3】:

    我已经尝试为您实现一个好的解决方案,但我不得不对代码做出一些假设。

    我将请求与构造函数逻辑解耦,并给搜索接口一个请求参数。这使得意图比使用请求功能凭空提取请求更清晰。

    final class SearchFactory
    {
        private ContainerInterface $container;
    
        /**
         * I am not a big fan of using the container to locate the dependencies.
         * If possible I would implement the construction logic inside the methods.
         * The only object you would then pass into the constructor are basic building blocks,
         * independent from the HTTP request (e.g. PDO, AlgoliaClient etc.)
         */
        public function __construct(ContainerInterface $container)
        {
            $this->container = $container;
        }
    
        private function databaseSearch(): DatabaseSearch
        {
            return // databaseSearch construction logic
        }
    
        public function thread(): AlgoliaSearch
        {
            return // thread construction logic
        }
    
        public function threads(): AlgoliaSearch
        {
            return // threads construction logic
        }
    
        public function profilePost(): AlgoliaSearch
        {
            return // thread construction logic
        }
    
        public function onlyTitle(): AlgoliaSearch
        {
            return // thread construction logic
        }
    
        public function fromRequest(Request $request): SearchInterface
        {
            if ($request->missing('q')) {
                return $this->databaseSearch();
            }
    
            // Fancy solution to reduce if statements in exchange for legibility :)
            // Note: this is only a viable solution if you have done correct http validation IMO
            $camelCaseType = Str::camel($request->get('type'));
            if (!method_exists($this, $camelCaseType)) {
                // Throw a relevent error here
            }
    
            return $this->$camelCaseType();
        }
    }
    
    // According to the code you provided, algoliasearch seems an unnecessary wrapper class, which receives a search interface, just to call another search interface. If this is the only reason for its existence, I would remove it
    final class AlgoliaSearch implements SearchInterface {
        private SearchInterface $search;
    
        public function __construct(SearchInterface $search) {
            $this->search = $search;
        }
    
        public function search(Request $request): SearchInterface {
            return $this->search->search($request);
        }
    }
    

    我也不确定 Search 类的意义。如果它只是有效地将搜索方法重命名为 getResults,我不确定重点是什么。这就是我省略它的原因。

    【讨论】:

    • 可能我对问题的解释不清楚(我的意思是代码不清楚)。首先,onlyTitle 不是由 'type' 决定的,而是由 'only_title' 参数决定的。因此,当您调用 $request->get('type') 时,您不会获得 onlyTitle 的值,因此不会找到该方法。此外,当给定类型时(例如,假设 type == 'thread' )。有两种不同的策略可以根据 request('q') 搜索'thread',如果 request('q') 存在则使用 Algolia 搜索'thread',否则使用 Database 搜索'thread'。
    • 所以在你的回答中你有以下行 if ($request->missing('q')) { return $this->databaseSearch(); }。当请求('q')。丢失了,那么我必须检查 request('type') 是什么以及 request('only_title') 的值是什么才能确定 searchStrategy
    • 是的,我会继续检查阳性病例,处理它们并立即返回。这样你就只使用 if 语句,不需要减少认知负担
    • 我添加了另一个很长的答案。以防你有时间检查它,也许更好地理解我的问题。
    【解决方案4】:

    我必须写下所有这些以使问题易于理解。

    SearchFactory 接受所有必需的参数,并根据这些参数调用 AlgoliaSearchFactoryDatabaseSearchFactory 以生成最终对象,该对象将被退回。

    class SearchFactory
    {
        protected $type;
        protected $searchQuery;
        protected $onlyTitle;
        protected $algoliaSearchFactory;
        protected $databaseSearchFactory;
    
        public function __construct(
            $type,
            $searchQuery,
            $onlyTitle,
            DatabaseSearchFactory $databaseSearchFactory,
            AlgoliaSearchFactory $algoliaSearchFactory
        ) {
            $this->type = $type;
            $this->searchQuery = $searchQuery;
            $this->onlyTitle = $onlyTitle;
            $this->databaseSearchFactory = $databaseSearchFactory;
            $this->algoliaSearchFactory = $algoliaSearchFactory;
        }
    
        public function create()
        {
            if (isset($this->searchQuery)) {
                return $this->algoliaSearchFactory->create($this->type, $this->onlyTitle);
            } else {
                return $this->databaseSearchFactory->create($this->type, $this->onlyTitle);
            }
        }
    }
    

    DatabaseSearchFactory 基于从 SearchFactory 传递的 $typeonlyTitle 参数返回一个object 是为了得到结果而需要使用的最终对象。

    class DatabaseSearchFactory
    {
        public function create($type, $onlyTitle)
        {
            if ($type == 'thread' && !$onlyTitle) {
                return app(DatabaseSearchThreads::class);
            } elseif ($type == 'profile_post' && !$onlyTitle) {
                return app(DatabaseSearchProfilePosts::class);
            } elseif ($type == 'thread' && $onlyTitle) {
                return app(DatabaseSearchThread::class);
            } elseif (is_null($type)) {
                return app(DatabaseSearchAllPosts::class);
            }
        }
    }
    

    DatabaseSearchFactory

    的逻辑相同
    class AlgoliaSearchFactory
    {
        public function create($type, $onlyTitle)
        {
            if ($type == 'thread' && !$onlyTitle) {
                return app(Threads::class);
            } elseif ($type == 'profile_post' && !$onlyTitle) {
                return app(ProfilePosts::class);
            } elseif (empty($type) && !$onlyTitle) {
                return app(AllPosts::class);
            } elseif ($onlyTitle) {
                return app(Thread::class);
            }
        }
    }
    

    AlgoliaSearchFactory 创建的对象有一个方法 search 需要一个 $searchQuery

    interface AlgoliaSearchInterface
    {
        public function search($searchQuery);
    }
    

    DatabaseSearchFactory 创建的对象有一个不需要任何参数的search 方法。

    interface DatabaseSearchInterface
    {
        public function search();
    }
    

    Search 类现在将 SearchFactory 生成的最终对象作为参数,该对象可以实现 AlgoliaSearchInterfaceDatabaseSearchInterface 这就是我没有输入提示的原因

    getResults 方法现在必须找出 search 变量的类型(它实现了哪个接口)才能传递 $searchQuery 是否作为参数。

    这就是 controller 如何使用 Search 类来获取结果。 类搜索 { 受保护的$策略;

        public function __construct($search)
        {
            $this->strategy = $search;
        }
    
        public function getResults()
        {
            if(isset(request('q')))
            {
                $results = $this->strategy->search(request('q'));
            }
            else
            {
                $results = $this->strategy->search();
            }
        }
    }
    
    
    class SearchController(Search $search)
    {
        $results = $search->getResults();
    }
    

    根据@Transitive 的所有建议,这就是我想出的。我唯一无法解决的是如何在 getResults 方法中调用 search 而没有 if 语句。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-05
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多