web-dev-qa-db-fra.com

Le modèle de référentiel dans une architecture propre ne viole-t-il pas le principe d'inversion des dépendances?

D'après ce que j'ai lu et vu, une architecture propre suppose que certaines entités ne connaissent rien à la persistance, bien que sur la même couche où elles résident, il puisse y avoir une interface qui a pour but d'être un contrat sur la façon dont l'entité doit être lue/mise à jour./supprimé.

// project Core
public class CarModel
{
    public string Name { get; set; }

    public void SomeImportantBehavior {}
}

public interface ICarModelRepository 
{
    CarModel FindByName(string name);
    CarModel Add(CarModel carModel);
}

Pendant ce temps, une autre couche tiendra la mise en œuvre de l'interface:

// project Infrastructure or Persistence
public class CarModelRepository : ICarModelRepository
{
    public CarModel FindByName(string name)
    {
        return new CarModel { Name = name }; // the implementation is not really important here
    }

    public CarModel Add(CarModel carModel) {
        // somehow persist it here
        return carModel;
    }
}

J'ai donc une question: l'implémentation du référentiel ne viole-t-elle pas le principe DiP? Puisqu'elle dépend non seulement de l'abstraction mais aussi de l'implémentation concrète (CarModel dans ce cas)?

Un autre exemple de ceci est ici .

P.S. CarModel est un modèle de domaine avec un comportement

5
arthur.borisow

L'idée centrale du DIP est d'éliminer toute dépendance directe entre les modules de niveau supérieur et les modules de niveau inférieur. Pour ce faire, une interface abstraite et stable est placée entre eux, dont les deux couches dépendent à la place.

Dans le modèle de référentiel, l'interface fait généralement référence à l'entité dont le référentiel est responsable. Il fait donc dépendre l'implémentation de niveau inférieur (CarModelRepository) d'une entité de module de niveau supérieur (CarModel).

Remplacer le béton CarModel par une interface ICarModel ne résout pas vraiment ce problème. Après tout, l'instance CarModel que nous obtenons via le référentiel implémente diverses règles métier et devrait donc vivre dans la couche métier.

Nous pourrions augmenter la séparation entre la couche métier et l'implémentation du référentiel. Par exemple, nous pourrions fournir un CarModelFactory (implémenté dans la couche de gestion) qui implémente ICarModelFactory (une partie de l'interface partagée) au référentiel. Alternativement, nous pouvons faire en sorte que le référentiel traite les objets de valeur CarData plutôt que les entités CarModel réelles - et que la couche métier instancie les entités elles-mêmes.

Dans la plupart des cas, cela ne semble pas fructueux. Tenter de trouver une interface abstraite entre les deux modules qui soit plus stable que CarModel lui-même va presque toujours être futile. C'est parce que, fondamentalement, CarModelRepository n'est pas destiné à être une classe utilitaire générique. Son but est essentiellement de coller votre modèle de domaine spécifique à la base de données.

Par conséquent, essayer de le séparer du modèle de domaine (ou de la base de données) ne produit vraiment rien d'utile. En d'autres termes, le référentiel peut être considéré comme un adaptateur entre le modèle de domaine et le pilote de base de données - et doit donc être conscient des deux.

Je dois noter que l'objectif principal de Martin lors de l'introduction du DIP était de séparer le module de niveau supérieur des modules de niveau inférieur, ce que le modèle de référentiel permet.

5
doubleYou

Non.

Considérez votre code avant l'introduction du référentiel:

public class MyView
{
    public void button_onClick()
    {
        var sql = $"select form car where id = {this.txt_id}"
        //do sql reading stuff
        this.Car = new Car();
        this.Car.Id = dr["id"].ToString();
    }
}

et après

public class MyView
{
    private IRepository repo;
    public void button_onClick()
    {
        this.Car = this.repo.GetCar(this.txt_id);
    }
}

Vous avez appliqué le DiP à votre classe MyView.

Vous pouvez aller plus loin et l'appliquer également à votre référentiel. Peut-être que la voiture est une classe abstraite si vous avez plus d'un type de voiture

public CarRepo : IRepository
{
    public ICarFactory carFac

    public Car GetCar(string id)
    {
        //do sql stuff
        var car = carFac.Create(dr["carType"].ToString(), dr)
        return car
    }
}

Dans chaque cas, nous supprimons une certaine responsabilité de la classe en question et l'injectons à la place en tant que dépendance à une interface.

Au lieu de cette classe en fonction d'une implémentation spécifique d'une classe de niveau inférieur, dans ce cas, la logique SQL. La classe de bas niveau dépend de l'interface, qui est définie en fonction des besoins de l'objet de haut niveau.

Si vous considérez Car votre objet de haut niveau, vous pouvez également appliquer le principe ici. Évidemment, il n'y a pas grand chose à injecter l'objet du référentiel. Mais disons par exemple

public class Car
{
    private IPriceCalculator calc;
    public decimal Price()
    {
        //comment out old code!
        //return (new MyCalculator).CalcPrice(this.colour);
        return this.calc.CalcPrice(this.colour);
    }
}

Maintenant, j'ai inversé la dépendance à la logique de calcul des prix

1
Ewan