Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien
Toon posts:

[C#] Een Abstract Singleton base class + Generic GetInstance

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik schrijf dit topic niet zozeer omdat ik een enorm probleem heb, maar meer omdat ik tegen een redelijk intellectueel uitdagend inheritance/design pattern probleem aan ben gelopen bij een eigen projectje. (Ten minste, ik hoop dat het intellectueel uitdagend is, anders heb ik over iets heen gekeken)

Probleem voorstelling:
Ik heb een programma, dat programma heeft settings, die moeten worden opgeslagen en opgehaald worden bij het respectievelijk sluiten en opstarten van het programma. Die settings wil ik graag gescheiden houden, maar wel in 1 keer kunnen saven. Voorbeeldje:
Ik wil drie settings classes hebben: (fictief)
1. UISettings
2. LibrarySettings
3. GlobalizationSettings
En in 1 statement kunnen opslaan bij het sluiten van mn programma:
Settings.SaveAll() // bijvoorbeeld..

Alle settings classes zijn natuurlijk singleton. (Static kan niet, dan kan je ze namelijk niet serializen?)
Het lijkt me makkelijk om een base class te hebben die al het benodigde laden en opslaan van *alle* settings classes kan doen.

Mijn oplossing: (Let vooral op GetInstance<>(), de rest vloeit daaruit voort.)
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
40
41
42
43
44
    public abstract class Settings
    {
        protected string fileName; // De filenaam van de file waarin de setting wordt opgeslagen. 

        protected static List<Settings> allSettings = new List<Settings>();
        protected static bool ContainsSetting(Type t)
        {// Kijkt in allSettings of er een Setting van type t tussen zit.           
        }

        protected static T GetSetting<T>() where T: Settings
        {// returned de setting in allSettings van type T, anders null.
        }

        protected void Save()
        {// Saved 'this' (Let op: geen static!        }
        protected static T Load<T>(string filename) where T: Settings
        { // Haalt object op uit IsolatedStorage.
          // Returned object as T. 
          // (dus null als het bestand waar filename naar verwijst niet van type T is.      
         }

        public static T GetInstance<T>() where T : Settings, new()
        {
            // Check if an instance of T already exists:
            if (Settings.ContainsSetting(typeof(T)))
                return GetSetting<T>();

            // load instance:
            T instance = Load<T>(new T().fileName);

            // if loading didn't succeed, return a new(default) instance:
            if (instance == null)
                instance = new T();

            // if loading did succeed, do not forget to add to allSettings: 
            allSettings.Add(instance);
            return instance;
        }
        public static void SaveSettings()
        {
            foreach (Settings setting in allSettings)
                setting.Save();
        }
    }


Dit werkt goed. (Alleen is het voor buitenstaanders waarschijnlijk bijna onmogelijk om te begrijpen hoe ze een een instance kunnen krijgen ivm de Generic GetInstance<T>().. )

Het probleem met deze oplossing is alleen dat child Settings classes een public constructor moet hebben ivm GetInstance<>().. (Een Generic type mag van de C# compiler niet genewed worden als de constructor niet public is. )
Dat doet het hele gedoe weer teniet. Maar aangezien dit een compleet doe het zelf projectje is, ben ik de enige die er rekening mee hoeft te houden, dus de kans op fouten is niet zo groot. (Alleen de kans dat ik op den duur de werking van de GetInstance<>() vergeet is natuurlijk wel aanwezig :P )

Wat wel een voordeel is, is dat een child class er nu zo uitziet:
C#:
1
2
3
4
5
6
7
8
9
10
11
    [Serializable]
    public class KeyWordSettings : Settings
    {
        const string settingFileName = "KeywordSetting.conf";
       
        public KeyWordSettings()
            : base(settingFileName)
        { }

       // En hier natuurlijk nog de inhoud qua setting fields en properties.
    }


Er is 1 oplossing die waarschijnlijk het best zou werken:
Iedere child-class een onafhangelijke GetInstance() en een destructor. En dan uiteindelijk alsnog de base class het opslaan en laden laten regelen.

Iemand nog leuke, interessante of ronduit vreemde suggesties?

Verwijderd

Niet slecht. Of het fijn werkt hangt af van hoe je het gebruikt en wat je wilt.

Je kunt prima een singleton pattern toepassen met classes die van een abstract class afleiden, alleen je hebt dan geen referentie. Wil je dat toch om ze bijvoorbeeld in een lijst te zetten zoals jij wilt, dan markeer je in zo'n geval niet de class als static, maar maak je een static property Instance die, thread-safe uiteraard, een instance geeft (en eerst maakt indien nodig). In de constructor zou je de nieuwe settings class kunnen registreren bij een static Settings controller, zodat je nog steeds op één plek kan opslaan.

Wat je zegt over een public constructor zonder parameters klopt inderdaad maar hoeft geen beperking te zijn. Als alle afgeleide settings classes dezelfde constructor hebben (zelfde signature), gebruik dan reflection. Stel dat alle afgeleide settings classes een constructor met een integer als parameter. Dan kan je dit doen:

C#:
1
2
ConstructorInfo constructorInfo = typeof(T).GetContructor(new Type[] { typeof(int) });
Settings settings = (Settings)constructorInfo.Invoke(new object[] { 1 });


Zo maak je dus een Settings class (type T) met een bepaalde constructor. Ik geef ter illustratie even het getal 1 mee. Let op dat GetConstructor null teruggeeft als de constructor niet gevonden wordt.

Als de settings classes een specifieke constructor hebben, dan kun je overwegen om een static Initialize() te maken per afgeleide settings class. Deze roept de specifieke constructor aan van je settings class, en slaat deze op in een private static field om terug te kunnen geven in de static property Instance. Die Initialize() method roep je dan bij het starten van je programma. Dit registreert dan meteen de settings class op de static Settings controller voor het in één keer kunnen opslaan van je gegevens.

[ Voor 5% gewijzigd door Verwijderd op 16-03-2008 18:17 ]


Verwijderd

Topicstarter
Dankjewel voor je reply.

Ik heb uiteindelijk besloten om alle generics erin te laten zitten. Maar inderdaad in de GetInstance<>() method reflectie te gebruiken om de constructor van een nieuwe class aan te roepen.

Verder heb ik een abstract getFileName() method gedefinieerd, waardoor alle childclasses een compiler error krijgen als ze geen filename kiezen, wat weer makkelijker is voor ze.

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

H!GHGuY

Try and take over the world...

Er zijn betere manieren om dit te doen.

Ik herinner me een reply op een soorgelijk topic van een van de mods hier, waar mooi aangehaald werd waarom abstract singleton een contradictio in terminis is.

Wat je eerder zoekt is een factory method of al dan niet een abstract factory. Deze patterns zijn beter geschikt om jouw probleem op te lossen.


Je huidige oplossing is bovendien nogal raar.

Wat is mis met:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SettingsManager
{
    private Dictionary<Type, object> cache;
 
    T RestoreSettings<T>(Type t)
    {
        // als in cache, return cached settings
        // anders in cache laden en return
    }
    void SaveSettings(Type t, object o)
    {
       // Steek in cache
    }
    void FlushSettings()
    {
        // schrijf alles uit
    }
}


Je kan de performance zelf in de hand hebben door te flushen wanneer jij het wil, je hebt een volledig centraal, generic en proper geinterfaced systeem. Er kan hierin nog veel getweaked worden natuurlijk, maar dit kan een goeie basis zijn.

[ Voor 50% gewijzigd door H!GHGuY op 18-03-2008 19:52 ]

ASSUME makes an ASS out of U and ME


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Ik vraag me eigenlijk af waarom hier het wiel opnieuw uitgevonden moet worden.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Verwijderd

Topicstarter
H!GHGuY,
Dankjewel voor je voorbeeld. Ik kom er nu net achter dat ik geen dictionary heb gebruikt. (heel dom 8)7 )
Ik denk dat we eigenlijk hetzelfde doen, behalve dat mijn implementatie abstract is en alleen childclasses kan opslaan. Voor de duidelijkheid (ik heb veel veranderd sinds de eerste keer), zal ik mijn code posten:
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
    [Serializable]
    public abstract class Settings
    {
        // the filename all child classes have to decide upon. 
        protected abstract string getFileName();

        // The list of all active instances of Settings child classes.
        private static Dictionary<Type, Settings> settings = new Dictionary<Type, Settings>();
        
        // Methods to save and load settings (to disk)
        protected void Save()
        {
         // Slaat de setting op. (Dit is dus op instance niveau)
        }
        protected static T Load<T>(string filename) where T: Settings
        {
        // Haalt een instance van disk op, returned null als die er niet is.
        }

        // Methods to instantiate a new Settings child class.
        public static T GetInstance<T>() where T : Settings
        {
            // (Ik heb de code tussen de comments weggehaald, dat leest hopelijk wat makkelijker)
            // Check if an instance of T already exists in settings. If so, return it:

            //  Create a new instance of T:

            // Get filename from instance of T:

            // Try to load an instance from disk using the filename:

            // If loading succeeded, add the loaded instance to settings and return
            // Otherwise return new Instance.
        }
        protected static T createInstance<T>() where T : Settings
        {
        // Maakt een nieuwe class T met behulp van Reflection.
        }        
    }


Pedorus,

Ik gebruikte eerst gewoon de automatische project settings van Visual Studio. Ik werd er echter heel snel knettergek van dat ie tussen elke build zn settings vergat. Dus heb ik maar een eigen implementatie geschreven met behulp van Serialization. (Serializen is ook redelijk nieuw voor me, maar werkt verrassend makkelijk.)

Verwijderd

Laat dat vergeten van default settings niet een reden zijn om van het hele System.Configuration verhaal af te stappen.

Ik weet niet welke Visual Studio jij gebruikt, maar mijn Visual Studio 2008 vergeet geen settings. Ik kan lijsten en allerlei complexe eigen types opslaan en heb volledige designer support dankzij wat attributes en interfaces implementeren.

En stel dat Visual Studio instellingen vergeet (kwam bij mij in versie 2005 nog wel eens voor), dan stel je ze toch in als ze niet bestaan bij het laden van je applicatie? Je wilt toch ook een optie voor je gebruikers om terug te gaan naar standaard instellingen, dus je moet toch ergens je standaard instellingen inbouwen? Ik zie het probleem niet.
Pagina: 1