webForumDet fria alternativet

Objektmodell

6 svar · 1 260 visningar · startad av Nickemannen

NickemannenMedlem sedan aug. 20003 575 inlägg
#1

Hej jag sitter och funderar på hur man skall göra i sin objektmodell

Säg att en Arbetare kan vara ansvarig för flera kunder.

Hur skall objektet se ut?

Skall man ha en lista t.ex.

Worker.Customers.Add(customer); osv
eller skall man ha
Worker.AddCustomer(customer);
och sedan ha en Worker.GetCustomerEnumerator() osv?

spangoMedlem sedan juni 20008 205 inlägg
#2

Jag tycker att det bör vara en AddCustomer istället för att exponera en ArrayList eller liknande direkt. Man kan ju tänkas vilja ha någon form av validering när man lägger till eller uppstädning när man tar bort kunder. Undantaget (som egentligen är det som ger mest funktionalitet) är om man gör en inre klass till Worker som är en WorkerCustomerList (och som implementerar t.ex. IList<Customer>) som kan ta hand om all validering, uppstädning etc.

NickemannenMedlem sedan aug. 20003 575 inlägg
#3

spango skrev:

Jag tycker att det bör vara en AddCustomer istället för att exponera en ArrayList eller liknande direkt. Man kan ju tänkas vilja ha någon form av validering när man lägger till eller uppstädning när man tar bort kunder. Undantaget (som egentligen är det som ger mest funktionalitet) är om man gör en inre klass till Worker som är en WorkerCustomerList (och som implementerar t.ex. IList<Customer>) som kan ta hand om all validering, uppstädning etc.

Japp det hade fått vara en AssignedCustomerCollection typ ja om det hade varit det andra alternativet.

Det tråkiga är ju att behöva göra en egen collection för varje samlingstyp Säg att en Personsamling kan tvingas ha flera olika samlingar beroende på olika krav på dem. Men jag e kluven man separerar ju koden mer med collections osv.

TomasJMedlem sedan feb. 200563 inlägg
#4

spango skrev:

Jag tycker att det bör vara en AddCustomer istället för att exponera en ArrayList eller liknande direkt. Man kan ju tänkas vilja ha någon form av validering när man lägger till eller uppstädning när man tar bort kunder.

Japp. Om man exponerar ett private fält som är mutable (dvs som går att modifiera) via en public property så har man ju inte vunnit särskilt mycket med att låta fältet vara private. Exempelvis vill du antagligen inte tillåta att klienten lägger in dubletter, vilket han kan göra om han får tillgång till en referens för en kollektion som han kan modifiera själv. Om man enkelt vill exponera en collection som inte går att modifiera av klienten så kan man i .NET returera en "ReadOnlyCollection<T>" som kan wrappa en "IList<T>" men förhindra modifiering.

Den välkände (inom systemutvecklingskretsar) skribenten Martin Fowler har skrivit en artikel som heter Data Access Routines " där han bl.a. visar med java hur man kan returnera en List som inte går att modifiera (m.h.a. metoden "Collections.unmodifiableList ").

OO-principerna för Java och C# är förstås desamma, och Java-gurun Joshua Bloch har skrivit en bok (som är prisbelönt och har fullt betyg på amazon.com) som heter Effective Java Programming Language Guide och skriver inte bara om att skydda kollektioner från att förändras utifrån utan om att generellt undvika att exponera fält som är mutable, i avsnittet "Item 13: Favor immutability".

Dessutom kan man konstatera att "Worker.Customers.Add(customer)" är en uppenbar "Law of Demeter " (LoD) violation (LoD är även känd under namnet "Don't talk to strangers ") som enkelt uttryckt går ut på att man inte ska anropa metoder på "stranger" objekt, d.v.s. inte göra så här: "a.getB().getC().getD().getE()".
LoD är visserligen kontroversiell (åtminstone angående hur rigoröst man ska tillämpa den) men det finns forskning som har påvisat att om man tillämpar LoD principerna så leder det till färre buggar. Det blir ofta opraktiskt att tolka LoD som en "lag" och helt följa den, men det bör åtminstone ringa en varningsklocka när man ser den typen av kod, trots att t.ex. Microsoft verkar inte bry sig alltför mycket, utan den där typen av kodexempel som bryter mot LoD (dvs av typen "Worker.Customers.Add(customer)") är inte särskilt ovanlig på MSDN.

