webForumDet fria alternativet

Exponera Listor

.NET

36 svar · 1 836 visningar · startad av johannormen

Medlem sedan sep. 200888 inlägg
Frågan#1

Tänkte kolla med er om ni brukar exponera typ IList, List o sånt från era publika proppar eller metoder?
haft en liten diskussion ang detta. Där vi alla var eniga om att man inte skall göra det av olika skäl
bla det stora skälet att en IList har en hel del metoder och då menar jag rätt många metoder som enligt en ren OOP inte bör finnas där i sin applikations yttre. Om man skall använda IList m.m. bör det vara som privata tillståndshanterare inne i sin kod. Om man måste returnera någon slags collection är IEnumerable det man bör använda och eller skapa sina egna collectionsobject med bara de metoder en utvecklare skall få komma åt.

Mycket av detta handlar bla om YAGNI och Defensive Programming, att inte ge för mkt funktioner till användarna de inte kan behärska samt göra felhanteringen mer otydlig. Exempelvis exponerar men en collection av List så har den remove metoder av olika slag, olika sätt att sätta objekt i den etc... Detta gör att andra utvecklare lätt kan använda den hur de vill istället för efter ens affärsregler.

ex:

Order.OrderLine.Add(product) <--- OrderLine är en IList<Product>

Denna rad bryter inte bara mot LoD (Law of Demter) utan exponerar en IList<Product> där man kan göra ottroligt mycket olika saker med den. Det är även svårt att ge tydliga fel vid exempelvis lägga till produkt eller ändra produkt om man tillåter utvecklare att använda sig av IList på detta sätt.

Vill man ändra en orderrad så kan man göra det på flera olika sätt bara för att IList har flera olika metoder för att ex ersätta, eller hämta eller ta bort dess object.
Problemet kanske inte är så tydligt när man tänker på det rakt upp och ner, problemet uppstår om utvecklarna gör olika implementationer i koden för att ändra en orderrad om man inte kräver ett gemensamt sätt att göra det på. Dvs mer rätt vore att göra.

Order.AddProduct(....)
Order.ChangeProduct(...)

Där man då använder IList som statebärare för OrderLines. Man sätter sen OrderLines propertyn till ReadOnly och returnerar endast IEnumerable istället för IList så utvecklarna mer eller mindre ”måste” använda de metoder som är avsedda för det som skall göras.

En anna fördel man får här är ökandet av den defensiva programmeringen. Bara för att man går via Wrapper metoder på sin Order kan man ha tydliga valideringar på det som kommer in och då även ge tydligare felmeddelanden till de som använder koden än vad ex Order.OrderLine.Add, RemoveAt etc kan ge tillbaka. Det finns även möjligheter att ex nyttja specification pattern för att kräva att en produkt man lägger till följer vissa kriterier så som att en produkt inte får väga över visst antal Kg. Sånt blir inte lika lätt att hantera om man exponerer listor med funktioner som tillåter förändringar den vägen.

Mvh Johan

Medlem sedan maj 20012 812 inlägg
#2

Raderar du listorna, så behöver du inte bry dig om hur de exponeras, men i övrigt håller jag med dig :)

Hallå... Du kan ju inte ändra efter jag kommenterat, då blir ju min kommentar helt fel :(. Men i övrigt så håller jag med dig... (igen)

- M

Medlem sedan okt. 200850 inlägg
#3

Gladh du finns ju överallt, om inte på Särö så på pelle eller här ;)

Kom och tänka på en sak när det gäller att exponera List<T>.. Läste en artikel jag fick av Patrik Löwendahl om just detta ämne, där säger Dino att internt är det ok att använda dom men inte exponera dom. Låt oss bara säga att efter en refactroing så fick jag detta resultatet:

internal IList<Object> GetObjects()
{
    //..
}

utifrån detta:

public void M()
{
   IList<Object> objects = new List<Object>
                                           {
                                                       new Object(...),
                                                       new Object(...),
                                                       new Object(...),
                                                       new Object(...)
                                           };

   //..

}

