Interfețe pe diferite straturi logice

Spuneți că aveți o aplicație împărțită în trei niveluri: GUI, logica de afaceri și accesul la date. În stratul dvs. de logică de afaceri ați descris obiectele afacerii dvs.: getters, setters, accesori și așa mai departe ... obțineți ideea. Interfața cu stratul de logică de afaceri garantează utilizarea sigură a logicii de afaceri, astfel încât toate metodele și accesoriile pe care le apelați vor valida intrarea.

Acest lucru este minunat când scrieți pentru prima oară codul UI, deoarece aveți o interfață bine definită pe care aveți încredere.

Dar aici vine partea dificilă, când începeți să scrieți stratul de acces la date, interfața cu logica de afaceri nu vă satisface nevoile. Trebuie să aveți mai mulți accesori și getters pentru a seta câmpurile care sunt/utilizate pentru a fi ascunse. Acum, sunteți forțat să erodați interfața logicii dvs. de afaceri; acum este posibil să se creeze câmpuri din stratul UI, pe care stratul UI nu are setări de afaceri.

Datorită modificărilor necesare pentru stratul de acces la date, interfața cu logica de afaceri a erodat până la punctul în care este posibilă chiar setarea logicii de afaceri cu date nevalide. Astfel, interfața nu mai garantează o utilizare sigură.

Sper că am explicat destul de clar problema. Cum preveniți erodarea interfeței, mențineți ascunderea și încapsularea informațiilor și, totuși, să satisfaceți nevoile de interfață diferite între diferitele straturi?

0
fr hi bn

9 răspunsuri

Aceasta este o problemă clasică - separarea modelului de domeniu de modelul de bază de date. Există mai multe modalități de a ataca, într-adevăr depinde de dimensiunea proiectului dvs. în opinia mea. Ați putea folosi modelul repozitoriu după cum au spus ceilalți. Dacă utilizați .net sau Java ați putea utiliza Nhibernate sau Hibernare .

Ceea ce fac este să folosesc Dezvoltarea testării , așa că îmi scriu mai întâi straturile de UI și Model și stratul de date este batjocorit, astfel încât interfața și modelul se construiesc în jurul obiectelor specifice domeniului, apoi mai târziu am cartografiat aceste obiecte la ceea ce tehnologia folosesc stratul de date. Este o idee foarte rău să lași baza de date să determine proiectul aplicației dvs., să scrieți mai întâi aplicația și să vă gândiți mai târziu la date.

ps titlul întrebării este puțin greșit

0
adăugat

Poate doriți să vă împărțiți interfețele în două tipuri, și anume:

  • Vizualizați interfețele - care sunt interfețe care specifică interacțiunile dvs. cu interfața dvs. utilizator și
  • Interfețe de date - care sunt interfețe care vă vor permite să specificați interacțiunile cu datele dvs.

Este posibil să moștenim și să implementăm ambele seturi de interfețe astfel:

public class BusinessObject : IView, IData

În acest fel, în stratul de date trebuie doar să vedeți implementarea interfeței IData, în timp ce în interfața dvs. utilizator trebuie doar să vedeți implementarea interfeței IView.

O altă strategie pe care ați putea dori să o utilizați este să vă compuneți obiectele în straturile UI sau Datele astfel încât acestea să fie consumate doar de aceste straturi,

public class BusinessObject : DomainObject

public class ViewManager where T : DomainObject

public class DataManager where T : DomainObject

Aceasta, la rândul său, permite obiectului dvs. de afaceri să rămână ignorant atât din stratul UI/Vizualizare, cât și din stratul de date.

0
adăugat

Ar putea fi o soluție, deoarece nu ar eroda interfața. Cred ca ai putea avea o clasa ca aceasta:

public class BusinessObjectRecord : BusinessObject
{
}
0
adăugat

Ce vrei să spui prin faptul că nivelul de date nu ar trebui să fie conștient de nivelul logicii de afaceri? Cum ați umple un obiect de activitate cu date?

Eu fac acest lucru adesea:

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
               //Exec stored procedure
         {
    }
}
0
adăugat

