János's profileJános JankaPhotosBlogListsMore ![]() | Help |
|
September 28 Observer patternMiközben renoválom az egyik régebbi projektemet, gondoltam egy-két okosságot megosztok, amit érdemes lehet Delphi fejlesztőként ismerni. Pl. előfordulhatnak olyan esetek, amikor tudni szeretnénk valamiről/valakiről, hogy mi is történt vele. Esetemben például ez az autentikált felhasználót jelenti. Azt szeretném, hogy a programból bárhonnan felhasználót lehessen váltani anélkül, hogy 67 ablakon át kelljen ugrálni. Az TApplicationEvents osztállyal ugyan eltudunk kapni minden bill. leütést és ezt felhasználva autentikálni, de ez kevés. Ki fogja értesíteni az objektumokat (ablakokat), hogy itt kijelentkeztek és más jelentkezett be? Ugyanis ilyenkor a jogosultságokat is kezelni kell! Ill. amihez meg nincs is joga az adott usernek, egyszerűen bezárni (persze a mentésre is figyelni kell). Na pl. ehhez tökéletes az Observer pattern. Az ötlet egyszerű: a figyelt objektum tárolja a figyelők referenciáit, majd pedig kiértesíti őket, ha megváltozik valami/szükséges. Én implementáltam ezt a patternt generikusokkal, itt egy pl.: unit Observer; interface uses Classes; type IObservableObject<T> = interface; IObserver<T> = interface ['{7C62D715-BE5E-4BC7-AF91-56D108957114}'] procedure ObservedObjectChanged(Value: IObservableObject<T>); end; IObservableObject<T> = interface ['{3E21EE2B-9A31-4406-B4BE-1ABB42DAA9F1}'] procedure Attach(Observer: IObserver<T>); procedure Detach(Observer: IObserver<T>); procedure Notify; end; TObservableObject<T> = class(TInterfacedObject, IObservableObject<T>) private FInterfaceList: IInterfaceList; protected procedure Attach(Observer: IObserver<T>); procedure Detach(Observer: IObserver<T>); procedure Notify; end; implementation procedure TObservableObject<T>.Attach(Observer: IObserver<T>); begin if not Assigned(Observer) then Exit; if not Assigned(FInterfaceList) then FInterfaceList:= TInterfaceList.Create; if FInterfaceList.IndexOf(Observer) = -1 then FInterfaceList.Add(Observer); end; procedure TObservableObject<T>.Detach(Observer: IObserver<T>); begin if Assigned(Observer) and Assigned(FInterfaceList) then begin FInterfaceList.Remove(Observer); if FInterfaceList.Count = 0 then FInterfaceList:= nil; end; end; procedure TObservableObject<T>.Notify; var I: Integer; begin A használata egy picivel körülményesebb: Ez lesz a figyelt objektum: type // Mégegy dolog, ami működik C#-ban és Delphiben nem: // IAuthentication = interface(IObservableObject<IAuthentication>) // E2086 Type 'IAuthentication' is not yet completely defined IAuthentication = interface ['{62369856-94E1-41C5-BBCE-C3A025287040}'] function GetEmployee: IEmployee; procedure SetEmployee(Value: IEmployee); property Employee: IEmployee read GetEmployee write SetEmployee; end; IObservableAuthentication = interface(IObservableObject<IAuthentication>) ['{2AF86F76-F708-42AA-8859-8070B1B15F6E}'] end; TAuthentication = class(TObservableObject<IObservableAuthentication>, IAuthentication) private FEmployee: IEmployee; function GetEmployee: IEmployee; procedure SetEmployee(Value: IEmployee); public property Employee: IEmployee read FEmployee write SetEmployee; end; procedure TAuthentication.SetEmployee(Value: IEmployee); begin FEmployee:= Value; Notify; end; S itt a form: unit FormObject; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Observer, Authentication, Employee; type TFoObject = class(TForm, IObserver<IAuthentication>) private procedure ObservedObjectChanged(Value: IObservableObject<IAuthentication>); protected procedure AuthenticationChanged(Authenticated: IEmployee); virtual; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; implementation {$R *.dfm} procedure TFoObject.AuthenticationChanged(Authenticated: IEmployee); begin // Ide jön a kód, pl.: Innentől kezdve ebből a formból származtatva, hála a vizuális form öröklésnek, minden ablak képes kezelni a bejelentkezési változásokat. September 26 C# using és lock utánzat natív kódbanunit ObjectHelper; interface uses SysUtils; type TObjectHelper = class sealed public class procedure Lock(Obj: TObject; Proc: TProc); static; class procedure Using<T: class>(Obj: T; Proc: TProc<T>); static; end; implementation //------------------------------------------------------------------------------ // C# lock // class procedure TObjectHelper.Lock(Obj: TObject; Proc: TProc); begin TMonitor.Enter(Obj); try Proc; finally TMonitor.Exit(Obj); end; end; //------------------------------------------------------------------------------ // C# using // class procedure TObjectHelper.Using<T>(Obj: T; Proc: TProc<T>); begin try Proc(Obj); finally Obj.Free; end; end; end. Egy rövid kis pl. az épp most renoválás alatt álló projektemből ennek használatára: TObjectHelper.Using<TJPEGImage> Látszik, hogy ez a módszer rövid életű objektumok létrehozásához kiválóan alkalmas, s végre el lehet felejteni a manuális Destroy/Free hívogatásokat. Persze saját objektumok esetében lehet ezt rugalmasabban, interfész alapon kezelni, ahol automatikusan megsemmisülnek az objektumok a Delphi referenciaszámlási módszerének köszönhetően. Elég kifinomult a memóriakezelési rendszere a Delphinek, s ha hozzá vesszük, hogy van ReportMemoryLeaksOnShutdown is, képtelenség memory leaket csinálni. September 16 Fő a stabilitásMegoldódott a Vista SP1 telepítési problémám a Vista DVD-s upgraddel (hála Istennek). Igen ám, de már meg nem tudok WPF-s projekteket fordítani. Hiába teszem újra a VS SP1-et, vagy javítom a .NET 3.5-t, akkor is ezt vágja a képembe egy új üres projekt lefordításakor: Error 1: The "SplashScreen" parameter is not supported by the "MarkupCompilePass1" task. Verify the parameter exists on the task, and it is a settable public instance property. Error 2: The "MarkupCompilePass1" task could not be initialized with its input parameters. Sőt mi több, a VS is elszáll kilépéskor. De úgy látom, vannak mások is rajtam kívül: Az utolsó megoldást a teljes VS újratelepítése jelenti számomra. Remélem nem a Windows-t kell, mert arra mostanában biztos, hogy nem fogok sort keríteni. Már így is többször kellett újra raknom, mint az XP-t. Közben megszületett a megoldás: mindent le kellett szedni, amibe a visual szó benne volt :) és újratelepíteni. September 11 Delphi 2009 for Win32Miközben a .NET erőteljesen fejlődik és a Világ egyre jobban a virtuális gépek irányába mozdul el, még mindig tudnak meglepetéseket okozni a natív fejlesztésben is. Ezt mi sem bizonyítja jobban, hogy még a Microsoft is fejleszti natív eszközeit, hiszen vannak olyan területek, ahol ezek használata megkerülhetetlen. Most a CodeGear és Embarcadero páros kihozta az első komolyabb és jelentősebb fejlesztéseken átesett natív kódú fejlesztőeszközét Delphi 2009 néven. Talán a Delphi 7 óta ez az első Delphi, ami ténylegesen sok újítást hoz a natív fejlesztésbe. A 14 napos próba verzió mától letölthető a CodeGear oldaláról. Néhány érdekességet azért kiemelnék, melyek szerintem egész jók, kezdve a nyelvvel: generikusok és anonymous metódusok (jegyezném meg, most natív kód compilerről beszélünk). Illetve ajánlanám Barry Kelly blogját, aki a Delphi compiler egyik fejlesztője és részletesen elmagyarázza az újdonságokat, ill. lehet kérdezni is tőle, mint pl. ahogy ezt mások már meg is tették: TELEPÍTÉS: Át kell állítani a területi és nyelvi beállításokat angolra (megj.: a Windows-t nem kell újraindítani), majd a telepítést követően már visszaállíthatjuk magyarra. Új nyelvi fícsörök A generikusok megvalósítása megegyezik a .NET-es megvalósítással, csak épp itt jól is működik, ellenben a Delphi .NET-el, ahol még voltak gondok, mint azt korábban írtam. Ezen felül van nekünk két olyan unitunk, hogy Generics.Defaults és Generics.Collections. A Generics.Defaults például a következőket tartalmazza: IComparer<T> IEqualityComparer<T> TComparer<T> TEqualityComparer<T> TSingletonImplementation TDelegatedEqualityComparer<T> TDelegatedComparer<T> TCustomComparer<T> TStringComparer Illetve még jónéhány segédfüggvényt, mint pl.: function _LookupVtableInfo(intf: TDefaultGenericInterface; info: PTypeInfo; size: Integer): Pointer; function BinaryCompare(const Left, Right: Pointer; Size: Integer): Integer; function BobJenkinsHash(const Date; Len, InitData: Integer): Integer; A Generics.Collections unit szintúgy tartalmaz néhány generikus kollekciót, mint pl.: TArray
TEnumerator<T>
TEnumerable<T>
TList<T>
TQueue<T>
TStack<T>
TPair<TKey,TValue>
TDictionary<TKey,TValue>
TObjectList<T>
TObjectQueue<T>
TObjectStack<T>
TObjectDictionary<TKey,TValue>
function InCircularRange(Bottom, Item, TopInc: Integer): Boolean;
Ezen felül minden osztály tartalmaz egy ToString-, Equals- és egy GetHashCode virtuális tagfüggvényt, akárcsak .NET-ben ezt megszokhattuk. Minden osztály tartalmaz egy UnitName propertyt is, ami annak a unitnak a nevét adja vissza értelemszerűen, ahol a típus deklarálva lett. Meg hát ugye a régóta hiányzó kis finomságok most már elérhetők a generikus lista osztályok által: AddRange Sőt még TStringBuilder osztály is van, akárcsak .NET-ben. Következő érdekes kis téma: program NewFeatures; {$APPTYPE CONSOLE} uses SysUtils; type TProc<T> = reference to procedure(const Value: T); TClass = class class procedure Call<T>(Proc: TProc<T>; const Value: T); static; end; class procedure TClass.Call<T>(Proc: TProc<T>; const Value: T); begin Proc(Value); end; procedure MyProc(const Value: string); begin WriteLn(Value); end; begin // a régi módszerrel TClass.Call<string>( MyProc, 'Unicode string' ); // anonymous metódussal TClass.Call<Integer>( procedure(const Value: Integer) begin WriteLn(Value); end, 10 ); ReadLn; end. Ez a fentebbi kis példa illusztrálja miként is akar működni ez. Mellesleg jegyzem meg, az anonymous metódusok kiválóan alkalmasak szálkezelés esetén egyaránt. Pontosan ezért a thread osztály Synchronize metódusa támogatja ezt a fajta paraméterezést is: type TAThread = class(TThread) private FListBox: TListBox; protected procedure Execute; override; public constructor Create(ListBox: TListBox); property ListBox: TListBox read FListBox write FListBox; end; constructor TAThread.Create(ListBox: TListBox); begin inherited Create(True); FreeOnTerminate := True; Self.ListBox := ListBox; Resume; end; procedure TAThread.Execute; var I : Integer; Numbers: TList<Integer>; begin Numbers:= TList<Integer>.Create; try for I := 1 to 1000 do if not Terminated then Numbers.Add(I) else Exit; Synchronize ( procedure var N: Integer; begin ListBox.Items.BeginUpdate; try for N in Numbers do ListBox.Items.Add(IntToStr(N)); finally ListBox.Items.EndUpdate; end; end ); finally Numbers.Free; end; end; Ebbe az a szép, hogy nem kell külön mezőket és metódusok deklarálni az osztályhoz, mert a scope kiterjed a Numbers változóra is. Vagy egy másik pl.: procedure TfoMain.btAnonymMethodsClick(Sender: TObject); var List: TList<TProc>; Proc: TProc; begin List:= TList<TProc>.Create; with List do try Add( procedure begin ShowMessage('A'); end ); Add( procedure begin ShowMessage('B'); end ); Add( procedure begin ShowMessage('C'); end ); for Proc in List do Proc; finally Free; end; end; Ami nem csinál mást, mint egy generikus listába eltárolja az eljárás referenciákat és utána egyszerre meghívja mindet. Persze, azért ne legyen már minden tökéletes, vannak megkötések is, mint pl. globális eljárások és függvények nem lehetnek generikusak, illetve anonymous metódusok nem használhatók eseménykezelő tagfüggvényekként, mint pl.: Button1.OnClick:= procedure (Sender: TObject) begin end; E2009 Incompatible types: 'method pointer and regular procedure' Sajnos a reference esetében az of object nem működik. Máskülönben a fenti kód is működne. C# esetében ez persze teljesen más, mivel a C# tisztán objektumorientált nyelv. Sajnos ez nincs: type TProc<T> = reference to procedure(const Value: T) of object; Exit function GetValue(const Value: Integer): Integer; begin if Value < 0 then begin Result:= 0; Exit; end; //... end; Ehelyett most már elég csak ennyit írni: function GetValue(const Value: Integer): Integer; begin if Value < 0 then Exit(0); //... end; Az MSBuild rendszert is megreformálták egy kicsit. A compiler még mindig bitang gyors, de talán az egyik legfontosabb újítás a unicode támogatás. Mostantól az alapértelmezett string a Delphiben a unicode string és minden SaveToFile tagfüggvény támogatja a kódolási forma meghatározását: ListBox1.Items.SaveToFile('Readme.txt', TEncoding.UTF8);
A VCL is továbbfejlődött, került bele jónéhány új control, mint például Office Ribbon controlok, csoportosítható ListView, s meglepően jó Vista támogatás (pl. a gomboknak van olyan tulajdonsága, hogy ElevationRequired). Emellett van egy kismillió új property, fel se lehetne sorolni. Történt jónéhány változás az adatkezelés terén is, mint pl. COM független adatelérés - illetve most ez ellen szólva - jobb és sokkal gyorsabb támogatás a COM könyvtárak használatához és készítéséhez. Amit most mondok, szintúgy meglepő, de a VCL for the Web támogatja a Silverlight 2.0-t. Az IDE tartalmaz néhány új eszközt, mint pl. Class Explorer, Resource manager, a lokalizáláshoz eszközöket, stb. "Egy szóval: Win32 platformra jobb fejlesztőeszközt el sem lehetne képzelni." Megint elhamarkodtam ezt a kijelentést. Miért? A generikus kollekciók némelyike totál bugos Generikus típusok type IMyInterface = interface procedure DoSomething; end; TMyClass<T> = class(TInterfacedObject, IMyInterface) end; Arról nem beszélve, hogy a kódszerkesztő még mindig allergiás a generikusok támogatására. Nem is kell megkönnyíteni a szerkesztést, nem fontos! Minek az nektek! :) Ez nem IntelliSense, hanem IntelliGenny. type IObservableObject<T> = interface end; IEmployee = interface(IObservableObject<IEmployee>) end; Szóval vannak itt akadályok, de alapvetően egész jó, már ahhoz képest, ami eddig volt. September 05 Singleton (.NET)Első körben amiről szeretnék beszélni, mely egyaránt használatos Win32 és .NET fejlesztés során: a singletonok. Mindkét részről bemutatom ennek a használatát, de kezdjük először is azzal, hogy mi célt szolgál ez a pattern: Gondolom sokunkkal előfordult már, hogy egy osztály egyetlen példányára volt szükségünk. Ez az osztály definiál különböző műveleteket, melyet a kódon belül mindenhol el szeretnénk érni anélkül, hogy azt 76x példányosítanunk kellene. Nagyon jó példa erre a Delphi VCL Application objektuma, mely egy TApplication példány és pontosan egy létezik belőle a program futása során. Ugyanez igaz a .NET-re is. Speciel az Application objektumokkal egy a baj, hogy nem thread safek, azaz nem szálbiztosak. A következő singleton pattern megvalósítások viszont választ adnak mindkét kérdésre: Először is, ha már vannak generikusok a nyelvbe, akkor érdemes azt kihasználni és egy általános megoldást adni. Itt külön megemlíteném, hogy a Delphi 2009 for Win32 már támogatja a generikusokat és az anonym metódusokat is. Majd ezekről is beszélek később. C# public sealed class Singleton<T> where T: class, new() { private static volatile T _instance; private static object _syncRoot = new object(); private Singleton() { } public static T Instance { get { if (_instance == null) { lock (_syncRoot) { // lazy initialization if (_instance == null) _instance = new T(); } } return _instance; } } } Lehet ezt még cicomázni, de a lényeg ugyanaz. Egy-két kisebb technikai részlet: a volatile kulcsszó jelzi, hogy a mezőt módosíthatják konkurrens szálak, vagy bármi más, az mindig az aktuális értékét fogja tükrözni az objektumnak. A osztály megvan jelölve a sealed módosítóval, tehát nem lehet származtatni belőle. A konstruktor private, ezáltal még véletlenül sem példányosítható a Singleton<T> osztály. S végül: lazy initialization, azaz akkor példányosítjuk csak az osztályt, amikor először szükség van rá, nem szemeteljük tele indításkor a memóriát nem használt singleton objektumokkal. A használata pedig nagyon egyszerű: Singleton<List<string>>.Instance.Add("AAAAA"); Singleton<List<string>>.Instance.Add("BBBBB"); Singleton<List<string>>.Instance.Add("CCCCC"); foreach (var s in Singleton<List<string>>.Instance) Console.WriteLine(s); Console.ReadLine();Az első Singleton<List<string>>.Instance automatikusan példányosít egy List<string> generikus listát és a többi már szintén ebbe fog pakolgatni. Tehát ezzel a Singleton segédosztállyal típusonként készíthetünk pontosan egy példányt, amelyhez különböző szálakból is hozzáférhetünk. Delphi.NET 2.0 unit VVMF.Delphi.Common.Singleton; interface type TSingleton<T: class, constructor> = class sealed strict private class var FInstance: &Object; class var FLock: &Object; class function SafeGetInstance: T; static; class procedure SafeSetInstance(Value: T); static; class function GetInstance: T; static; class constructor Create; constructor Create; public class property Instance: T read GetInstance; end; implementation uses System.Threading; //------------------------------------------------------------------------------ // class contructor // class constructor TSingleton<T>.Create; begin FLock := &Object.Create; end; //------------------------------------------------------------------------------ // default constructor // constructor TSingleton<T>.Create; begin inherited; end; //------------------------------------------------------------------------------ // instance // class function TSingleton<T>.GetInstance: T; begin if SafeGetInstance = nil then begin Monitor.Enter(FLock); try if SafeGetInstance = nil then SafeSetInstance(T.Create); finally Monitor.Exit(FLock); end; end; Result := SafeGetInstance; end; //------------------------------------------------------------------------------ // C# volatile = VolatileRead // class function TSingleton<T>.SafeGetInstance: T; begin Result := T(Thread.VolatileRead(FInstance)); end; //------------------------------------------------------------------------------ // C# volatile = VolatileWrite // class procedure TSingleton<T>.SafeSetInstance(Value: T); begin Thread.VolatileWrite(FInstance, &Object(Value)); end; end. Ez megegyezik a C# implementációval. A különbség először is, hogy nincs volatile kulcsszó. Ez persze csak nyelvi fícsör a C#-ban, nem más mint a Thread.VolatileRead és Thread.VolatileWrite metódusának lerövidítése. Nincs lock se. Ez szintén orvosolható a Monitor.Enter / Monitor.Exit -el. A metódusok amellett, hogy osztálymetódusok (class procedure, class function) megvannak jelölve static kulcsszóval is, amely annyit tesz, hogy a Self (C# this) nem elérhető. Az osztály Delphi.NET-ben szintén megjelölhető sealed-ként. Az FInstance Object, mert a Volatile Read/Write ezt kívánja meg. Szóval azért látszik, hogy ez annyira nem szép a C# kódhoz képest, de használni használható, az eredmény kb. ugyanaz: TSingleton<List<string>>.Instance.Add('AAAAA'); TSingleton<List<string>>.Instance.Add('BBBBB'); TSingleton<List<string>>.Instance.Add('CCCCC'); for Value in TSingleton<List<string>>.Instance do WriteLn(Value); ReadLn; SQL Server Management Studio 2008 - NEWSEQUENTIALIDIgen, szóval vannak akadályozó tényezők. Ezt csak érdekességként írom azoknak, akik még szintén nem használták ezt, mert elsőre elég furának tűnt a dolog, hogy nem akart minden zökkenőmentesen történni. Ha newsequentialid()-et használunk és elkezdjük módosítani a táblát, hamar szembesülhetünk a következő validációs üzenettel: Ez nem tudom, hogy bug vagy direkt így működik, de amit később kaptam az arcomba az már érdekesebb volt, pontosan amikor módosítani akartam a táblaszerkezetet és aztán menteni: Szerencsére van megoldás, ki kell kapcsolni a prevent saving changes that require table re-creation-t: |
|
|