Ostatnio trochę zaniedbuję swój blog. Praca nad projektem i praca „przymusowa” pozwalająca na wygodną egzystencję wymaga pewnych poświęceń kosztem zaniedbywania bloga.
Dzisiejszym tematem będzie wzorzec bardzo przydatny, którego zastosowanie będzie wręcz wymagane w przypadku rozbudowanych aplikacji posiadających dużo formularzy i paneli, choć nie tylko do tego ogranicza się zastosowanie opisywanego wzorca.
Lazy Loading (ewentualnie Lazy initialization) w tłumaczeniu na polski oznacza opóźnione ładowanie. Te opóźnione ładowanie polega na utworzeniu zasobu do którego chcemy uzyskać dostęp dopiero w chwili pierwszego odwołania/użycia.
Na pewno przestudiowałeś wiele przykładowych projektów zawartych w SDK bada i w żadnym z nich nie natknąłeś się na zastosowanie tego wzorca.
Dla przypomnienia, inicjalizacja i tworzenie wszystkich formularzy następuje w funkcji OnAppInitializing() aplikacji i OnInitializing() w formularzu w przypadku tworzenia pozostałych zasobów, np. paneli.
W przypadku rozbudowanych aplikacji, tworzenie i inicjalizacja zasobów może zająć wiele czasu i pamięci, podczas gdy nie zawsze wszystkie zasoby są użytkowane przez użytkownika.
Dodatkowo, użytkownik jest zmuszony do oglądania SplashScreena przez dłuższy czas, co może powodować jego osobistą frustrację.
W takim przypadku, tworzenie formularzy i paneli, które są rzadko wykorzystywane niepotrzebnie zajmują pamięć i czas procesora.
Rozwiązaniem tego kosztownego problemu jest Lazy Loading.
Implementacja jest różna w zależności od wymagań i programisty. Chyba najlepiej wykorzystać po prostu wzorzec Singleton dla formularza.
Przykładowa implementacja:
/** * Name : LazyLoading * Version : * Vendor : BadaDev.pl * Description : */ #include "LazyLoading.h" #include "Form1.h" using namespace Osp::App; using namespace Osp::Base; using namespace Osp::System; using namespace Osp::Ui; using namespace Osp::Ui::Controls; LazyLoading::LazyLoading() { } LazyLoading::~LazyLoading() { } Application* LazyLoading::CreateInstance(void) { // Create the instance through the constructor. return new LazyLoading(); } bool LazyLoading::OnAppInitializing(AppRegistry& appRegistry) { // TODO: // Initialize UI resources and application specific data. // The application's permanent data and context can be obtained from the appRegistry. // // If this method is successful, return true; otherwise, return false. // If this method returns false, the application will be terminated. // Uncomment the following statement to listen to the screen on/off events. //PowerManager::SetScreenEventListener(*this); // Create a form and initialize Form1 *pForm1 = Form1::GetInstance(); // Add the form to the frame Frame *pFrame = GetAppFrame()->GetFrame(); // pFrame->AddControl(*pForm1); // Set the current form pFrame->SetCurrentForm(*pForm1); // Draw and Show the form pForm1->Draw(); pForm1->Show(); return true; } bool LazyLoading::OnAppTerminating(AppRegistry& appRegistry, bool forcedTermination) { // TODO: // Deallocate resources allocated by this application for termination. // The application's permanent data and context can be saved via appRegistry. return true; } void LazyLoading::OnForeground(void) { // TODO: // Start or resume drawing when the application is moved to the foreground. } void LazyLoading::OnBackground(void) { // TODO: // Stop drawing when the application is moved to the background. } void LazyLoading::OnLowMemory(void) { // TODO: // Free unused resources or close the application. } void LazyLoading::OnBatteryLevelChanged(BatteryLevel batteryLevel) { // TODO: // Handle any changes in battery level here. // Stop using multimedia features(camera, mp3 etc.) if the battery level is CRITICAL. } void LazyLoading::OnScreenOn (void) { // TODO: // Get the released resources or resume the operations that were paused or stopped in OnScreenOff(). } void LazyLoading::OnScreenOff (void) { // TODO: // Unless there is a strong reason to do otherwise, release resources (such as 3D, media, and sensors) to allow the device to enter the sleep mode to save the battery. // Invoking a lengthy asynchronous method within this listener method can be risky, because it is not guaranteed to invoke a callback before the device enters the sleep mode. // Similarly, do not perform lengthy operations in this listener method. Any operation must be a quick one. }
Stworzyliśmy i zainicjowaliśmy tylko jeden formularz Form1 podczas startu aplikacji. Jeśli chcemy odwołać się do innego formularza w innej części aplikacji i jednej z jego metod, po prostu odwołujemy się do niego przez publiczną funkcję statyczną GetInstance():
// Create a form and initialize if necessary Form2 *pForm2 = Form2::GetInstance(); pForm2->SetTitleText(L"Formularz 2");
Formularz powinien implementować wzorzec Singleton, jak poniższy Form1:
#ifndef _FORM1_H_ #define _FORM1_H_ #include <FBase.h> #include <FUi.h> #include <FApp.h> class Form1 : public Osp::Ui::Controls::Form { private: // Construction Form1(void); static Form1 *__pInstance; public: virtual ~Form1(void); bool Initialize(void); virtual result OnInitializing(void); virtual result OnTerminating(void); static Form1 *GetInstance(); }; #endif //_FORM1_H_
#include "Form1.h" using namespace Osp::Base; using namespace Osp::Ui; using namespace Osp::Ui::Controls; Form1* Form1::__pInstance = null; Form1::Form1(void) { } Form1::~Form1(void) { } bool Form1::Initialize() { // Construct an XML form Construct(L"IDF_FORM1"); return true; } result Form1::OnInitializing(void) { result r = E_SUCCESS; return r; } result Form1::OnTerminating(void) { result r = E_SUCCESS; return r; } Form1 *Form1::GetInstance() { if (__pInstance == null) { __pInstance = new Form1(); __pInstance->Initialize(); Osp::App::Application::GetInstance()->GetAppFrame()->GetFrame()->AddControl(*__pInstance); } return __pInstance; }
Wzorzec Singleton ma swoje wady i zalety. Wadą jest to, że możemy stworzyć tylko jeden obiekt danej klasy. Natomiast zaletą jest to, że możemy odwołać się do danej klasy, gdziekolwiek byśmy nie byli.
To nie jest wada, ponieważ Singleton właśnie po to się tworzy 🙂 Singleton możemy zaimplementować tak, że pozwala na stworzenie tyle obiektów, ile chcemy, sprawdzając ilość instancji aktualnie stworzonych. Pewną wadą może być natomiast to, że nie można dziedziczyć Singletona i dla każdej klasy trzeba go implementować oddzielnie. Plusem jak dla mnie jest to, że można go traktować jak zmienną globalną. Unikamy w ten sposób przekazywania referencji do poszczególnych obiektów, jak to jest w przypadku wzorca Context, o czym także przy okazji napiszę, jeśli będę zmuszony go zastosować.