Întotdeauna creez un ansamblu separat care conține:

  • O mulțime de interfețe mici (cred că ICreateRepository, IReadRepository, IReadListRepsitory .. lista continuă și majoritatea se bazează în mare măsură pe generice)
  • Multe interfețe de beton, ca un IPersonRepository, care moștenesc de la IReadRepository, obțineți punctul ..
    Orice nu puteți descrie doar cu interfețele mai mici, ați pus în interfața de beton Atâta timp cât utilizați IPersonRepository pentru a vă declara obiectul, obțineți o interfață curată și consistentă cu care să lucrați. Dar kicker este, puteți face, de asemenea, o clasă care ia f.x. un ICreateRepository în constructorul său, astfel încât codul va ajunge foarte ușor să faci niște chestii cu adevărat funky. Există, de asemenea, interfețe pentru serviciile din categoria de afaceri aici.
  • În sfârșit, leagă toate obiectele de domeniu în ansamblul suplimentar, doar pentru a face ca baza de cod să fie puțin mai curată și mai puțin cuplată. Aceste obiecte nu au nici o logică, ele sunt doar o modalitate obișnuită de a descrie datele pentru toate straturile 3+.

BTW. De ce ați defini metode în stratul de logică de afaceri pentru a acoperi nivelul de date? Nivelul de date nu ar trebui să aibă nici un motiv să știe chiar că există un nivel de afaceri ..

0
adăugat

@ Ice ^^ căldură:

Ce vrei să spui prin faptul că nivelul de date nu ar trebui să fie conștient de nivelul logicii de afaceri? Cum ați umple un obiect de activitate cu date?

UI solicită ServiceClass în nivelul de afaceri pentru un serviciu, și anume obținerea unei liste de obiecte filtrate de un obiect cu datele parametrilor necesari.
Apoi, ServiceClass creează o instanță a uneia dintre clasele de depozit din nivelul de date și apelează filtrele GetList (ParameterType).
Apoi, nivelul de date accesează baza de date, trage datele și îl mapează la formatul comun definit în ansamblul "domeniu". BL nu mai are de-a face cu aceste date, așa că le trimite la interfață.

Apoi UI vrea să editeze articolul X. Trimite elementul (sau obiectul de activitate) serviciului din Business Tier. Nivelul de afaceri validează obiectul și, dacă este OK, îl trimite la nivelul de date pentru stocare.

UI cunoaște serviciul din nivelul de afaceri care cunoaște din nou nivelul de date.

Interfața utilizator este responsabilă pentru maparea datelor de intrare a utilizatorilor către și de la obiecte, iar nivelul de date este responsabil pentru maparea datelor din db către și de la obiecte. Nivelul de afaceri rămâne exclusiv de afaceri. :)

0
adăugat

Voi continua să obișnuiesc să merg împotriva cerealelor și să spun că trebuie să te întrebi de ce construiești toate aceste straturi complexe de obiecte îngrozitoare.

Cred că mulți dezvoltatori se gândesc la baza de date ca pe un simplu strat de persistență pentru obiectele lor și se referă numai la operațiile CRUD pe care le au aceste obiecte. Prea mult efort se pune în "nepotrivirea impedanței" între modelele obiect și relaționale. Iată o idee: încetați să încercați.

Scrie proceduri stocate pentru a încapsula datele. Utilizați seturile de rezultate, DataSet, DataTable, SqlCommand (sau java/php/indiferent de echivalent) după cum este necesar din cod pentru a interacționa cu baza de date. Nu aveți nevoie de acele obiecte. Un exemplu excelent este încorporarea unei SqlDataSource într-o pagină .ASPX.

Nu ar trebui să încercați să ascundeți datele de la nimeni. Dezvoltatorii trebuie să înțeleagă exact cum și când interacționează cu magazinul de date fizice.

Obiective-relaționate harta sunt diavolul. Nu mai folosi.

Dezvoltarea aplicațiilor pentru întreprinderi este adesea un exercițiu în gestionarea complexității. Trebuie să păstrați lucrurile cât mai simple posibil sau veți avea un sistem absolut nerentabil. Dacă sunteți dispus să permiteți o anumită cuplare (care oricum este inerentă unei aplicații), puteți să îndepărtați atât stratul de logică de afaceri, cât și stratul de acces la date (înlocuindu-le cu proceduri stocate) și nu veți avea nevoie de niciunul dintre aceste interfețe.

0
adăugat
Nu, nu vorbesc despre aplicații care sunt atât de simple. Vorbesc despre scrierea de procs cu granulație grosieră care să se ocupe de logica dvs. de afaceri în aplicații complexe ale întreprinderii și să păstreze codul care solicită procs-urile cât mai simple posibil.
adăugat autor Eric Z Beard, sursa
Nu văd cum mișcarea logicii de afaceri în procedurile stocate ajută la chestiuni. Aș prefera să le păstrez într-un strat de afaceri frumos într-o limbă care să susțină testarea bunelor unități.
adăugat autor Ben Fulton, sursa
Presupun că s-ar putea să funcționeze dacă tot ce faci este să puneți un ecran pe masa de bază.
adăugat autor JC., sursa