/ Tomas

NickemannenMedlem sedan aug. 20003 575 inlägg
#5

TomasJ skrev:

Japp. Om man exponerar ett private fält som är mutable (dvs som går att modifiera) via en public property så har man ju inte vunnit särskilt mycket med att låta fältet vara private. Exempelvis vill du antagligen inte tillåta att klienten lägger in dubletter, vilket han kan göra om han får tillgång till en referens för en kollektion som han kan modifiera själv. Om man enkelt vill exponera en collection som inte går att modifiera av klienten så kan man i .NET returera en "ReadOnlyCollection<T>" som kan wrappa en "IList<T>" men förhindra modifiering.

Den välkände (inom systemutvecklingskretsar) skribenten Martin Fowler har skrivit en artikel som heter Data Access Routines " där han bl.a. visar med java hur man kan returnera en List som inte går att modifiera (m.h.a. metoden "Collections.unmodifiableList ").

OO-principerna för Java och C# är förstås desamma, och Java-gurun Joshua Bloch har skrivit en bok (som är prisbelönt och har fullt betyg på amazon.com) som heter Effective Java Programming Language Guide och skriver inte bara om att skydda kollektioner från att förändras utifrån utan om att generellt undvika att exponera fält som är mutable, i avsnittet "Item 13: Favor immutability".

Dessutom kan man konstatera att "Worker.Customers.Add(customer)" är en uppenbar "Law of Demeter " (LoD) violation (LoD är även känd under namnet "Don't talk to strangers ") som enkelt uttryckt går ut på att man inte ska anropa metoder på "stranger" objekt, d.v.s. inte göra så här: "a.getB().getC().getD().getE()".
LoD är visserligen kontroversiell (åtminstone angående hur rigoröst man ska tillämpa den) men det finns forskning som har påvisat att om man tillämpar LoD principerna så leder det till färre buggar. Det blir ofta opraktiskt att tolka LoD som en "lag" och helt följa den, men det bör åtminstone ringa en varningsklocka när man ser den typen av kod, trots att t.ex. Microsoft verkar inte bry sig alltför mycket, utan den där typen av kodexempel som bryter mot LoD (dvs av typen "Worker.Customers.Add(customer)") är inte särskilt ovanlig på MSDN.

/ Tomas

Tack för ett bra svar.

Det jag funderar på är inte att exponera en Lista, IList utan isåfall en egentypad collection där jag kan sätta de regler jag vill i en Add osv.
Ditt svar var väldigt bra och informationsrikt nu får jag fundera lite över vilket jag tycker känns bäst.

NickemannenMedlem sedan aug. 20003 575 inlägg
#6

Jag har nu läst lite mer på länkarna, boken har jag bara läst lite om vilket verkar vara en bra bok.

Det jag kan tycka är tråkigt är att om jag har dessa objekt, Musiker och skivor.

Säg att jag vill kunna lista alla skivor en musiker har på flera olika sorteringar, och kanske vilja ta ut antalet, kanske filtrera listan på olika sätt. Då blir jag tvungen att lägga många metoder i musiker som jag annars hade kunnat lagt i en egendefinerad collection.

hmm svårt.

Tror på att ha listan readonly och ha add och remove funktionalitet i klassen som äger listan.

TomasJMedlem sedan feb. 200563 inlägg
#7

Nickemannen skrev:

Det jag kan tycka är tråkigt är att om jag har dessa objekt, Musiker och skivor.