I metoden M() så använder jag min lista internt men jag kanske har flera metoder som ska ha samma lista med Objects och dess data, så för att inte DRY så flyttar vi det till en metod som returnerar den föredetta interna lista via en internal metod (för flera klasser i mitt projekt behöver komma åt den). Då har vi en exponering även om listan används internt för att undvika bryta mot DRY. Så med andra ord bör vi inte använda IList eller List alls om det inte är så att vi behöver använda större delen av dess interface, bara en tanke ;)

Problemet med List är att den bryter "lite" mot SRP (A class should only have one reason to change), i detta fall så är List inte sealed och har inga virtual methods. Så det enda vi kan göra är att bygga ut den med ännu fler metoder, vi kan inte förändra den.

Fick en fråga från en utvecklare om arrayer, hon undrade när man ska använda arrayer och inte. Mitt svar va "Jag avänder nästan aldrig arrayer utan istället System.Collections.*". Varför använder jag inte arrayer? Först så är det mutable, och för att göra dom immutable så måste jag skapa upp dom på nytt, vilket kan påverka prestandan. En orskat till att Reflection .Net kan påverka prestandan är just att de returnerar arrayer (i .Net 1.0 fanns inte generics). Många kanske tycker att följande kan lösa problemet med arrayer:

public class MyClass { 

   private MyInfo[] myInfos;
 
   public MyInfo[] GetInfos()
   {
       if (myInfos == null)
           myInfos = GetMyInfoArray();

     return myInfos;
   }

Vi får bra prestanda, men eftersom arrayer inte är immutable så kan den som får ut MyInfo[] lätt förändra dess data. Så för att lösa problemet så måste vi returnera en ny array varje gång.

Medlem sedan jan. 20023 327 inlägg
#4

johannormen skrev:

..radera...

Varför raderade du inlägget? Jag läste det innan du raderade det, men förstår inte varför du tog bort det. :q

Medlem sedan aug. 20003 575 inlägg
#5

fredrikn skrev:

Gladh du finns ju överallt, om inte på Särö så på pelle eller här ;)

Kom och tänka på en sak när det gäller att exponera List<T>.. Läste en artikel jag fick av Patrik Löwendahl om just detta ämne, där säger Dino att internt är det ok att använda dom men inte exponera dom. Låt oss bara säga att efter en refactroing så fick jag detta resultatet:

internal IList<Object> GetObjects()
{
//..
}

utifrån detta:

public void M()
{
IList<Object> objects = new List<Object>
{
new Object(...),
new Object(...),
new Object(...),
new Object(...)
};

//..

}

I metoden M() så använder jag min lista internt men jag kanske har flera metoder som ska ha samma lista med Objects och dess data, så för att inte DRY så flyttar vi det till en metod som returnerar den föredetta interna lista via en internal metod (för flera klasser i mitt projekt behöver komma åt den). Då har vi en exponering även om listan används internt för att undvika bryta mot DRY. Så med andra ord bör vi inte använda IList eller List alls om det inte är så att vi behöver använda större delen av dess interface.

Problemet med List är att den bryter "lite" mot SRP (A class should only have one reason to change), i detta fall så är List inte sealed och har inga virtual methods. Så det enda vi kan göra är att bygga ut den med ännu fler metoder, vi kan inte förändra den.

Fick en fråga från en utvecklare om arrayer, hon undrade när man ska använda arrayer och inte. Mitt svar va "Jag avänder nästan aldrig arrayer utan istället System.Collections.*". Varför använder jag inte arrayer? Först så är det mutable, och för att göra dom immutable så måste jag skapa upp dom på nytt, vilket kan påverka prestandan. En orskat till att Reflection .Net kan påverka prestandan är just att de returnerar arrayer (i .Net 1.0 fanns inte generics). Många kanske tycker att följande kan lösa problemet med arrayer:

public class MyClass {

private MyInfo[] myInfos;

public MyInfo[] GetInfos()
{
if (myInfos == null)
myInfos = GetMyInfoArray();

 return myInfos;

}

Vi får bra prestanda, men eftersom arrayer inte är immutable så kan den som får ut MyInfo[] lätt förändra dess data. Så för att lösa problemet så måste vi returnera en ny array varje gång.

När det gäller SRP och List så skall man aldrig ärva av List<T> utan av Collection<T> där du har virtuella metoder för skrivning till listan :).

Medlem sedan sep. 200888 inlägg
#6

