Laravel-扎实的原则

时间:2019-02-04 13:48:42

标签: php laravel

我想问您有关Laravel中的SOLID原则,特别是关于接口隔离原则的问题。

假设我们有一个应用程序,用户可以在其中发送订单。但是对于每个用户,我们都有不同的发送订单的方式。例如,用户一-通过电子邮件发送订单。用户二-通过xml发送的命令上传到ftp。在php中,我们可以创建界面:

<?php
interface ISendOrder
{
    public function sendOrder(); 
}

然后为每个特定用户实施它。但是在Laravel中,我们只有一个类User。假设用户具有用户类型的属性,从中可以找到sendOrder类型。在Laravel中执行此操作的最佳方法是什么?也许是一个开关/如果找到并返回正确的实现,或者有更好的方法?

3 个答案:

答案 0 :(得分:4)

这对于Strategy pattern来说是一个很好的用例。在您的示例中,用户可以通过多种方式发送订单。换句话说,多种策略。现在也是考虑将您的订单逻辑添加到Service Layer的好时机。

诚然,您的问题有点含糊,但是我将尝试举例说明如何完成此任务。因此,假设您的App\User模型将实现您的接口ISendOrder

use App/Services/Order/OrderService; 
use App/Services/Order/ISendOrder;

class User extends Authenticatable implements ISendOrder 
{
    protected $orderStrategies = [
        'standard' => App/Services/Order/Strategies/StandardStrategy::class, 
        'vip' => App/Services/Order/Strategies/VipStrategy::class
    ];

    public function sendOrder(Order $order, OrderService $service)
    {
        $service->setStrategy($this->orderStrategies[$this->type]); 

        return $service->sendOrder($this, $order); 
    }
}

您的OrderService的一个非常简单的示例如下:

class OrderService 
{
    protected $strategy; 

    public function setStrategy(OrderStrategy $strategy)
    {
        $this->strategy = $strategy; 
    }

    public function sendOrder(ISendOrder $user, Order $order)
    {
        // logic to send order 
        // $this->strategy holds the strategy to be used
    }
}

此步骤纯粹是可选的,但我可能会将订单发送逻辑封装为特征。您的模型看起来会干净很多。另外,您可以轻松地将此逻辑添加到将来可能发送订单的任何其他models上。

class User extends Authenticatable implements ISendOrder 
{
    use InteractsWithOrder;
}

您的特质看起来像这样:

trait InteractsWithOrder
{
    protected $orderStrategies = [
        'standard' => App/Services/Order/Strategies/StandardStrategy::class, 
        'vip' => App/Services/Order/Strategies/VipStrategy::class 
    ];

    public function sendOrder(Order $order, OrderService $service)
    {
        $strategy = new $this->orderStrategies[$this->type]; 

        $service->setStrategy($strategy); 

        return $service->sendOrder($this, $order); 
    }
}

这是一个非常简单的示例,用于说明如何可能完成此操作。

答案 1 :(得分:1)

请注意诸如“什么是最好的方法……”之类的主观问题,该站点实际上是针对特定问题的。但是关于您的问题,有些事情可能会有所帮助。

S 单一责任:一堂课只有一个改变的理由。如果遇到多个,请将其重构为单独的类。即,不要在同一类中融合“用户”和“消息驱动程序”的概念。

O 笔关闭:应该打开一个类以扩展或覆盖子类,但不能修改一个类本身中现有方法的输入或输出。即,不要从可能已经在使用该类的东西中抽出来。功能测试是确保重构不会意外执行的好方法。

L iskov替代:同级类应可互换。您谈到拥有不同类型的传输驱动程序。电子邮件与FTP。您的EmailDriver和FtpDriver类都应实现一个定义send()方法的接口。因此,任何使用一个的人也应该能够应付另一个。您的Adapter类既消耗了要与之通信的User和定义了通信机制的Driver,则不必关心具体细节。

界面隔离:简而言之,请保持界面苗条。如果一个类愿意,它总是可以实现多个。如果单个Interface要求对象提供太多支持,而实际需要的支持可能更多,则可能导致不必要的复杂性。

D 倾向倒置:与Liskov Substitution紧密相关。高级类应该依赖于Interfaces而不是Concrete类,以便在需要时以最少的重构就更容易交换出组件。

Laravel的Service Container完成了很多工作。 Service Providers将具体类绑定到抽象。通常,这些抽象是字符串接口名称,但实际上它们可以是任何字符串。 Facades的工作方式部分是这样。

Laravel实例化新对象的方式应始终使用Dependency Injection。通过Type Hinting(如果适用)或使用app() Helper Function

当您要求Laravel以这种方式实例化对象时,您正在做的事情就是使Container有机会与Provider中定义的类交换出您要的类。

不过,回到您的特定问题上,当涉及到指示您的代码向哪个传输驱动程序供谁使用时,如果没有更多信息,我不能肯定地说,这只是一种意见,但是您可能想看看是否Factory Pattern适合您的情况。

答案 2 :(得分:0)

我写这篇文章是希望它能清楚地阐明某人关于接口隔离的概念。

我认为,@mozammil 为您提供了 Laravel 特定案例的最佳答案。

但是如果您对这种接口隔离有清晰的概念,那么在任何编程案例中都可以使用它一样简单 - JavaPHPC#Python 或使用任何编程语言。

接口隔离:

客户/用户不应依赖于他们不能使用的接口,而应将接口分离为几个。

喜欢如果我们这样做,请检查一切是否正确。

interface Payment{
  public function initiatePayments();
  public function getPayment();
}

public class BankPayment implements Payment{
  public function initiatePayments(){
    // Do initialization
  }

  public function getPayment(){
    // Make Payments Stuff
  }
}

BankPayment 类使用两个与其相关且必要的函数。但是,如果有新的东西到来。喜欢需要添加贷款计算和检查。

interface Payment{
  public function initiatePayments();
  public function getPayment();

  public function getLoanHistory();
  public function printLoanPayments();
}

public class LoanPayment implements Payment{
  public function initiatePayments(){
    // Do initialization
  }

  public function getPayment(){
    // Make Payments Stuff
  }
  
  // Ok for this class
  public function getLoanHistory(){
    
  }

  // Ok for this class
  public function printLoanPayments(){

  }
}

public class BankPayment implements Payment{
  public function initiatePayments(){
    // Do initialization
  }

  public function getPayment(){
    // Make Payments Stuff
  }

  // Not Necessary for this class but have to implement
  public function getLoanHistory(){
    
  }

  // Not Necessary this class but have to implement
  public function printLoanPayments(){

  }
}

看看这个 LoanPayment 类,一切正常,但对于 BankPayment 类,新的两个函数是不必要的,应该在另一个接口中隔离。

这就是 SOLID 中的接口隔离原则。

要获得有关 SOLID 原则的完整概念和实际实现,您可以查看此内容 - https://devsenv.com/tutorials/solid-principle-in-depth-with-practical-example