[C#] Factory pattern, juiste keuze?

Pagina: 1
Acties:
  • 287 views sinds 30-01-2008
  • Reageer

  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
Hallo,

Ik ben me op mijn werk wat aan het verdiepen in design patterns.
We hebben nu een programmeer opdracht en ik dacht voor de structuur aan het abstract factory pattern maar ik snap een aantal dingen niet goed en vraag me daarom af of het pattern in mijn geval wel de juiste oplossing is en of er misschien een ander pattern is dat beter aansluit.

Het probleem:

We gaan een file based queue maken en om de files te schrijven heb ik 2 verschillende implementaties nodig van de filewriter.

De client (queuehandler) moet dus kiezen welke instantie te maken van de filewriter.

ik heb de volgende classen

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class QueueItemWriter
    {
        public abstract void WriteItem();
    }


public class ContentWriter : QueueItemWriter
    {
        public void ContentWriterMethod()
        { 
            
        }

        public override void  WriteItem()
        {
            
        }
 
    }

public class MetaWriter : QueueItemWriter
    {

        public void MetaWriterMethod()
        {

        }

        public override void WriteItem()
        {
            
        }
    }


De abstracte factory en de concrete factories:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//abstract factory
public abstract class QueueItemWriterFactory
    {
        public abstract QueueItemWriter CreateQueueItemWriter();
    }

//concrete factory 1
public class MetaFileWriterFactory : QueueItemWriterFactory
    {

        public override QueueItemWriter CreateQueueItemWriter()
        {
            return new MetaFileWriter();
        }
    }

//concrete factory 2
public class ContentFileWriterFactory : QueueItemWriterFactory
    {

        public override QueueItemWriter CreateQueueItemWriter()
        {
            return new ContentFileWriter();
        }
    }


Wat ik nu niet echt begrijp is het volgende:

- als ik nu de specifieke MetaFileWriter methode aan wil roepen dan moet ik het ding alsnog casten naar een MetaFileWriter. (ik ben dus niet veel opgeschoten door het factory pattern in te zetten)

- Ik kan toch net zo goed het volgende doen?

C#:
1
2
3
4
5
6
 public void SomeMethod(object content, object meta)
        {
          
            MetaFileWriter metaFileWriter = new MetaFileWriter();

        }


Ik ga er dus eigenlijk vanuit dat het factory pattern niet van toepassing is op mijn situatie, of toch wel?
En wat is dan het voordeel van dit pattern?

En hoe voorkom ik dat ik alsnog moet gaan casten?

Of is er een ander pattern dat beter van toepassing is op mijn situatie?

Dank je! ik hoop dat jullie me wat meer begrip kunnen geven design patterns!

[ Voor 25% gewijzigd door 4of9 op 23-03-2007 12:24 ]

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


  • whoami
  • Registratie: December 2000
  • Laatst online: 00:54
als ik nu de specifieke MetaFileWriter methode aan wil roepen dan moet ik het ding alsnog casten naar een MetaFileWriter. (ik ben dus niet veel opgeschoten door het factory pattern in te zetten)
Waarom moet je nog casten ?
Je write method is toch virtual ?

Als je dit doet:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QueueItemWriterFactory factory;

// check config file .. blaat
switch( factoryToCreate )
{
    case "metafile": 
          factory = new MetaFileWriterFactory();
          break;

    case "contentfile" :
          factory = new ContentFileWriterFactory();
          break;
  
    default :
          throw new Exception("unknown factory specified.");
}

Dan kan je nu toch overal je factory gebruiken, en je QueueWriter gebruiken, zonder dat je de implementatie hoeft te weten ?
code:
1
2
3
QueueItemWriter writer = factory.CreateQueueItemWriter();

writer.WriteItem();

?

Of mis ik iets in je verhaal ?

[ Voor 15% gewijzigd door whoami op 23-03-2007 12:31 ]

https://fgheysels.github.io/


  • Vedett.
  • Registratie: November 2005
  • Laatst online: 08:28
Het abstract factory pattern is wel wat overkill in deze situatie. Het wordt pas echt interessant (en een correcte implementatie van het pattern) als je met je factory meerdere types van gerelateerde objecten kan maken.

whoami heeft wel een punt dat je niet echt hoeft te casten. Maar je bedoelt misschien dat je toch nog wat properties moet zetten op je object. Dat moet inderdaad op je concrete instantie van de class gebeuren.

Is het niet een idee om je method
C#:
1
2
3
4
5
6
public void SomeMethod(object content, object meta) 
{ 
           
    MetaFileWriter metaFileWriter = new MetaFileWriter(); 

}


te veranderen in
C#:
1
2
3
4
public void SomeMethod(object content, object meta, QueItemWriter writer)
{ 
    writer.Write("text");
}


Of maak er desnoods een property van in je class waarin SomeMethod staat?
C#:
1
this.QueItemWriter.Write("text");

  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
Wat het casten betreft:

als ik een QueueIItemWriter object creeer dmv een factory en ik wil een methode aanroepen die speciefiek is voor de betreffende Writer zoals de MetaWriterMethod() in de metaWriter dan moet ik mijn QueueItemWriter casten naar een MetaWriter omdat queueItemWriter geen MetaWriterMethod() bevat.

Ik neem aan dat mijn geval niet geschikt is voor een factory pattern.

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


  • Vedett.
  • Registratie: November 2005
  • Laatst online: 08:28
En waarom gebruik je niet de WriteItem methode? Het hele verhaal is bedoeld zodat je client (buiten de correcte instantie gebruiken) zich niets moet aantrekken van naar waar er wordt geschreven. Dus: die concrete methdoen kunnen handig zijn, maar waarom heb je ze nodig? Als je hier positief op kunt antwoorden moet je je design toch nog maar eens opnieuw bekijken.

  • whoami
  • Registratie: December 2000
  • Laatst online: 00:54
^^ met Vedett.
Wat is het nut van die specifieke methods ? In wat verschillen ze van die WriteItem method ?

https://fgheysels.github.io/


  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
Vedett. schreef op vrijdag 23 maart 2007 @ 13:26:
En waarom gebruik je niet de WriteItem methode? Het hele verhaal is bedoeld zodat je client (buiten de correcte instantie gebruiken) zich niets moet aantrekken van naar waar er wordt geschreven. Dus: die concrete methdoen kunnen handig zijn, maar waarom heb je ze nodig? Als je hier positief op kunt antwoorden moet je je design toch nog maar eens opnieuw bekijken.
Die Specifieke Methoden zijn nog even hypothetisch.
Ik begrijp nu dat mocht ik die nodig hebben dat ik een ander ontwerp of ander pattern moet kiezen.

Het wordt me al iets helderder.

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Maak ook even het onderscheid tussen het factory en het abstract factory pattern.

Meestal wordt de abstract factory door een instelling concreet gemaakt (config file en dergelijke) waar er bij een gewone factory gewoonlijk maar 1 factory is.

Ik heb eerder de indruk dat je niet afhankelijk van een persistente/globale setting OF een metawriter OF een contentwriter wil doorheen je hele programma.
Ik denk dat je eerder afhankelijk van de situatie/functie een meta/content writer nodig hebt:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public abstract class QueueItemWriter
{
    public abstract void WriteItem();
}

public class ContentWriter : QueueItemWriter
{
    public override void  WriteItem()
    {
         
    }
 
}

public class MetaWriter : QueueItemWriter
{
    public override void WriteItem()
    {
        
    }
}

public QueueItemWriterFactory
{
  public static QueueItemWriter GetWriterFor(WorkItem wi)
  {
    switch (wi.Type)
    {
      case eContent:
        return new ContentFileWriter();
      case eMeta:
        return new MetaFileWriter();
      default:
        return null; // throw exception ?
    }
  }
}

Ik heb nu even de factory als aparte class gemaakt. Je kan de factory method binnen de QueueItemWriter class stoppen als je de extra uitbreidbaarheid kan missen. (Dan zitten meestal alle klassen in 1 assembly die je niet kan uitbreiden met andere writers. Stop je de writers in aparte assemblies dan kan je het nog ingewikkelder maken met plugins van writers en nog meer zoets)

ASSUME makes an ASS out of U and ME


  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
Ik denk dat het (abstract) factory pattern niet echt voor mijn situatie van toepassing is denk ik nu.

Ik snap nu alleen wel het pattern en waar het handig voor is. Ook spreekt het idee van een abstracte classe van de QueueItemWriter me wel aan, alleen ben ik nu aan het twijfelen tussen een Interface of een abstracte classe.

Ik probeer even de voors en tegens van bijde manieren op een rijtje te zetten.

Ik merk dat ik wel "Object based" maar nog niet echt OO programmeer (ik kom tenslotte uit de classic ASP hoek). Ik maak te weinig gebruik van overerving etc.

Dit was weer een mooi leer moment :)

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


  • Vedett.
  • Registratie: November 2005
  • Laatst online: 08:28