Säg att jag vill kunna lista alla skivor en musiker har på flera olika sorteringar, och kanske vilja ta ut antalet, kanske filtrera listan på olika sätt. Då blir jag tvungen att lägga många metoder i musiker som jag annars hade kunnat lagt i en egendefinerad collection.

Du kan nöja dig med en metod för filtrering om du tillämpar Specification pattern (PDF-länk).
Då definierar du alltså ett interface:

    public interface RecordSpecification
    {
        bool IsSatisfiedBy(Record record);
    }

som du sedan använder som formell parameter till en generell filtreringsmetod:

    public class Musician
    {
        public ReadOnlyCollection<Record> getFilteredSubsetOfRecords(
            RecordSpecification recordSpecification
        )
        {
            List<Record> filteredListOfRecords = new List<Record>();
            foreach(Record record in this.records) {
                if (recordSpecification.IsSatisfiedBy(record))
                {
                    filteredListOfRecords.Add(record);
                }
            }
            return new ReadOnlyCollection<Record>(filteredListOfRecords);
        }
	  // ....
	  private List<Record> records = new List<Record>();
    }

och som aktuell (konkret) parameter till metoden använder du implementationer av interfacet, t.ex. en sån här implementation:

    public class NumberOfSongsSpecification: RecordSpecification
    {
        private int min;
        private int max;

        public NumberOfSongsSpecification(int min, int max)
        {
            this.min = min;
            this.max = max;
        }
        
        public bool IsSatisfiedBy(Record record)
        {
            return this.min <= record.NumberOfSongs && record.NumberOfSongs <= this.max;
        }
    }

och anropet kan då se ut så här t.ex. för att filtrera ut alla skivor där antalet låtar är 9-11:


	ReadOnlyCollection<Record> records = musician.getFilteredSubsetOfRecords(
      	new NumberOfSongsSpecification(9, 11)
	);

Ett alternativ till att skapa ett eget interface är att använda .NET's inbyggda stöd för Specification, m.h.a. "Predicate<T>".
Exempel:

    public class Musician
    {
        public ReadOnlyCollection<Record> getFilteredSubsetOfRecords(Predicate<Record> recordFilterPredicate)
        {
            List<Record> filteredSubsetOfRecords = records.FindAll(recordFilterPredicate);
            return new ReadOnlyCollection<Record>(filteredSubsetOfRecords);
        }
	  // ...
	  private List<Record> records = new List<Record>();
    }

Ett anrop kan då se ut så här:

            ReadOnlyCollection<Record> records = musician.getFilteredSubsetOfRecords(
                delegate(Record record) { 
                    return 9 <= record.NumberOfSongs && record.NumberOfSongs <= 11;
                }
            );

När det gäller sortering så kan man använda "Comparison<T>" så här:

        public ReadOnlyCollection<Record> getFilteredSubsetOfRecords(
            Predicate<Record> recordFilterPredicate, 
            Comparison<Record> recordSortComparison
        )
        {
            List<Record> filteredSubsetOfRecords = records.FindAll(recordFilterPredicate);
            filteredSubsetOfRecords.Sort(recordSortComparison);
            return new ReadOnlyCollection<Record>(filteredSubsetOfRecords);
        }

Två anrops-exempel med både filtrering och sortering:

            ReadOnlyCollection<Record> records = musician.getFilteredSubsetOfRecords(
                delegate(Record record) { return record.TotalLengthInSeconds >= 2400; },
                delegate(Record x, Record y) { return -1*(x.NumberOfSongs - y.NumberOfSongs); }
            );

            ReadOnlyCollection<Record> records = musician.getFilteredSubsetOfRecords(
                delegate(Record record) { return record.NumberOfSongs >= 10; },
                delegate(Record x, Record y) { return 1 * (x.TotalLengthInSeconds - y.TotalLengthInSeconds); }
            );

/ Tomas

137 ms totalt · 3 externa anrop · v20260731065814-full.3ab8d573
0 ms — hämta forumlista (cache)
0 ms — hämta statistik (cache)
133 ms — hämta tråd, inlägg och bilagor (db)