sec*_*tus 9 php oop inheritance liskov-substitution-principle solid-principles
我想将数据与数据源分开.一个用于数据库交互的类和用于数据操作的类.但是我的方法违反了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?
}
Run Code Online (Sandbox Code Playgroud)
如何重新组合代码以适应LSP?
更新:好的,我可以重写方法.
class DataRepositoryItem extends DataRepositoryAbstract {
/** @return DataItem */
public function loadOne(){}
/** @return DataItem[] */
public function loadAll(){}
public function save(DataAbstract $data) {
assert($date instanceof DataItem);
//...
}
}
Run Code Online (Sandbox Code Playgroud)
在PHP中工作,但仍违反LSP.怎么避免呢?
如果您的语言支持泛型,则问题解决起来非常简单:
public interface Repository<T> {
public void save(T data);
}
public class DataItemRepository implements Repository<DataItem> {...}
Run Code Online (Sandbox Code Playgroud)
如果你没有泛型,那么你可以简单地避免尝试拥有一个通用的存储库,这弊大于利.是否真的有任何客户端代码应该依赖于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);
}
Run Code Online (Sandbox Code Playgroud)
现在,如果以某种方式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);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,此时您应该通过让存储库与GenericRepository实例协作来使用组合而不是继承:
public void save(DataItem item) {
genericRepository.save(item);
}
Run Code Online (Sandbox Code Playgroud)
PS:请注意,这些代码都不是真正的PHP代码.我不是PHP程序员,也没有查找语法,但你应该弄明白.
无论如何,您的\xe2\x80\x82继承层次结构违反了LSP原则,因为save方法及其使用取决于传入对象的具体类。即使您在 save 方法中删除类型断言,您也将无法使用子类 DataRepositoryItem 而不是父类 DataRepositoryAbstract,因为保存 DataItem 实体与保存 DataAbstact 实体不同。让我们想象一下使用 DataRepositoryItem 而不是 DataRepositoryAbstract 的以下情况:
\n\n$repository = new DataRepositoryItem();\n$entity = new DataAbstract()\n// It causes incorrect behavior in DataRepositoryItem\n$repository->save($entity);\nRun Code Online (Sandbox Code Playgroud)\n\n我们可以得出结论:在 DataRepositoryAbstract 中声明 save 方法是没有意义的。Save 方法只能在具体的存储库类中声明。
\n\nabstract class DataRepositoryAbstract \n{\n /** \n * @return DataAbstract \n */\n public function loadOne(){}\n\n /** \n * @return DataAbstract[] \n */\n public function loadAll(){} \n}\n\nclass DataRepositoryItem extends DataRepositoryAbstract \n{\n /** \n * @return DataItem \n */\n public function loadOne(){}\n\n /** \n * @return DataItem[] \n */\n public function loadAll(){}\n\n /** \n * @param DataItem\n */\n public function save(DataItem $data) {}\n}\n\nclass DataRepositoryObject extends DataRepositoryAbstract \n{\n /** \n * @return DataObject \n */\n public function loadOne(){}\n\n /** \n * @return DataObject[] \n */\n public function loadAll(){}\n\n /** \n * @param DataObject\n */\n public function save(DataObject $data) {}\n}\nRun Code Online (Sandbox Code Playgroud)\n\n此继承层次结构提供了从 DataRepositoryObject 和 DataRepositoryItem 读取数据的能力,就像从 DataRepositoryAbstract 读取数据一样。
\n\n但让我问:在哪里以及如何使用 DataRepositoryAbstract 类?我确信您使用它来确保具体存储库类和另一个代码之间的联系。这意味着您的 DataRepositoryAbstract 类没有实现任何功能,没有在功能上使用,它是一个纯接口。如果我的假设是正确的,那么你应该使用接口而不是抽象类
\n\n接口:
\n\ninterface BaseDataRepositoryInterface\n{ \n /** \n * @return DataAbstract \n */\n public function loadOne();\n\n /** \n * @return DataAbstract[] \n */\n public function loadAll(); \n}\n\ninterface DataRepositoryItemInterface extends BaseDataRepositoryInterface\n{\n /** \n * @return DataItem \n */\n public function loadOne();\n\n /** \n * @return DataItem[] \n */\n public function loadAll(); \n\n /** \n * @param DataItem $data \n */\n public function save(DataItem $data);\n}\n\ninterface DataRepositoryObjectInterface extends BaseDataRepositoryInterface\n{\n /** \n * @return DataObject \n */\n public function loadOne();\n\n /** \n * @return DataObject[] \n */\n public function loadAll(); \n\n /** \n * @param DataObject $data \n */\n public function save(DataObject $data);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n具体实现:
\n\nclass DataRepositoryItem implements DataRepositoryItemInterface \n{ \n public function loadOne()\n {\n //... \n }\n\n public function loadAll()\n {\n //...\n }\n\n public function save(DataItem $data)\n {\n //...\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n