aaahh, interfaces of abstrace classes. Daar zijn al heel wat pagina's over vol geluld.
Ik stel mezelf altijd de volgende vraag: Is er algemene logica die ik wil implementeren zowel nu als in de toekomst. Als je hier neen op kunt antwoorden, wat een zware beslissing is, zou ik altijd gaan voor een interface. In het andere geval, altijd gaan voor een abstrace class (of een gewone niet sealed class als deze op zichzelf ook nuttig is).

In uw geval zou ik gaan voor de abstracte class. Op die manier kan je bijvoorbeeld een concrete methode implementeren in je abstrace class die niet anders doet dan abstracte methoden oproepen.
Stel je een overload van WriteItem voor die een array van objecten accepteert. Dan zou je binnen een for-each voor elk object in die array de abstrace WriteItem methode kunnen oproepen. ContentWriter en MetaWriter moeten dan elleen maar de WriteItem methode die één object (dus niet de array) aanvaardt overriden.

  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
Ik ben nu gegaan voor de abstracte classe.

Nu heb ik echter een 3 writer die ik wil afleiden maar heb nu het volgende probleem.

Mijn abstracte classe definieerd een CreateFile(Page page, string filename) methode.

Die override ik in de afgeleide classen. Mijn 3e writer echter heeft het Page object niet nodig als input parameter.

