webForumDet fria alternativet

Förändring av Interfaces: Good or Bad?

Programmering

14 svar · 1 080 visningar · startad av Fredde Mannen

Medlem sedan nov. 20014 054 inlägg
Frågan#1

Satt och funderade lite, efter att vi hade haft en kortare diskussion på arbetet om interface-användning.

Vi har funderingar på att börja nyttja Repository pattern i våra projekt. När jag läser idag lite här och där på The Big Deep Internet hittar man en del varierande exempel på implementation av mönstret.

Jag ser:
- Repositories i form CustomerRepository, etc. utan användning av BaseRepositories, Repositories interfaces etc.

http://geekswithblogs.net/AndrewSiemer/archive/2008/02/05/linq-to-sql---implementing-the-repository-pattern.aspx

Sen ser Jag:
- Base interfaces i form av ex: IRepository<TEntity> som inkluderar ex antal saker som skall implementeras.
- Base Repository i form av ex: Repository<TEntity> som implementerar dessa metoder.
- Där varje repository ärver från Repository<TEntity>, samt implementerar ett eget interface som ärver av IRepository.

http://www.codeproject.com/KB/architecture/linqrepository.aspx

Sen ser jag också:
- En hel uppsjö av interfaces, en för samtliga typer av Repository: ICustomerRepository, IProductRepository osv, som inte ärver från någon IRepository .
- Sen implementerar ex. CustomerRepository ICustomer.. , ProductRepository implementerar IProductRepository.

http://weblogs.asp.net/fredriknormen/archive/2008/04/24/what-purpose-does-the-repository-pattern-have.aspx

http://mdpopescu.blogspot.com/2008/12/repository-pattern.html

And so on. :OO :h

Hur gör ni när ni implementerar Repository pattern? Oavsett vilket programmeringsspråk ni nu sitter med.

Använder ni andra mönster? Använder ni olika mönster i olika projekt? Vad avgör vad ni tänker satsa på? Hur ser ni på att ändra i interfaces i en existerande applikation? Kan ju få en stor inverkan på en gäng med klasser..

vad är best practise? iiiik

Jag börjar bli smått

Medlem sedan mars 20007 896 inlägg
#2

Anledningen till att du inte hittar något konkret är att mönstret egentligen måste anpassas efter varje projekt. Att kopiera/klistra in en repo-lösning kan vara ett risktagande, och kan ta mer tid än att försöka hitta en lösning själv. Det finns inget självklart svar på frågan, helt enkelt. Hur vill ni implementera det? Ska erat repo vara ett DAL samtidigt - eller ska repot vara ett medium mellan BL och DAL? Jag gissar på att ni sitter på någon Active Record Pattern lösning idag?

Gränssnitt (interfaces) ska ju helst av allt inte ändras, utan i så fall implementationen av gränssnittet. På så sätt kan en klass skrivas om utan att bryta kontraktet som gränssnittet förser klassen med, och alla klasser som läser av implementationerna vet precis vilken typ av data de har att arbeta med. Så att byta ut ett kontrakt är ett no-no, men att byta implementation är helt ok.

Medlem sedan dec. 19996 522 inlägg
#3

Brukar jag ha ett generiskt abstrakt basrepo med grundfunktionalitet. Ett repo per aggregate root. Har som regel att alltid låta repositories vara ansvariga för hur jag kan kommunicera med domänen, tex genom att inte exponera IQueryable direkt (Detta har ju andra orsaker också i min värld). Huruvida varje repo implementerat ett eget interface (som i sin tur ärver IRepository) beror ju på om de delar funktionalitet mellan repona. Men oftast.

Brukar också bryta ut find-metoder för annars blir ofta repona så bloated.

Medlem sedan nov. 20014 054 inlägg
#4

SPiN skrev:

Anledningen till att du inte hittar något konkret är att mönstret egentligen måste anpassas efter varje projekt. Att kopiera/klistra in en repo-lösning kan vara ett risktagande, och kan ta mer tid än att försöka hitta en lösning själv. Det finns inget självklart svar på frågan, helt enkelt. Hur vill ni implementera det? Ska erat repo vara ett DAL samtidigt - eller ska repot vara ett medium mellan BL och DAL? Jag gissar på att ni sitter på någon Active Record Pattern lösning idag?

