我想将数据与数据源分开。一个类用于数据库交互,一个类用于数据操作。但我的方法违反了LSP:
preconditions cannot be strengthened in a subtype
并引发了严格的错误:Declaration of DataRepositoryItem::save() should be compatible with DataRepositoryAbstract::save(DataAbstract $data)
class DataAbstract {
}
class DataItem extends DataAbstract {
}
class DataObject extends DataAbstract {
}
abstract class DataRepositoryAbstract {
/** @return DataAbstract */
public function loadOne(){}
/** @return DataAbstract[] */
public function loadAll(){}
public function save(DataAbstract $data){}
}
class DataRepositoryItem extends DataRepositoryAbstract {
/** @return DataItem */
public function loadOne(){}
/** @return DataItem[] */
public function loadAll(){}
public function save(DataItem $data) {} // <--- violates LSP, how to avoid it?
}
class DataRepositoryObject extends DataRepositoryAbstract {
/** @return DataObject */
public function loadOne(){}
/** @return DataObject[] */
public function loadAll(){}
public function save(DataObject $data) {} // <--- violates LSP, how to avoid it?
}
如何重新组合代码以适应LSP?
更新:好的,我可以重写方法。
class DataRepositoryItem extends DataRepositoryAbstract {
/** @return DataItem */
public function loadOne(){}
/** @return DataItem[] */
public function loadAll(){}
public function save(DataAbstract $data) {
assert($date instanceof DataItem);
//...
}
}
适用于 PHP,但仍然违反 LSP。如何避免?
如果您的语言支持泛型,那么问题就很容易解决:
interface Repository<T> {
public function save(T $data): void;
}
class DataItemRepository implements Repository<DataItem> {...}
如果您没有泛型,那么您可以首先避免尝试拥有泛型存储库,这弊大于利。是否真的有任何客户端代码应该依赖于
DataRepositoryAbstract
而不是具体的存储库类?如果不是,那为什么要在设计中强加无用的抽象呢?
interface DataItemRepository {
public function loadOne(): DataItem;
public function loadAll(): array;
public function save(DataItem $dataItem): void;
}
class SqlDataItemRepository implements DataItemRepository {
...
}
interface OtherRepository {
public function loadOne(): Other;
public function loadAll(): array;
public function save(Other $other): void;
}
现在,如果所有
save
操作都可以以通用方式处理,您仍然可以实现一个由所有存储库扩展的 RepositoryBase
类,而不会违反 LSP。
abstract class RepositoryBase {
protected function genericSave(DataAbstract $data): void { ... }
}
class SqlDataItemRepository extends RepositoryBase implements DataItemRepository {
public function save(DataItem $item): void {
$this->genericSave($item);
}
}
但是,此时您可能应该通过让存储库与
GenericRepository
实例协作来使用组合而不是继承:
class GenericRepository implements DataItemRepository {
public function save(DataAbstract $data): void {...}
}
class SqlDataItemRepository {
private GenericRepository $repository;
public function save(DataItem $item): void {
$this->repository->save(item);
}
}
$repository = new DataRepositoryItem();
$entity = new DataAbstract()
// It causes incorrect behavior in DataRepositoryItem
$repository->save($entity);
我们可以得出结论:在DataRepositoryAbstract中声明save方法是没有意义的。 Save 方法只能在具体的存储库类中声明。
abstract class DataRepositoryAbstract
{
/**
* @return DataAbstract
*/
public function loadOne(){}
/**
* @return DataAbstract[]
*/
public function loadAll(){}
}
class DataRepositoryItem extends DataRepositoryAbstract
{
/**
* @return DataItem
*/
public function loadOne(){}
/**
* @return DataItem[]
*/
public function loadAll(){}
/**
* @param DataItem
*/
public function save(DataItem $data) {}
}
class DataRepositoryObject extends DataRepositoryAbstract
{
/**
* @return DataObject
*/
public function loadOne(){}
/**
* @return DataObject[]
*/
public function loadAll(){}
/**
* @param DataObject
*/
public function save(DataObject $data) {}
}
此继承层次结构提供了从 DataRepositoryObject 和 DataRepositoryItem 读取数据的能力,就像从 DataRepositoryAbstract 读取数据一样。
但是让我问:在哪里以及如何使用 DataRepositoryAbstract 类?我确信您使用它来确保具体存储库类和另一个代码之间的联系。这意味着您的 DataRepositoryAbstract 类没有实现任何功能,没有在功能上使用,它是一个纯接口。如果我的假设是正确的,那么你应该使用接口而不是抽象类
接口:
interface BaseDataRepositoryInterface
{
/**
* @return DataAbstract
*/
public function loadOne();
/**
* @return DataAbstract[]
*/
public function loadAll();
}
interface DataRepositoryItemInterface extends BaseDataRepositoryInterface
{
/**
* @return DataItem
*/
public function loadOne();
/**
* @return DataItem[]
*/
public function loadAll();
/**
* @param DataItem $data
*/
public function save(DataItem $data);
}
interface DataRepositoryObjectInterface extends BaseDataRepositoryInterface
{
/**
* @return DataObject
*/
public function loadOne();
/**
* @return DataObject[]
*/
public function loadAll();
/**
* @param DataObject $data
*/
public function save(DataObject $data);
}
具体实现:
class DataRepositoryItem implements DataRepositoryItemInterface
{
public function loadOne()
{
//...
}
public function loadAll()
{
//...
}
public function save(DataItem $data)
{
//...
}
}