Nu zit ik met de volgende vraag:

Moet ik mijn base classe een extra methode geven CreateFile(string filename)
Zoja: wat moet ik dan met de implementatie doen in de afgeleide classen?

Of is mijn 3e writer geen "IS-A" en moet hij gewoon niet afleiden van mijn base classe?

Of mag ik gewoon erven en de CreateFile overriden en een null toestaan als Page object? (lijkt me niet correct)

Ik weet niet goed hoe ik hier mee om moet gaan.

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


  • whoami
  • Registratie: December 2000
  • Laatst online: 00:54
Of je 3de writer een 'writer' is of niet, kunnen we niet echt weten adhv de info die je nu geeft. :)

Je kan voor je extra constructor gaan, en ervoor zorgen dat die method bv een exceptie gooit als ie gebruikt wordt door je eerste en tweede writer.
Zowiezo zal je die CreateFile constructor / method met 2 parameters ook in je 3de writer moeten implementeren als hij in je abstracte class als 'abstract' gedefinieerd is.

Echter, misschien is je design wel niet goed; maar daar kunnen we hier ook niet echt over oordelen.

https://fgheysels.github.io/


  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
whoami schreef op maandag 26 maart 2007 @ 10:32:
Of je 3de writer een 'writer' is of niet, kunnen we niet echt weten adhv de info die je nu geeft. :)

Je kan voor je extra constructor gaan, en ervoor zorgen dat die method bv een exceptie gooit als ie gebruikt wordt door je eerste en tweede writer.
Zowiezo zal je die CreateFile constructor / method met 2 parameters ook in je 3de writer moeten implementeren als hij in je abstracte class als 'abstract' gedefinieerd is.

Echter, misschien is je design wel niet goed; maar daar kunnen we hier ook niet echt over oordelen.
Mijn base writer heeft een location member die gedeeld wordt door alle 3 de writers.

De metafile writer implementeerd de CreateFile method als een "xmlwriter"
De content file writer implemeneerd de CreateFile als een text writer

Mijn 3e writer is een "semafoor file" writer. Die schrijft een leeg text bestandje.

De eerste 2 writers halen informatie uit het page object. De 3e writer heeft die info helemaal niet nodig.
Dus wat is dan de correcte manier? Is het een goed design als ik in mijn abstracte classe een extra overload zet CreateFile(string filename) en als ik die in de eerste 2 writers implemteer een exceptie laat throwen als ze aangeroepen worden?

Of mag ik gewoon een null value toestaan voor het page object in mijn 3 writer?

Of zijn er nog andere manieren om dit soort design probleempjes op te lossen?

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


  • mOrPhie
  • Registratie: September 2000
  • Laatst online: 01-12 17:27

mOrPhie

❤️❤️❤️❤️🤍