Gränssnitt (interfaces) ska ju helst av allt inte ändras, utan i så fall implementationen av gränssnittet. På så sätt kan en klass skrivas om utan att bryta kontraktet som gränssnittet förser klassen med, och alla klasser som läser av implementationerna vet precis vilken typ av data de har att arbeta med. Så att byta ut ett kontrakt är ett no-no, men att byta implementation är helt ok.

Jag vet inte riktigt om det är en renodlad Active Record lösning, i och med att Active Record lägger all "data access logic" i domän objektet.

Eftersom vi i dag nyttjar Linq-to-Sql i grunden så ser det sedan iut så här:

1. ) dbml (LINQ TO SQL-klasser)
2. ) CustomerManager (en manager per linq-to-sql class i stort sett)

CustomerManager kan då innehålla:
- SaveOrUpdate
- Remove
- MarkForDeletion
- GetById
- GetAll
- GetByStatus
- GetByCategoryId

m.fl. som ett exempel bara.

Kan även bli så att vi lägger på funktionalitet enligt active record pattern på linq-to-sql klasserna.

Ex. ett ärende har en ansökan kopplat till sig, och för att få ut ansökningsid som är kopplat till ärendet kan en sådan här funktion ingå som partiell klass då till linq-to-sql klassen.

- GetApplicationId

	Public ReadOnly Property GetApplicationId() As Integer
		Get
			Dim applicationManager As New ApplicationManager
			Dim application = applicationManager.GetByIssueId(Me.IssueId)
			If application IsNot Nothing Then Return application.ApplicationId
			Return 0
		End Get
	End Property
Medlem sedan aug. 20003 575 inlägg
#5

Jag tycker man skall använda interfacen för att det ger ökad testbarhet till projektet.
Där man med IoC containers där implemmentationen injekteras i objekten.

Kolla gärna denna arkitektur som finns beskriven i en blogserie på tre inlägg som jag gillar skarpt, det är inga nya saker utan bara ett bra koncept som han satt ett bra namn på:
http://jeffreypalermo.com/blog/the-onion-architecture-part-1/

Medlem sedan dec. 19996 522 inlägg
#6

Nickemannen, OT. http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/ Bra artikel angående IoC

Medlem sedan aug. 20003 575 inlägg
#7

erka skrev:

Nickemannen, OT. http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/ Bra artikel angående IoC

Det tycker jag inte mitt inlägg handlade om aspekter varför man bör använda interface, länken visar bara ett exempel på hur interface kan vara viktiga i en arkitektur, speciellt Repository interface.

Medlem sedan dec. 19996 522 inlägg
#8

Alltså min länk var OT, inte ditt inlägg

Medlem sedan aug. 20003 575 inlägg
#9

erka skrev:

Alltså min länk var OT, inte ditt inlägg

aah okay, missuppfattning från min sida, ursäkta

Medlem sedan mars 20007 896 inlägg
#10

Hur kommer det sig att man ofta ser datahämtningsmetoder(!?) som har namnen getXById(), getXByName(), getXByOtherConstraint(), osv.? (inget personligt nu Fredde Mannen ;))

Jag vet ju att ni allihop utvecklar i .NET, varför då inte utnyttja kraften hos closures? Du kan då få ett minimalistiskt gränssnitt, och implementationerna blir snyggare IMO.

interface IRepository<T> {
    ...
    IEnumerable<T> GetAll(Func<T, bool> constraint);
}
class CustomerRepository : IRepository<Customer> {
    public IEnumerable<Customer> GetAll(Func<Customer, bool> constraint) {
        using (YourDAO dao = new YourDAO()) {
            return dao.Customers.Where(constraint);
        }
    }
    ...
}

    public void anyOtherMethod() {
        var customers = customerRepository.GetAll((customer => customer.Id == 5));
        ...
        customers = customerRepository.GetAll((customer => customer.Name == "Pelle"));
        ...
    }

Du minskar beroenden samtidigt som kontraktet för dina repon blir mindre begränsat och risken för att kontraktet byts ut vid ett senare tillfälle minskar drastiskt. Om ni måste hämta kunder med andra restriktioner (constraints) behöver ni inte utöka gränssnittet - det är helt enkelt bara att anropa GetAll() med ett annat predikat. Kontraktet behålls och implementationerna kommer fortfarande att fungera. Det här gäller ju inte bara ARP, även i RP kan man dra nytta av closures. :)