Compusa skrev:

Varför raderade du inlägget? Jag läste det innan du raderade det, men förstår inte varför du tog bort det. :q

HAHA... söta ni är... får väl lägga till det igen då...

Medlem sedan sep. 200888 inlägg
#7
internal IList<Object> GetObjects()
{
//..
}

utifrån detta:

public void M()
{
IList<Object> objects = new List<Object>
{
new Object(...),
new Object(...),
new Object(...),
new Object(...)
};

//..

}

Tänkte på en sak här. För mig är ju detta exponering även om den är internal. Dock är det ju även skillnad på Domän och Ramverkskod. Jag tycker o anser att List lätt får exponeras o nyttjas i Ramverkskod av den anledning att ett ramverk skall vara lite mer flexibelt än ex domänen som helst bara skall göra det domänen skall göra.

Om man nu vill köra en Internal LIST så går det väl lika bra med IEnumerable för troligen vill man väl mestadels bara köra itteration på den i alla fall. Sen är det ju inte omöjligt att kasta om den till List om man måste ha mer funktioner. Jag tycker det viktiga är mer att "Gömma" sånt man inte vill att andra skall använda i första hand. Men kanske tillåta undantagen som faktiskt kan uppstå.

men jag vet inte...

Mvh Johan

Medlem sedan okt. 200850 inlägg
#8

Nickemannen skrev:

När det gäller SRP och List så skall man aldrig ärva av List<T> utan av Collection<T> där du har virtuella metoder för skrivning till listan :).

Japp, därför vill man inte exponera List<T>, det är för att den "bryter" mot SRP pga att den inte har virtual methods :h

Medlem sedan dec. 19996 522 inlägg
#9

Antar att det är denna artikel du syftar på johan, http://dotnetslackers.com/articles/net/List-and-Object-oriented-Design-Principles.aspx#The_Liskov_Principle.

I övrigt håller jag med.

Medlem sedan sep. 200888 inlägg
#10

erka skrev:

Antar att det är denna artikel du syftar på johan, http://dotnetslackers.com/articles/net/List-and-Object-oriented-Design-Principles.aspx#The_Liskov_Principle.

I övrigt håller jag med.

Faktiskt inte läst... Kanske den Fredrik och Patrik syftade på?
Skall läsas. Ser intressant ut.

Medlem sedan aug. 20003 575 inlägg
#11

fredrikn skrev:

Japp, därför vill man inte exponera List<T>, det är för att den "bryter" mot SRP pga att den inte har virtual methods :h

Ne List<T> suger, därför fungerar ju IList<T> :).
Alltid bättre att exponera interfacet om det finns något :D

Medlem sedan okt. 200850 inlägg
#12

Nickemannen skrev:

Ne List<T> suger, därför fungerar ju IList<T> :).
Alltid bättre att exponera interfacet om det finns något :D

Suger, vilka underbara ord ;)

Min enga uppfattning om IList<T> är att den har också en massa metoder, iofs bättre än att exponera List<T>, men se på IList<T>:

IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

Bättre att göra som Dino skrev i sin artikel att börja med IEnumerable och räcker inte det ICollection etc.. eller kanske bygga sin egna custom lista.. då får man precis det man vill ha.. fast fasen va jobbigt, då får vi koda mer.. ska verkligen 1-3 metoder extra behöva leda till att vi ska "återskapa" hjulet på nytt ;)

Medlem sedan sep. 200888 inlägg
#13

en annan tanke som slog mig är varför vi ens skall exponera IList<T> om vi inte vet vad andra skall göra med den. Det känns onödigt att lämna ut ILIst<t> om man själv gör kod som bara skall itterera den. Är det inte smart att köra IEnumerable<t> och sen låta utvecklarna mappa, kasta eller göra vad de vill med den om de måste nyttja den som en lista? Bara en tanke....

Det är alltid svårt det här med kod, vad skall man visa och vad skall man inte visa... Lika viktigt som det egentligen är att sätta public, private, internal på sina klasser för skydda mot att de inte kan råka användas fel m.m,

Medlem sedan maj 20012 812 inlägg
#14

fredrikn skrev:

Gladh du finns ju överallt, om inte på Särö så på pelle eller här