4of9 schreef op maandag 26 maart 2007 @ 10:40:
Of zijn er nog andere manieren om dit soort design probleempjes op te lossen?
Ja, met delegates. Dat is in C# imho zelfs eleganter; het is er immers voor bedoeld. :)

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    public enum QueueItemWriterType
    {
        ContentWriter,
        MetaWriter
    }

    class QueueItemWriter
    {
        private delegate void WriteItemDelegate(string s);
        private WriteItemDelegate _writer;

        public QueueItemWriter(QueueItemWriterType type)
        {
            switch (type)
            {
                case QueueItemWriterType.ContentWriter:
                    _writer = new WriteItemDelegate(ContentWriterMethod);
                    break;
                case QueueItemWriterType.MetaWriter:
                    _writer = new WriteItemDelegate(MetaWriterMethod);
                    break;
            }
        }

        public void WriteItem(string s)
        {
            _writer(s);
        }

        private void ContentWriterMethod(string s)
        {
            Console.WriteLine("Content: {0}", s);
        }

        private void MetaWriterMethod(string s)
        {
            Console.WriteLine("Meta: {0}", s);
        }
    }


En de implementatie:

C#:
1
2
3
4
5
QueueItemWriter qw1 = new QueueItemWriter(QueueItemWriterType.ContentWriter);
QueueItemWriter qw2 = new QueueItemWriter(QueueItemWriterType.MetaWriter);

qw1.WriteItem("Test");
qw2.WriteItem("Test");


De output is nu:

code:
1
2
Content: Test
Meta: Test


Echt meer voorgekauwd kan het niet. ;)

Een experimentele community-site: https://technobabblenerdtalk.nl/. DM voor invite code.


  • whoami
  • Registratie: December 2000
  • Laatst online: 00:54
Ik zit me sterk af te vragen waarom je hier voor delegates zou kiezen, en dan zeker voor de implementatie die je nu laat zien ? :?
Ik vind dit helemaal niet eleganter; het is omslachtig, en zal ook minder efficient zijn.

Daarnaast is het imho ook minder duidelijk / onderhoudbaar, aangezien je nu je verschillende implementaties in één class hebt.
Hier kan je imho beter gebruik maken van een interface / abstract class, en dan de verschillende concrete implementaties laten overerven van die class / die interface implementeren en gebruik maken van polymorphisme.

[ Voor 23% gewijzigd door whoami op 27-03-2007 21:45 ]

https://fgheysels.github.io/


  • whoami
  • Registratie: December 2000
  • Laatst online: 00:54
4of9 schreef op maandag 26 maart 2007 @ 10:40:
[...]


Mijn base writer heeft een location member die gedeeld wordt door alle 3 de writers.

De metafile writer implementeerd de CreateFile method als een "xmlwriter"
De content file writer implemeneerd de CreateFile als een text writer

Mijn 3e writer is een "semafoor file" writer. Die schrijft een leeg text bestandje.

De eerste 2 writers halen informatie uit het page object. De 3e writer heeft die info helemaal niet nodig.
Dus wat is dan de correcte manier? Is het een goed design als ik in mijn abstracte classe een extra overload zet CreateFile(string filename) en als ik die in de eerste 2 writers implemteer een exceptie laat throwen als ze aangeroepen worden?

Of mag ik gewoon een null value toestaan voor het page object in mijn 3 writer?

Of zijn er nog andere manieren om dit soort design probleempjes op te lossen?
Kan je die 'Page' niet meegeven bij de constructor van de content & de metafile writer ?
De constructor van je semafoor-file writer neemt dan weer geen Page object mee. (Van waar komt dat page object trouwens ? ).

De verantwoordelijkheid van het creeëren van de specifieke Writer laat je dan weer over aan een factory class. (Die geen abstract factory hoeft te zijn).
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Factory
{
     public static  FileWriter CreateFileWriter ( Page p )
     {
           ...
     }

     public static ContentWriter CreateContentWriter( Page p )
     {
             ...
     }

}
Al vraag ik me momenteel wel een beetje af wat het nut / extra waarde van deze factory class is... Het abstraheert misschien wel iets meer hoe de specifieke writer gecreeërd moet worden enzo...

https://fgheysels.github.io/


  • mOrPhie
  • Registratie: September 2000
  • Laatst online: 01-12 17:27

mOrPhie

❤️❤️❤️❤️🤍

