我是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我认为无论怎样,把它包括在内都有利于答案的完整性。