Nej Särö har jag nog inte varit på, inte vad jag vet iallafall. Tror inte ens jag vet var det ligger... säkert i någon skärgård (Stockholm? Göteborg?)

I vilket fall som helst så håller jag med alla som tycker som jag att skall man exponera listor så skall de vara en readonly lista (gärna ett interface också typ IEnumerable), därför att förändringar i listan bör ske genom metoder som vi själva skapar så vi har kontroll på vad som kommer in och vad som försvinner. Alltså precis som Johan säger att valideringen av datan som kommer in i listan är viktig.

Sen kan man ju diskutera ihjäl sig om Add metoden skall ligga på Order, eller OrderLinesCollection, eller kanske en domainservics som skall göra det, och för mig är inte det det viktiga isig, utan mer att det görs på ett ställe och att man har koll på vad som läggs till/tas bort från listan.

Extra viktigt blir det med listor som acceptera att ta emot interface istället för objekt. För du kan nämligen ha 2 olika objekt som implementerar samma interface, och även om man bara har tänkt att listan skall ta det ena objektet och därmed kastar om interfacet till sitt objekt och kör på. Om då någon utvecklare gör misstaget att lägga till det andra objektet som implementerar samma interface så kommer koden att krasch i runtime.

- M

Medlem sedan okt. 200850 inlägg
#15

Gladh skrev:

Nej Särö har jag nog inte varit på, inte vad jag vet iallafall. Tror inte ens jag vet var det ligger... säkert i någon skärgård (Stockholm? Göteborg?)

Sorry, då blandar jag ihop dig med en annan Gladh antar jag..

Problemet med ReadOnlyCollection är att den "suger". Den implementerar fortfarande IList och har Add etc. Så vi kan fortfarande anropa Add och först i runtime får vi fel. Skulle vi tex skriva:

IList<Object> objects = GetObjects();

och GetObjects ger oss en ReadOnlyCollection fast som ILizt<Object>, så kan vi sedan skriva:

objects.Add(...)

Det borde ha funnits ett IReadOnlyCollection interface.

Medlem sedan aug. 20003 575 inlägg
#16

fredrikn skrev:

Sorry, då blandar jag ihop dig med en annan Gladh antar jag..

Problemet med ReadOnlyCollection är att den "suger". Den implementerar fortfarande IList och har Add etc. Så vi kan fortfarande anropa Add och först i runtime får vi fel. Skulle vi tex skriva:

IList<Object> objects = GetObjects();

och GetObjects ger oss en ReadOnlyCollection fast som ILizt<Object>, så kan vi sedan skriva:

objects.Add(...)

Det borde ha funnits ett IReadOnlyCollection interface.

Jag säger inte att man ska använda IReadonlyCollection, men man kan ju använda Checker/Doer pattern genom att kika på IsReadOnly propertyn.

Medlem sedan okt. 200850 inlägg
#17

Nickemannen skrev:

Jag säger inte att man ska använda IReadonlyCollection, men man kan ju använda Checker/Doer pattern genom att kika på IsReadOnly propertyn.

Sure, fast en utvecklare som kommer använda koden ser bara att det är en IList som kommer tillbaka (om vi ser på mitt exempel i föregående post). Så då spelar det ingen roll för han/hon kommer troligtvis använda Add.

Michael. T. Nygard nämner i sin bok "Realse It!":

"Users are a terrible thing. System would be infinitely more stable without them. The human users of a system have this knack for creative desctruction. Human users have a gift for doing exactly the worst possible thing at the worst possible time."

Medlem sedan aug. 20003 575 inlägg
#18

fredrikn skrev:

Sure, fast en utvecklare som kommer använda koden ser bara att det är en IList som kommer tillbaka (om vi ser på mitt exempel i föregående post). Så då spelar det ingen roll för han/hon kommer troligtvis använda Add.

Michael. T. Nygard nämner i sin bok "Realse It!":

"Users are a terrible thing. System would be infinitely more stable without them. The human users of a system have this knack for creative desctruction. Human users have a gift for doing exactly the worst possible thing at the worst possible time."

Japp håller med dig, man skall aldrig lita på sina användare :).
Ju mer man kan gömma desto bättre.

