避免违反LSP

问题描述 投票:0回答:2

我想将数据与数据源分开。一个类用于数据库交互,一个类用于数据操作。但我的方法违反了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。如何避免?

php oop inheritance solid-principles liskov-substitution-principle
2个回答
7
投票

如果您的语言支持泛型,那么问题就很容易解决:

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);
    }
}

3
投票
无论如何,你的继承层次结构违反了 LSP 原则,因为 save 方法及其使用取决于传入对象的具体类。即使您在 save 方法中删除类型断言,您也将无法使用子类 DataRepositoryItem 而不是父类 DataRepositoryAbstract,因为保存 DataItem 实体与保存 DataAbstact 实体不同。让我们想象一下使用 DataRepositoryItem 而不是 DataRepositoryAbstract 的以下情况:

$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) { //... } }
    
© www.soinside.com 2019 - 2024. All rights reserved.