2009-05-22 9 views
19

Ich schaute auf die differences between POCO and DTO (Es scheint, dass POCO sind dto's mit Verhalten (Methoden?)) Und stolperte this article von Martin Fowler auf dem anämischen Domain-Modell.POCOs, DTOs, DLLs und anämische Domain-Modelle

Durch mangelndes Verständnis habe ich glaube ich eines dieser anämischen Domain-Modelle erstellt.

In einer meiner Anwendungen habe ich meine Business-Domain-Entitäten in einer 'dto' DLL definiert. Sie haben viele Eigenschaften mit Getter und Setter und nicht viel mehr. Mein Geschäftslogik-Code (ausfüllen, berechnen) befindet sich in einer anderen DLL-Datei, und mein Datenzugriffscode befindet sich in einer DAL-DLL. "Best Practice", dachte ich.

So Regel erstelle ich ein dto wie so:

dto.BusinessObject bo = new dto.BusinessObject(...) 

und an die BLL Schicht passieren etwa so:

bll.BusinessObject.Populate(bo); 

die wiederum eine gewisse Logik führt und leitet sie an den dal Schicht etwa so:

dal.BusinessObject.Populate(bo); 

Von meinem Verständnis meiner DTO in POCO mache ich das Geschäft logi machen müssen c und Verhalten (Methoden) Teil des Objekts. Also anstelle des obigen Codes ist es eher wie folgt:

poco.BusinessObject bo = new poco.BusinessObject(...) 
bo.Populate(); 

dh. Ich rufe die Methode für das Objekt auf, anstatt das Objekt an die Methode zu übergeben.

Meine Frage ist - wie kann ich das tun und immer noch die 'Best Practice' Schichtung von Bedenken (separate DLL's etc ...). Ruft die Methode für das Objekt nicht auf, bedeutet das, dass die Methode im Objekt definiert werden muss?

Bitte helfen Sie meine Verwirrung.

Antwort

22

Normalerweise möchten Sie keine Persistenz in Ihre Domänenobjekte einführen, da sie nicht Teil dieses Geschäftsmodells ist (ein Flugzeug konstruiert sich nicht selbst, es transportiert Passagiere/Fracht von einem Ort zum anderen). Sie sollten das repository pattern, ein ORM framework oder ein anderes Datenzugriffsmuster verwenden, um die dauerhafte Speicherung und das erneute Einreichen des -Status eines Objekts zu verwalten.

Wo das anämische Domänenmodell in spielen kommt, wenn man Dinge wie dies tut:

IAirplaneService service = ...; 
Airplane plane = ...; 
service.FlyAirplaneToAirport(plane, "IAD"); 

In diesem Fall wird die Verwaltung des staatlichen Flugzeugs (ob es fliegt, wo es ist, was ist die Abflugszeit/Flughafen, was ist die Ankunftszeit/Flughafen, was ist der Flugplan, etc.) an etwas außerhalb des Flugzeugs delegiert ... die AirplaneService-Instanz.

A POCO Weg, dies umzusetzen wäre Ihre Schnittstelle auf diese Weise zu entwerfen:

Airplane plane = ...; 
plane.FlyToAirport("IAD"); 

Diese mehr auffindbar ist, da die Entwickler wissen, wo ein Flugzeug sehen fliegen zu lassen (nur das Flugzeug sagen, es zu tun). Sie können auch sicherstellen, dass der Status nur intern verwaltet wird. Sie können Dinge wie den aktuellen Standort schreibgeschützt machen und sicherstellen, dass er nur an einer Stelle geändert wird. Da ein Zustand mit einem anämischen Domänenobjekt extern festgelegt wird, wird es immer schwieriger, zu erkennen, wo sich der Status ändert, da die Größe Ihrer Domäne zunimmt.

+4

Kennen Sie Quellcodebeispiele für ein solches Design? Ich finde die Prinzipien sinnvoll, aber wenn ich zur Implementierung von FlyToAirport komme, habe ich eine gesprächige Schnittstelle zur Datenbank, wenn ich mehrere Tabellen aktualisieren muss, besonders wenn ich keine SPs verwende. –

+1

Wenn externe Transaktionen beteiligt sind, ist die Verwendung von Diensten sinnvoller. Sie sollten versuchen, diese so grobkörnig wie möglich zu gestalten, damit Sie so viel Verhalten in Ihr Domänenmodell einbetten können. Leider sehe ich es nicht so oft wie möglich verwendet. Natürlich verwende ich es in jedem nicht-alten Produktionscode, den ich schreibe, und habe an einigen Projekten gearbeitet, die diesen Ansatz verwenden. –

6

Persönlich finde ich diese anämischen Domain-Modelle nicht so schlecht; Ich mag die Idee, Domänenobjekte zu haben, die nur Daten repräsentieren, nicht das Verhalten. Ich denke, der größte Nachteil bei diesem Ansatz ist die Auffindbarkeit des Codes. Sie müssen wissen, welche Aktionen verfügbar sind, um sie zu verwenden. Ein Weg, um das zu bekommen und immer noch das Verhalten Code aus dem Modell entkoppelt ist einzuführen Schnittstellen für das Verhalten:

interface ISomeDomainObjectBehaviour 
{ 
    SomeDomainObject Get(int Id); 
    void Save(SomeDomainObject data); 
    void Delete(int Id); 
} 

class SomeDomainObjectSqlBehaviour : ISomeDomainObjectBehaviour 
{ 
    SomeDomainObject ISomeDomainObjectBehaviour.Get(int Id) 
    { 
     // code to get object from database 
    } 

    void ISomeDomainObjectBehaviour.Save(SomeDomainObject data) 
    { 
     // code to store object in database 
    } 

    void ISomeDomainObjectBehaviour.Delete(int Id) 
    { 
     // code to remove object from database 
    } 
} 
class SomeDomainObject 
{ 
    private ISomeDomainObjectBehaviour _behaviour = null; 
    public SomeDomainObject(ISomeDomainObjectBehaviour behaviour) 
    { 

    } 

    public int Id { get; set; } 
    public string Name { get; set; } 
    public int Size { get; set; } 


    public void Save() 
    { 
     if (_behaviour != null) 
     { 
      _behaviour.Save(this); 
     } 
    } 

    // add methods for getting, deleting, ... 

} 

Auf diese Weise können Sie das Verhalten Umsetzung vom Modell getrennt halten. Die Verwendung von Schnittstellenimplementierungen, die in das Modell eingefügt werden, macht den Code auch ziemlich einfach zu testen, da Sie das Verhalten leicht verspotten können.

+2

Wie Sie Verhalten zu verwalten ist ähnlich der Strategie-Muster: (http://en.wikipedia.org/wiki/Strategy_pattern). Es ist wirklich gut, wenn das tatsächliche Verhalten zur Laufzeit ermittelt werden muss, aber bei einer anderen Verwendung zu einer Überdimensionierung führen kann. Ich mag dieses Muster, weil es das Verhalten außerhalb der Klassenhierarchie wiederverwendbar macht. Ich muss jedoch bewusst vermeiden, es zu verwenden, wenn es nicht notwendig ist, um zu vermeiden, dass Lösungen zu komplex werden. –

+0

Ja, es ist wie das Delegieren des Verhaltens an eine separate Klasse, aber der aufrufende Code greift weiterhin auf das Verhalten über Ihr Domänenobjekt zu. (Vielleicht machen Sie die Verhaltensklasse intern, um dies sicherzustellen.) – Rodi

10

Ich denke, der beste Weg, dies ist per Definition zu klären:

DTO: Datenübertragung Objekte:

Sie nur für Datentransport typischerweise zwischen Präsentationsschicht und Serviceschicht dienen. Nichts weniger oder mehr. Im Allgemeinen wird es als Klasse mit Gets und Sets implementiert.

public class ClientDTO 
{ 
    public long Id {get;set;} 
    public string Name {get;set;} 
} 

BO: Business Objects:

Business-Objekte stellen die betriebswirtschaftlichen Elemente und natürlich die beste Praxis sagt, sie sollten auch Business-Logik enthalten. Wie von Michael Meadows gesagt, ist es auch eine gute Praxis, den Datenzugriff von diesen Objekten zu isolieren.

public class Client 
{ 
    private long _id; 
    public long Id 
    { 
     get { return _id; } 
     protected set { _id = value; } 
    } 
    protected Client() { } 
    public Client(string name) 
    { 
     this.Name = name;  
    } 
    private string _name; 
    public string Name 
    { 
     get { return _name; } 
     set 
     { // Notice that there is business logic inside (name existence checking) 
      // Persistence is isolated through the IClientDAO interface and a factory 
      IClientDAO clientDAO = DAOFactory.Instance.Get<IClientDAO>(); 
      if (clientDAO.ExistsClientByName(value)) 
      { 
       throw new ApplicationException("Another client with same name exists."); 
      } 
      _name = value; 
     } 
    } 
    public void CheckIfCanBeRemoved() 
    { 
     // Check if there are sales associated to client 
     if (DAOFactory.Instance.GetDAO<ISaleDAO>().ExistsSalesFor(this)) 
     { 
      string msg = "Client can not be removed, there are sales associated to him/her."; 
      throw new ApplicationException(msg); 
     } 
    } 
} 

Dienst oder Anwendungsklasse Diese Klassen stellen die Interaktion zwischen Benutzer und dem System, und sie werden den Einsatz von sowohl ClientDTO und Kunden machen.

public class ClientRegistration 
{ 
    public void Insert(ClientDTO dto) 
    { 
     Client client = new Client(dto.Id,dto.Name); /// <-- Business logic inside the constructor 
     DAOFactory.Instance.Save(client);   
    } 
    public void Modify(ClientDTO dto) 
    { 
     Client client = DAOFactory.Instance.Get<Client>(dto.Id); 
     client.Name = dto.Name; // <--- Business logic inside the Name property 
     DAOFactory.Instance.Save(client); 
    } 
    public void Remove(ClientDTO dto) 
    { 
     Client client = DAOFactory.Instance.Get<Client>(dto.Id); 
     client.CheckIfCanBeRemoved() // <--- Business logic here 
     DAOFactory.Instance.Remove(client); 
    } 
    public ClientDTO Retrieve(string name) 
    { 
     Client client = DAOFactory.Instance.Get<IClientDAO>().FindByName(name); 
     if (client == null) { throw new ApplicationException("Client not found."); } 
     ClientDTO dto = new ClientDTO() 
     { 
      Id = client.Id, 
      Name = client.Name 
     } 
    } 
} 
+0

+1 für die gute Perspektive – boj