När det gäller exponera listor generellt så vet jag att de flesta böcker inom OOP trycker på att man skall exponera endast det som är menat att använda.

Dock så tycker jag att .NET ramverket är uppbyggt på att exponera Collections/Listor och då tror jag att de flesta .net utvecklare är vana vid det och nästan förväntar sig en OrderItemCollection. Jag tycker väl kanske fel men jag gillar att skicka över ansvar i mina collections så att OrderItemCollection håller på en del GetByXXX samt att den också innehåller businessreglerna för tilläggande av en ny OrderItem samt borttagning.

Orsaken till detta är att jag tycker att jag då får mindre kod i min Order klass och att då min OrderItemCollection får ansvaret över samlingen.
Jag tycker också att det går snabbare och blir enklare än att istället wrappa listan i Order där OrderItemCount, Add, Remove och IEnumerable<T> samt ett par Get metoder kanske måste exponeras. Då blir det enklare om jag bara exponerar min lista.

public IOrderItemCollection OrderItems
{
    get;
    private set;
}

Men jag håller med i att den perfekta världen så bör man wrappa samlingen :).

I Java hade det ju varit helt fel att ha en GetOrderItems().Add(), men i .NET känns det mer normalt att göra OrderItems.Add(); Tyvärr?

Medlem sedan maj 20012 812 inlägg
#19

nickemannen skrev:

I Java hade det ju varit helt fel att ha en GetOrderItems().Add(), men i .NET känns det mer normalt att göra OrderItems.Add(); Tyvärr?

Det känns mer normalt att köra i 130 på motorvägen än på 70 vägen. Men det betyder inte att det är mer rätt för det. Nu har vi (du/jag/alla andra) ett ypperligt läge att ändra på denna känsla. För jag håller med dig att .NET (eller rättar sagt MS) har fått en stämpel på sig som ett hobby utvecklingsverktyg och mycket av det är nog MS fel, som hävdar att man kan utveckla program utan att skriva någon rad kod, bara klicka - gissa - spring. Men man kan ju bygga bra, robusta och underhållsenkla program i .NET också om man bara håller sig i från "MS-way" och tänker ett steg längre...

Så även om det känns okej med GetOrderItems.Add() så kanske vi skall protestera och säga att bara för att du kan göra så, så betyder det inte att det är rätt, för jag är helt övertygad att man kan göra på samma sätt i JAVA, men att man låter bli för man lärt sig att det inte är optimalt att göra så...

- M

Medlem sedan sep. 200888 inlägg
#20

Nickemannen skrev:

Dock så tycker jag att .NET ramverket är uppbyggt på att exponera Collections/Listor och då tror jag att de flesta .net utvecklare är vana vid det och nästan förväntar sig en OrderItemCollection. Jag tycker väl kanske fel men jag gillar att skicka över ansvar i mina collections så att OrderItemCollection håller på en del GetByXXX samt att den också innehåller businessreglerna för tilläggande av en ny OrderItem samt borttagning.

Problemet som uppstår här är Singe Responsability Principle/Rule.
Helt plötsligt blir din lista som egentligen mer eller mindre är en datacontainer beroende av så mkt mer än just containa... Ett annat problem är att OrderListan har inte reglerna som order kan kräva för viss kund etc. dessa regler äger Ordern. Vilket gör att det bör vara ordern som kör businesslogiken o kollar att man verkligen kan lägga till mer saker till ordern m.m.

Argumentet att alla är vana är sant. Problemet är bara att de är vara för de oftast inte har så stor kunskap om koddesign. Man härmar av exempel som ex microsoft har, man härmar av andra som härmat av andra etc... Det är för många idag som tyvärr inte baserar sin .Net kod efter OO samt OOP. Utan det är någon blandning av OO och gamla scriptvärldens tid, funktionsorienteringen etc som gör att man är van. Men vi ser ju felen oxå allt för ofta men ökad risk för spagettikod,. för komplexkod. för många sätt att ändra koden på etc...

Så jag tycker det är dags att göra det mer vant att nyttja OO...

286 ms totalt · 4 externa anrop · v20260731065814-full.a51de22e
132 ms — deklarationer (db)
0 ms — hämta statistik (cache)
149 ms — hämta tråd, inlägg och bilagor (db)
127 ms — ändringar (db)