以数据库驱动的应用程序为例,将面向对象理论与实践联系起来


Making the connection between OOP theory and practice with a database driven app example

我是OOP的新手,我想让Silex试试我正在尝试的一个小应用程序。关于我的设计是否符合良好的面向对象原则,我正在寻求一些建议。

我有一个User对象,它基本上只是一堆属性、getter和setter。然后,我有一个UserService对象,它将包含用于验证用户、从数据库中获取用户、设置或更新用户信息等的逻辑。我还有一个UserServiceProvder类,它在那里为应用程序提供UserService类的实例(这似乎是在Silex中创建可重用代码块的最佳方式)。

我现在的问题是:我正在使用Silex附带的Doctrine DBAL,当我实例化UserService类时,我很想传入对Doctrine对象的引用,然后将对该对象的硬代码调用传递到UserService类的方法中。

例如,要通过id从数据库返回User,我可能会创建一个名为getUserById($id)的方法,然后将Doctrine准备的语句硬编码到该方法中,以从数据库中选择该用户,然后返回一个User对象。

对我来说,创建一个完全其他的服务,只是对条令DBAL的进一步抽象,并在实例化它时将其传递给UserService,这会更好吗?这样,我就可以将准备好条令的语句硬编码到该类中,使我的UserService类更加封装和可重用,以防我将来决定放弃条令。

我想我很难意识到OOP中是否存在过度使用的问题。在我看来,第二种方法更容易重复使用,但它是必要的还是明智的?

将数据库访问转移到一个单独的类将带来一些优势。首先,如果将数据库访问与逻辑的其他部分分开,那么可以更容易地替换数据库访问的实现。如果出于某种原因,你想放弃Doctrine DBAL,你会很高兴所有的代码只是引用存储库的某个接口,而不是直接查询数据库。

第二个很大的优点是,您可以在分离数据库访问逻辑的情况下测试应用程序逻辑。如果您在UserService中为用户注入了一个Repository,那么您可以在测试中模拟它,并确保它们只有在实际应用程序逻辑出现问题时才会失败。

的一个小例子

该接口便于在整个代码库中参考。没有代码引用实现,只有接口。这样你就可以很容易地替换接口的实现,而不必接触它所使用的所有地方:

interface IUserRepository
{
  /**
   * @return User
   */
  public function getUserById($userId); 
}

当然,您确实需要实现上述接口。这就是您在UserService中注入的内容。这就是你有一天可能会用另一种接口实现取代的东西:

class DoctrineDBALUserRepository implements IUserRepository
{
  /**
   * @return User
   */
  public function getUserById($userId)
  {
    //implementation specific for Doctrine DBAL
  }
}

UserService只知道接口,可以自由使用。为了避免在代码中的许多地方注入UserRepository,可以创建一个方便的构建方法。请注意引用接口的构造函数和注入该接口的实现构建方法:

class UserService 
{
  private $UserRepository;
  public static build()
  {
    return new UserService(new DoctrineDBALUserRepository());
  }
  public function __construct(IUserRepository $UserRepository)
  {
    $this->UserRepository = $UserRepository;
  }
  public function getUserById($userId)
  {
    if ($User = $this->UserRepository->getUserById($userId) {
      return $User;
    }
    throw new RuntimeException('O noes, we messed up');
}

有了这一点,您可以为业务逻辑编写测试(例如,如果保存失败,则抛出异常):

public function UserServiceTest extends PHPUnit_Framework_TestCase
{
  public function testGetUserById_whenRetrievingFails_shouldThrowAnException()
  {
    $RepositoryStub = $this->getMock('IUserRepository');
    $RepositoryStub->expects($this->any())->method('getUserById')->will($this->returnValue(false);
    $UserService = new UserService($RepositoryStub);
    $this->setExpectedException('RuntimeException');
    $UserService->getUserById(1);
  }
}

我可以想象,如果你还没有进入单元测试,你还不熟悉最后一段代码。我希望你是,如果不是的话,我也敦促你读一读:D我认为无论怎样,把它包括在内都有利于答案的完整性。