Deci, problema este că stratul de afaceri trebuie să expună mai multă funcționalitate la nivelul de date, iar adăugarea acestei funcționalități înseamnă expunerea prea mult la nivelul UI? Dacă înțeleg corect problema dvs., suna ca și cum ați încerca să satisfaceți prea mult o singură interfață, și asta doar face să fie aglomerată. De ce nu aveți două interfețe în stratul de afaceri? Una ar fi o interfață simplă și sigură pentru stratul UI. Celălalt ar fi o interfață la nivel inferior pentru stratul de date.

Puteți aplica această abordare cu două interfețe pentru orice obiecte care trebuie transferate atât la interfața de utilizare cât și la straturile de date.

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}
0
adăugat

Dacă am înțeles corect întrebarea, ați creat un model de domeniu și doriți să scrieți un cartograf obiect-relațional pentru a hărți între înregistrările din baza de date și obiectele domeniului dvs. Cu toate acestea, sunteți preocupat de poluarea modelului dvs. de domeniu cu codul "sanitar" care ar fi necesar pentru a citi și a scrie în câmpurile obiectului.

Dacă faceți un pas înapoi, aveți, în esență, două opțiuni de unde să plasați codul de mapare a datelor - în cadrul clasei de domeniu sau într-o clasă externă de cartografiere. Prima opțiune este denumită adesea modelul Active Record și are avantajul că fiecare obiect știe să persiste și are suficient acces la structura sa internă pentru a permite efectuarea cartografierii fără a fi necesară expunerea câmpurilor care nu sunt legate de afaceri.

De exemplu

public class User
{
    private string name;
    private AccountStatus status;

    private User()
    {
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public AccountStatus Status
    {
        get { return status; }
    }

    public void Activate()
    {
        status = AccountStatus.Active;
    }

    public void Suspend()
    {
        status = AccountStatus.Suspended;
    }

    public static User GetById(int id)
    {
        User fetchedUser = new User();

       //Lots of database and error-checking code
       //omitted for clarity
       //...

        fetchedUser.name = (string) reader["Name"];
        fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;

        return fetchedUser;
    }

    public static void Save(User user)
    {
       //Code to save User's internal structure to database
       //...
    }
}

În acest exemplu, avem un obiect care reprezintă un utilizator cu un nume și un cont de cont. Nu vrem să permitem ca starea să fie setată direct, poate pentru că vrem să verificăm dacă schimbarea este o tranziție valabilă a statutului, deci nu avem setter. Din fericire, codul de cartografiere din metodele GetById și Save static are acces deplin la câmpurile de nume și de stare ale obiectului.

The second option is to have a second class that is responsible for the mapping. This has the advantage of seperating out the different concerns of business logic and persistence which can allow your design to be more testable and flexible. The challenge with this method is how to expose the name and status fields to the external class. Some options are: 1. Use reflection (which has no qualms about digging deep into your object's private parts) 2. Provide specially-named, public setters (De exemplu. prefix them with the word 'Private') and hope no one uses them accidentally 3. If your language suports it, make the setters internal but grant your data mapper module access. De exemplu. use the InternalsVisibleToAttribute in .NET 2.0 onwards or friend functions in C++

Pentru mai multe informații, aș recomanda cartea clasică a lui Martin Fowler "Patterns of Enterprise Architecture"

Cu toate acestea, ca un cuvânt de avertizare, înainte de a merge în jos pe calea de a scrie propriii dvs. cartografiți, aș recomanda cu tărie să vă uitați la utilizarea unui instrument de cartograf obiect relațional (ORM) al unui terț, cum ar fi nHibernate sau Microsoft Entity Framework. Am lucrat la patru proiecte diferite în care, din diverse motive, am scris propriul cartograf și este foarte ușor să pierdem o mulțime de timp menținând și extindând cartograful în loc să scriem cod care să ofere valoare utilizatorului final. Am folosit nHibernate pentru un proiect până acum și, deși are o curbă destul de abruptă de învățare inițial, investiția pe care o puneți pe timpuriu se plătește considerabil.

0
adăugat