Edit, Nu tittade jag igenom ett par av dina länkar lite snabbt, och den här snubben tycker jag är helt rätt ute! Men som sagt, att kopiera/klistra in en lösning kan vara ett risktagande - så se till att modellera väl innan implementation. ;)

Medlem sedan dec. 19996 522 inlägg
#11

Generiska interface går inte så fint ihop med DDD i mitt tycke. Greg Young tar upp det rätt bra. http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx

Find kan ju implementeras enkelt med queryobjekt eller specification-pattern, ungefär som Spin pratar om. http://www.lostechies.com/blogs/chad_myers/archive/2008/08/02/query-objects-with-repository-pattern-part-2.aspx är ju en start

Medlem sedan nov. 20014 054 inlägg
#12

SPiN skrev:

Hur kommer det sig att man ofta ser datahämtningsmetoder(!?) som har namnen getXById(), getXByName(), getXByOtherConstraint(), osv.? (inget personligt nu Fredde Mannen ;))

Men som sagt, att kopiera/klistra in en lösning kan vara ett risktagande - så se till att modellera väl innan implementation. ;)

Nu vill jag ju inte påstå att jag vill göra en Copy N Paste. Utan jag läser på och ser hur olika personer har gjort olika implementeringar av Repository pattern, för att sedan göra min/vår egen för att uppfylla våra krav.

Men ang din fundering på datahämtningsmetoder som har namn get..By...() jag tar det inte personligt, men det känns lättare att nyttja färdiga metoder som används ofta, än att behöva ex skriva
FindAll(c => c.MemberStatus == MemeberStatus.Gold)

Överallt där man vill hämta sina guldmedlemmar.. Känns lättare att lägga in det i sin egen metod med ex: GetByMemberStatusGold();

Medlem sedan mars 20007 896 inlägg
#13

Jag förstår vad du menar, visst känns det mer naturligt att hämta via GetByMemberStatusGold() - men frågan om att behöva ändra i gränssnitt just på grund av detta var vad jag syftade på. Istället för att behöva uppdatera ett gränssnitt när man lägger till funktionalitet för att hämta GetByMemberStatusBronze() och då oroa sig för implementationerna bör man ju ha ett så abstrakt gränssnitt som möjligt. Det var en återknytning till topic, "Förändring av interfaces", där jag bara påpekade att .NET erbjuder funktionalitet som på ett enkelt sätt kan appliceras för att slippa ändra i gränssnitt.

Jag har inte programmerat så mycket i .NET, men det är ofta jag stöter på GetXByACriteria()-metoder istället för att använda sig av closures. Men det är väl som du säger, det känns lättare att utnyttja metoder som direkt gör det man vill. För att minska beroenden mellan sina applikationer bör man däremot inte deklarera dessa GetXByACriteria() i sina, generella, gränssnitt.

erkas länk var bra och rakt på sak. Däremot håller jag inte med om att man ska sluta använda generella (inte generiska ;)) gränssnitt. En service som inte har full koll på vilket repo som ska användas, kan i så fall använda sig av det generella gränssnittet. Vill man, som Greg menar, minska kontraktes område kan man i så fall ärva från den generella gränssnittet och utöka med mer funktionalitet. Bara för att man ärver av ett gränssnitt, betyder inte det att det är gränssnittets funktionalitet man är ute efter när man tar emot i metoder osv. Bara att man _kan_ använda den funktionaliteten då ärvande klasser måste implementera gränssnittets metoder. Alldelles för ofta tycker jag att folk tar emot ett gränssnitt och kastar om det till en specifik klass istället för att ta emot klassen direkt. S.k. down-cast är tråkigt att se, då är det något i modelleringen som inte stämmer...

