首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >避免违反LSP

避免违反LSP
EN

Stack Overflow用户
提问于 2015-08-03 03:20:34
回答 2查看 446关注 0票数 9

我想把数据从数据来源中分离出来。一个类用于数据库交互,一个类用于数据操作。但是我的方法违反了LSPpreconditions cannot be strengthened in a subtype,并引发了严格的错误:Declaration of DataRepositoryItem::save() should be compatible with DataRepositoryAbstract::save(DataAbstract $data)

代码语言:javascript
复制
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?

更新: Ok,我可以重写方法。

代码语言:javascript
复制
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。如何避免呢?

EN

回答 2

Stack Overflow用户

发布于 2018-10-05 13:45:43

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

代码语言:javascript
复制
public interface Repository<T> {
    public void save(T data);
}

public class DataItemRepository implements Repository<DataItem> {...}

如果您没有泛型,那么您可以简单地避免一开始就尝试拥有一个泛型存储库,这样做弊大于利。真的有任何客户端代码应该依赖于DataRepositoryAbstract而不是一个具体的存储库类吗?如果不是,那为什么要在设计中强制一个无用的抽象呢?

代码语言:javascript
复制
public interface DataItemRepository {
    public DataItem loadOne();
    public DataItem[] loadAll();
    public void save(DataItem dataItem);
}

public class SqlDataItemRepository implements DataItemRepository {
  ...
}    

public interface OtherRepository {
    public Other loadOne();
    public Other[] loadAll();
    public void save(Other other);
}

现在,如果可以以通用的方式处理所有save操作,那么仍然可以实现由所有存储库扩展的RepositoryBase类,而不会违反LSP。

代码语言:javascript
复制
public abstract class RepositoryBase {
    protected genericSave(DataAbstract data) { ... }
}

public class SqlDataItemRepository extends RepositoryBase implements DataItemRepository {
    public void save(DataItem item) {
        genericSave(item);
    }
}

但是,在这一点上,您可能应该通过让存储库与GenericRepository实例协作来使用组合而不是继承:

代码语言:javascript
复制
public void save(DataItem item) {
    genericRepository.save(item);
}

PS:请注意,所有这些代码都不是实际的PHP代码。我不是一个PHP程序员,也没有查过语法,但是你应该想清楚。

票数 6
EN

Stack Overflow用户

发布于 2018-10-05 13:07:35

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

代码语言:javascript
复制
$repository = new DataRepositoryItem();
$entity = new DataAbstract()
// It causes incorrect behavior in DataRepositoryItem
$repository->save($entity);

我们可以得出结论:在DataRepositoryAbstract中声明一个保存方法是没有意义的。方法应该只在具体的存储库类中声明。

代码语言:javascript
复制
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类没有实现任何功能,这在功能上没有使用,它是一个纯接口。如果我的假设是有效的,那么您应该使用接口而不是抽象类。

接口:

代码语言:javascript
复制
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);
}

具体执行情况:

代码语言:javascript
复制
class DataRepositoryItem implements DataRepositoryItemInterface 
{       
    public function loadOne()
    {
    //...       
    }

    public function loadAll()
    {
    //...
    }

    public function save(DataItem $data)
    {
    //...
    }
}
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/31778946

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档