Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding of violating LSP

I want to separate data from source of the data. One class for database interaction and class for data manipulation. But my approach violates LSP: preconditions cannot be strengthened in a subtype and raises strict error: 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?
}

How to recombine the code to fit LSP?

Update: Ok, I could rewrite methods.

class DataRepositoryItem extends DataRepositoryAbstract {
    /** @return DataItem */
    public function loadOne(){}
    /** @return DataItem[] */
    public function loadAll(){}
    public function save(DataAbstract $data) {
        assert($date instanceof DataItem);
        //...
    }               
}

Works in PHP, but still violates LSP. How to avoid it?

like image 418
sectus Avatar asked Aug 03 '15 03:08

sectus


People also ask

How do you not violate the Liskov Substitution Principle?

There are several possible ways: Returning an object that's incompatible with the object returned by the superclass method. Throwing a new exception that's not thrown by the superclass method. Changing the semantics or introducing side effects that are not part of the superclass's contract.

Why is the Liskov Substitution Principle important?

The Liskov Substitution Principle helps us model good inheritance hierarchies. It helps us prevent model hierarchies that don't conform to the Open/Closed principle. Any inheritance model that adheres to the Liskov Substitution Principle will implicitly follow the Open/Closed principle.

What is LSP Liskov Substitution Principle and what are some examples of its use good and bad )?

The Liskov Substitution Principle (LSP, lsp) is a concept in Object Oriented Programming that states: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

What is the most accurate example of Liskov Substitution Principle?

A good example here is that of a bird and a penguin; I will call this dove-penguin problem. The below is a Java code snippet showing an example that violates the LSP principle. Here, the Dove can fly because it is a Bird. In this inheritance, much as technically a penguin is a bird, penguins do not fly.


2 Answers

If your language would support generics then the problem would be quite simple to solve:

public interface Repository<T> {
    public void save(T data);
}

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

If you don't have generics then you could simply avoid trying to have a generic repository in the first place, which does more harm than good. Is there really any client code that should depend on DataRepositoryAbstract rather than a concrete repository class? If not then why forcing a useless abstraction in the design?

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

Now, if somehow all save operations can be handled in a generic way you could still implement a RepositoryBase class which is extended by all repositories without violating the LSP.

public abstract class RepositoryBase {
    protected genericSave(DataAbstract data) { ... }
}

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

However at that point you should probably use composition over inheritance by having your repositories collaborate with a GenericRepository instance:

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

PS: Please note that none of the code is actual PHP code. I'm not a PHP programmer and haven't looked-up the syntax, but you should figure it out.

like image 179
plalx Avatar answered Sep 27 '22 22:09

plalx


In any event, your inheritance hierarchy violates LSP principle, because save method and its use depends on a concrete class from an incoming object. Even if you remove a type assertion in the save method then you will not be able to use the child class DataRepositoryItem instead of parent class DataRepositoryAbstract because saving a DataItem entity differs from saving a DataAbstact entity. Let's imagine the following case of using DataRepositoryItem instead of DataRepositoryAbstract:

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

We can conclude: There is no point in declaring a save method in DataRepositoryAbstract. Save method should be declared only in the concrete repository class.

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

This inheritance hierarchy provides an ability to read data from DataRepositoryObject and DataRepositoryItem the same as from DataRepositoryAbstract.

But let me ask: Where and how do you use DataRepositoryAbstract class? I sure you use it to ensure contact between concrete repository class and another code. It means that your DataRepositoryAbstract class doesn't implement any functionality, that isn't used functionally and it is a pure interface. If my assumption is valid then you should use an interface instead of an abstract class

Interfaces:

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

Concrete implementation:

class DataRepositoryItem implements DataRepositoryItemInterface 
{       
    public function loadOne()
    {
    //...       
    }

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

    public function save(DataItem $data)
    {
    //...
    }
}
like image 37
Maksym Fedorov Avatar answered Sep 27 '22 23:09

Maksym Fedorov