避免违反LSP


Avoiding of violating LSP

我想把数据和数据源分开。一个用于数据库交互的类和一个用于数据操作的类。但我的方法违反了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?

Update: Ok,我可以重写方法

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。如何避免呢?

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

public interface Repository<T> {
    public void save(T data);
}
public class DataItemRepository implements Repository<DataItem> {...}

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

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。

public abstract class RepositoryBase {
    protected genericSave(DataAbstract data) { ... }
}
public class SqlDataItemRepository extends RepositoryBase implements DataItemRepository {
    public void save(DataItem item) {
        genericSave(item);
    }
}

然而,在这种情况下,你可能应该使用组合而不是继承,让你的存储库与GenericRepository实例协作:

public void save(DataItem item) {
    genericRepository.save(item);
}

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

在任何情况下,你的继承层次都违反了LSP原则,因为save方法和它的使用依赖于一个来自传入对象的具体类。即使在保存方法中删除了类型断言,也不能使用子类DataRepositoryItem代替父类DataRepositoryAbstract,因为保存DataItem实体不同于保存dataabstract实体。让我们想象一下以下使用DataRepositoryItem而不是DataRepositoryAbstract的情况:

$repository = new DataRepositoryItem();
$entity = new DataAbstract()
// It causes incorrect behavior in DataRepositoryItem
$repository->save($entity);

我们可以得出结论:在DataRepositoryAbstract中声明保存方法是没有意义的。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)
    {
    //...
    }
}

相关文章:
  • 没有找到相关文章