whoami schreef op dinsdag 27 maart 2007 @ 21:43:
Ik vind dit helemaal niet eleganter; het is omslachtig, en zal ook minder efficient zijn.
Minder effecient? Die moet je me even uitleggen. :)
Daarnaast is het imho ook minder duidelijk / onderhoudbaar, aangezien je nu je verschillende implementaties in één class hebt.
Fair enough. Het hangt natuurlijk wel erg van de omvang af. Als je weet dat er veel implementaties van WriteMethod komen, dan is mijn voorbeeld af te raden. Gaat het echter 2 of 3 implementaties, dan zie ik niet in waarom dat minder duidelijk zou zijn.

Het zwaartepunt in de keuze van implemenentatie zit 'm uiteindelijk in flexibiliteit vs. uitbreidbaarheid. Delegates kun je namelijk makkelijker definieren dat je meerdere implementaties tegelijk aanroept:

C#:
1
2
3
4
DelegateFunc c = DelegateFunc(myFunc1);
c += DelegateFunc(myFunc2);

c(); //hier worden myFunc1 en myFunc2 aangeroepen.


Ik heb hier toch echt te weinig info om de ene voor de andere te kiezen. Hier is in elk geval wat over te lezen: http://msdn2.microsoft.com/en-us/library/ms173173.aspx

Hoe dan ook. In the end wilde hij weten of er nog andere manieren waren voor het probleem. Zodoende de delegate. :)

[ Voor 4% gewijzigd door mOrPhie op 27-03-2007 22:03 ]

Een experimentele community-site: https://technobabblenerdtalk.nl/. DM voor invite code.


  • whoami
  • Registratie: December 2000
  • Laatst online: 00:54
mOrPhie schreef op dinsdag 27 maart 2007 @ 22:01:
[...]


Minder effecient? Die moet je me even uitleggen. :)
Een method via een delegate aanroepen zorgt zowiezo voor meer overhead.
Het zwaartepunt in de keuze van implemenentatie zit 'm uiteindelijk in flexibiliteit vs. uitbreidbaarheid. Delegates kun je namelijk makkelijker definieren dat je meerdere implementaties tegelijk aanroept:
Wat houdt je tegen om al je writer instances in een collection bij te houden, over de collectie iteraten en voor iedere instance de WriteFile method aan te roepen. :)

https://fgheysels.github.io/


  • mOrPhie
  • Registratie: September 2000
  • Laatst online: 01-12 17:27

mOrPhie

❤️❤️❤️❤️🤍

whoami schreef op dinsdag 27 maart 2007 @ 22:08:
[...]
Een method via een delegate aanroepen zorgt zowiezo voor meer overhead.
Ik kreeg al het idee dat je dat vond. Ik ken er de achtergrond echter niet van en kan deze ook niet vinden. :)
Wat houdt je tegen om al je writer instances in een collection bij te houden, over de collectie iteraten en voor iedere instance de WriteFile method aan te roepen. :)
Omdat juist dat (voor een simpele implementatie) erg omslachtig is?

Zoals ik al zei hangt het heel erg van het aantal implementaties van WriteItem, de omvang van deze implementaties en de gewenste functionaliteit af.

Een experimentele community-site: https://technobabblenerdtalk.nl/. DM voor invite code.


Verwijderd

4of9 schreef op maandag 26 maart 2007 @ 10:40:
De metafile writer implementeerd de CreateFile method als een "xmlwriter"
De content file writer implemeneerd de CreateFile als een text writer

Mijn 3e writer is een "semafoor file" writer. Die schrijft een leeg text bestandje.
Ik zal wel iets te kort door de bocht denken, maar de enige 2 processen die een semaphore file mogen schrijven zijn de metafile en content file schrijver routines. Daar komt geen factory aan te pas. Misschien denk je gewoon veel te moeilijk? Waarom een semafoor file writer afleiden van een eigen base class terwijl ieder framework daar al prima ondersteuning voor biedt?

  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
Om de flow een beetje te beschrijven:

Er wordt een semafoor file geschreven, dan een meta en dan een content file, als die processen klaar zijn wordt de semafoor file "vrij gegeven" zodat een ander process weet dat de bestanden "klaar" staan.

Whoami: ik had ook al zitten denken aan de contructor, ik had er echter helemaal niet aan gedacht dat de constructor per classe verschillende kon zijn.

En het Page object is een object dat informatie bevat dat door een webspider is verzameld. Deze webspider moet echter bepaalde informatie naar een filebased qeueue schrijven. Vandaar het page object.

Een factory is in dit geval niet echt nodig. Dank je voor de tips allemaal!

[ Voor 17% gewijzigd door 4of9 op 28-03-2007 08:12 ]

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...

Pagina: 1