interface IFace {
    public void DoStuff();
}
class AnyClass : IFace {
   ...
}
...
    public void AnyMethod(IFace obj) {
        AnyClass a = (AnyClass) obj;
    }
    /* gentemot: */
    public void AnyMethod(AnyClass obj) {
...

Nu känns det som att jag bara svamlar, men jag hoppas att jag får fram något vettigt...

Medlem sedan nov. 20014 054 inlägg
#14

SPiN skrev:

Jag förstår vad du menar, visst känns det mer naturligt att hämta via GetByMemberStatusGold() - men frågan om att behöva ändra i gränssnitt just på grund av detta var vad jag syftade på. Istället för att behöva uppdatera ett gränssnitt när man lägger till funktionalitet för att hämta GetByMemberStatusBronze() och då oroa sig för implementationerna bör man ju ha ett så abstrakt gränssnitt som möjligt. Det var en återknytning till topic, "Förändring av interfaces", där jag bara påpekade att .NET erbjuder funktionalitet som på ett enkelt sätt kan appliceras för att slippa ändra i gränssnitt.

Jag har inte programmerat så mycket i .NET, men det är ofta jag stöter på GetXByACriteria()-metoder istället för att använda sig av closures. Men det är väl som du säger, det känns lättare att utnyttja metoder som direkt gör det man vill. För att minska beroenden mellan sina applikationer bör man däremot inte deklarera dessa GetXByACriteria() i sina, generella, gränssnitt.

erkas länk var bra och rakt på sak. Däremot håller jag inte med om att man ska sluta använda generella (inte generiska ;)) gränssnitt. En service som inte har full koll på vilket repo som ska användas, kan i så fall använda sig av det generella gränssnittet. Vill man, som Greg menar, minska kontraktes område kan man i så fall ärva från den generella gränssnittet och utöka med mer funktionalitet. Bara för att man ärver av ett gränssnitt, betyder inte det att det är gränssnittets funktionalitet man är ute efter när man tar emot i metoder osv. Bara att man _kan_ använda den funktionaliteten då ärvande klasser måste implementera gränssnittets metoder. Alldelles för ofta tycker jag att folk tar emot ett gränssnitt och kastar om det till en specifik klass istället för att ta emot klassen direkt. S.k. down-cast är tråkigt att se, då är det något i modelleringen som inte stämmer...

interface IFace {
    public void DoStuff();
}
class AnyClass : IFace {
   ...
}
...
    public void AnyMethod(IFace obj) {
        AnyClass a = (AnyClass) obj;
    }
    /* gentemot: */
    public void AnyMethod(AnyClass obj) {
...

Nu känns det som att jag bara svamlar, men jag hoppas att jag får fram något vettigt...

Det är nu jag ser istället för att "skapa" flera GetByX använda FindAll. Som ex. på

GetXByACriteria()-metoder istället för att använda sig av closures

Och implementera ett specification-pattern, exempelvis.
- GetByMemberStatusGold
- GetByMemberStatusSilver
- GetByMemberStatusBronze

Kan sättas som:
- MemberStatusSpecification.GoldStatus
- MemberStatusSpecification.SliverStatus
- MemberStatusSpecification.BronzeStatus

Och nyttja FindAll från gränssnitt:

_repository.FindAll(MemberStatusSpecification.GoldStatus);

För att sedan lätt kunna lägga till:
- MemberStatusSpecification.Platina
- MemberStatusSpecification.Iron
- MemberStatusSpecification.Dirt

Eller för exempelvis anställda:
- GetByHighPaid
- GetByLowPaid
- GetByNormalPaid

Kan sättas som:
- EmployeePaidThresholdSpecification.High
- EmployeePaidThresholdSpecification.Low
- EmployeePaidThresholdSpecification.Normal

Och nyttja FindAll från gränssnitt:

_repository.FindAll(EmployeePaidThresholdSpecification.Normal);

Så ja, det är nog kanske åt det hållet jag skulle vilja arbeta.

Istället för att använda en massa:
- GetByFirstName()
- GetByName()
- GetBySocialSecurityNumber()

Nyttja:

_repository.FindAll(e => e.SocialSecurityNumber.Equals(socialSecurityNumber));

osv.

Och använda specification delen på sådant som har Fasta värden, eller värden som styrs av ex ett tröskelvärde av något slag.

Nu babblar jag också på.. Men jag får lite svar på ang. ändra i interfacet eller inte ändra i det.

Medlem sedan mars 20007 896 inlägg
#15

Ja, det låter som en lite vettigare lösning i mina öron (och det ser snyggt ut!). Då behåller det ursprunliga kontraktet, men kan lägga till mer funktionalitet till implementationen. :)

266 ms totalt · 4 externa anrop · v20260731065814-full.a51de22e
118 ms — deklarationer (db)
0 ms — hämta statistik (cache)
139 ms — hämta tråd, inlägg och bilagor (db)
124 ms — ändringar (db)