Prezentacja programu PowerPoint

advertisement
Dobre praktyki w projektowaniu
aplikacji mobilnych
Arkadiusz Waśniewski
[email protected]
Wprowadzenie
Spróbujemy odpowiedzieć na pytanie jak
powinna wyglądać dobrze napisana
aplikacja dla platformy .NET Compact
Framework
 Główne pole zainteresowań to wzorce
projektowe (design patterns)

Porządek spotkania (50 minut)
Przedstawienie założeń
 Opis szkieletu aplikacji
 Tworzenie obiektów
 Dostęp do danych
 Obsługa formularzy
 Przykładowe rozwiązanie

Terminologia (testy)



Stub – klasa zawierająca metody, które nic nie
robią. Główne zadanie takiej klasy to
umożliwienie kompilacji programu
Fake – klasa zawierająca metody, które zwracają
ściśle określone wartości, np. wpisane na
sztywno w kod klasy
Mock – klasa, dla której możemy określić jakie
metody czy właściwości mogą być wywoływane,
jakie wartości mają być przyjmowane i zwracane
Założenia
Napisać lub zanalizować aplikację mobilną
 Aplikacja składa się z wielu formularzy
 Jeden formularz może mieć różne
zastosowanie (np. formularz z DataGrid)
 Dane składowane są w zewnętrznym pliku
lub plikach na urządzeniu mobilnym
 Do operowania na danych mamy
dedykowany silnik bazodanowy

Założenia c.d.
Ilości danych, które wykorzystujemy są
rzędu setek lub tysięcy rekordów
 Aplikacja musi być wydajna i możliwie
łatwa w modyfikacji
 Istnieje konieczność posiadania wielu
wersji dla różnych klientów
 Aplikacja ma działać pod Windows Mobile
for Pocket PC i Windows CE

Konsekwencje założeń
Formularze wielokrotnie używane
powinny być umieszczone w pamięci
podręcznej
 Każdy z wybranych systemów
operacyjnych musi mieć własny zestaw
formularzy ze względu na duże różnice w
sposobie prezentacji

Konsekwencje założeń c.d.
Rezygnujemy z przechowywania danych
zewnętrznych w plikach XML (dobra
wydajność jedynie do rozmiaru kilku KB)
 Rezygnujemy z wykorzystania
wewnętrznie obiektu DataSet (wydajność)
 Dane, na których będzie operować
aplikacja będą odwzorowane w obiekty
(encje) i kolekcje obiektów

Szkielet aplikacji

Szukamy rozwiązania, które umożliwi
odseparowanie formularzy od reszty
aplikacji. Jako podstawę rozważań
przyjmujemy dwa podstawowe w tej
dziedzinie wzorce jakimi są Model-ViewController oraz Model-View-Presenter
Model-View-Controller
Model – odpowiedzialny za logikę i stany
biznesowe
 View – będący warstwą prezentacji
 Controller – odpowiedzialny za sterowanie
przepływem

Model-View-Presenter
Model – odpowiedzialny za logikę i stany
biznesowe
 View – będący warstwą prezentacji
 Presenter – będący mediatorem pomiędzy
widokiem a modelem

MVC a MVP
We wzorcu MVC widok informuje
kontroler o zdarzeniu. Kontroler wywołuje
metody modelu, który informuje widok o
zmianach
 We wzorcu MVP widok komunikuje się
tylko w prezenterem, który wykonuje
żądania korzystając z metod modelu

Jaki wzorzec wybieramy?

Model-View-Presenter wzbogacony o
klasy obsługujące konkretne przypadki
użycia
Tworzenie obiektów
Słowo kluczowe new
 Metoda fabryki, fabryka abstrakcyjna
 Registry
 Singleton
 Inversion of Control oraz Dependency
Injection
 Service Locator

Inversion of Control i
Dependency Injection
Tworzenie instancji zleca się obiektowi
(kontenerowi), który zna zależności
pomiędzy klasami. Zazwyczaj powiązania
te definiuje się w plikach konfiguracyjnych
w formacie XML
 Mobile Composite UI Application Block
wraz z Mobile ObjectBuilder firmy
Microsoft opisuje zależności korzystając z
atrybutów

Dependency Injection

Obiekty zależne oznaczane są dla tej
przykładowej implementacji atrybutami
public SelectCustomerPresenter(
[ServiceDependency] ShellService shell,
[ServiceDependency] ICustomerRepository customerRepository)
{
this.shell = shell;
this.customerRepository = customerRepository;
}
Utworzenie nowej instancji klasy
SelectCustomerPresenter presenter =
WorkItem.Items.AddNew<SelectCustomerPresenter>();
Service Locator
Oparty o wzorzec Singleton
 Dostarcza obiekt umiejący odnaleźć
dowolną usługę wykorzystywaną przez
aplikację
 Może być statyczny lub dynamiczny

Service Locator c.d.
class ObjectLocator
{
private BusinessEntityFactory entities;
private RepositoryFactory repositories;
private ObjectDictionary services;
private TypedDictionary<IView> views;
private IViewManager viewManager;
#region Wzorzec Singleton
private static readonly ObjectLocator instance = new ObjectLocator();
private ObjectLocator()
{
entities = new BusinessEntityFactory();
repositories = new RepositoryFactory();
services = new ObjectDictionary();
views = new TypedDictionary<IView>();
viewManager = new FormViewManager();
}
#endregion
Service Locator c.d.
public static BusinessEntityFactory Entities
{
get { return instance.entities; }
}
public static RepositoryFactory Repositories
{
get { return instance.repositories; }
}
public static ObjectDictionary Services
{
get { return instance.services; }
}
public static TypedDictionary<IView> Views
{
get { return instance.views; }
}
public static IViewManager ViewManager
{
get { return instance.viewManager; }
}
}
Dostęp do danych
Bridge – wzorzec mostu, którego
zadaniem jest usunięcie powiązań
pomiędzy abstrakcją (interfejsem obiektu)
a implementacją
 Umożliwia podpięcie różnych silników
baz danych
 Umożliwia testowanie bez konieczności
posiadania rzeczywistej bazy danych

Dostęp do danych c.d.
interface IRepository
{
}
interface IRepository<T> : IRepository
{
EntityList<T> GetList();
}
class CustomerRepository : IRepository<Customer>
{
public EntityList<Customer> GetList()
{
EntityList<Customer> list = new EntityList<Customer>();
string sql = "SELECT Id, Code, Barcode, Name1, Name2, " +
"LocationId, TaxNumber, StatisticNumber, CustomerBranchId, " +
"CustomerCategoryId, CustomerGroupId, Phone1, Phone2, Fax, " +
"Email, Web, Description, IsActive FROM Customer";
...
return list;
}
}
Dostęp do danych – Bridge

Możemy również zdefiniować domyślny
konstruktor korzystający z Service Locator
interface IDataService<T>
{
EntityList<T> GetList();
}
class CustomerRepository : IRepository<Customer>
{
private readonly IDataService<Customer> provider;
public CustomerRepository(IDataService<Customer> provider)
{
this.provider = provider;
}
public EntityList<Customer> GetList()
{
return provider.GetList();
}
}
Dostęp do danych – Bridge
class Repository<T> : IRepository<T>
{
private readonly IDataService<T> provider;
public Repository(IDataService<T> provider)
{
this.provider = provider;
}
public EntityList<T> GetList()
{
return provider.GetList();
}
}
class CustomerRepository : Repository<Customer>
{
public CustomerRepository(IDataService<Customer> provider)
: base(provider)
{
}
}
Dostęp do danych – wywołanie

Warianty wywołania repozytorium przy
wykorzystaniu wzorca Service Locator
IRepository<Customer> repository =
ObjectLocator.Repositories.GetCustomerRepository();
IRepository<Customer> repository =
ObjectLocator.Repositories.Get<IRepository<Customer>>();
Formularze
Proces tworzenia formularza powoduje
odczuwalne dla użytkownika opóźnienia
zwłaszcza jeśli konieczne jest załadowanie
lub przygotowanie danych
 Formularze wielokrotnie wykorzystywane
muszą mieć odpowiednio utworzony lub
odtworzony stan

Formularze c.d.
Wyświetlenie formularza może odbywać
się na dwa sposoby: metodą Show() lub
ShowDialog()
 Aktywowanie formularza niemodalnego
wywołującego formularz modalny!
 Jak wyświetlić formularz wielokrotnego
zastosowania przy pomocy ShowDialog()
tak aby ekran nie migotał

Przykład
Przykład – założenia
Definiujemy interfejs wspólny dla
wszystkich widoków
 Każdy interfejs widoku wie jaki prezenter
go obsługuje
 Widoku są rejestrowane w systemie w
powiązaniu z interfejsami, które
implementują
 Każdy prezenter wie, z jakiego interfejsu
widoku będzie korzystać (wyświetlanie!)

Przykład – założenia c.d.
Każdy prezenter posiada skojarzony ze
sobą interfejs umożliwiający dowolnemu
kontrolerowi zarządzanie prezenterem (w
ramach dowolnego przypadku użycia)
 Interfejsy implementowane przez kontroler
nie powinny być widoczne dla obiektów
wywołujących kontroler
 Wyświetlaniem widokami zarządca
odpowiedni obiekt

Interfejs bazowy widoku
Lifetime – czas życia widoku
 Presenter – obiekt kontrolujący widok

interface IView
{
string Title
{
set;
}
Lifetime Lifetime
{
get;
set;
}
Presenter Presenter
{
get;
set;
}
}
Przykładowy interfejs widoku
interface ILoginView : IBaseView<LoginPresenter>
{
string Username
{
get;
set;
}
string Password
{
get;
set;
}
void FocusOnUsername();
void FocusOnPassword();
void ShowErrorMessage(string message);
}
Rejestracja widoków
public override void AddViews()
{
#if ((PocketPC || WindowsCE || Smartphone))
ObjectLocator.Views.AddNew<IDataGridView, DataGridForm>();
ObjectLocator.Views.AddNew<ICustomerDetailView, CustomerDetailForm>();
ObjectLocator.Views.Add<IEmailAccountDetailView, EmailAccountDetailForm>(
Lifetime.SingleCall);
ObjectLocator.Views.Add<IEmailDetailView, EmailDetailForm>(
Lifetime.SingleCall);
ObjectLocator.Views.Add<IHtmlView, HtmlForm>(
Lifetime.SingleCall);
ObjectLocator.Views.Add<ILoginView, LoginForm>(
Lifetime.SingleCall);
ObjectLocator.Views.AddNew<IMenuView, MenuForm>();
ObjectLocator.Views.AddNew<IProductDetailView, ProductDetailForm>();
#endif
}
Przykładowy prezenter
sealed class LoginPresenter : BasePresenter<ILoginView,
ILoginPresenterController>
{
public LoginPresenter(ILoginPresenterController controller)
: base(controller) {}
protected override void OnInitialize()
{
base.OnInitialize();
View.Title = "Logowanie do programu";
Command loginCommand = new Command("Zaloguj", this.LogIn);
Command closeCommand = new Command("Zamknij", Controller.OnCancel);
View.AddActionCommand(loginCommand);
View.AddActionCommand(closeCommand);
View.Username = string.Empty;
View.Password = string.Empty;
View.FocusOnUsername();
}
private void LogIn() { ... }
}
Przykładowy interfejs
prezentera dla kontrolera
interface ILoginPresenterController : IPresenterController
{
void OnCancel();
void OnLogIn();
}
Przykładowa implementacja
interfejsu prezentera

Poniższy przykład wykorzystuje implementację
jawną (explicitly) w odróżnieniu od niejawnej
(implicitly). Dzięki temu obiekt wywołujący
kontroler widzi jedynie metody publiczne lub
wewnętrzne tegoż kontrolera
#region ILoginPresenterController Members
void ILoginPresenterController.OnCancel()
{
ObjectLocator.ViewManager.Exit();
}
void ILoginPresenterController.OnLogIn()
{
ObjectLocator.ViewManager.Show(this.Items.Get<MainMenuPresenter>());
}
#endregion
Formularze – interfejs klasy
zarządzającej
interface IViewManager
{
string Title
{
set;
}
TPresenter Display<TPresenter>(TPresenter presenter,
params object[] parameters)
where TPresenter : Presenter;
TPresenter Display<TPresenter>(TPresenter presenter,
Action<TPresenter> action, params object[] parameters)
where TPresenter : Presenter;
void Exit();
TPresenter Show<TPresenter>(TPresenter presenter,
params object[] parameters)
where TPresenter : Presenter;
TPresenter Show<TPresenter>(TPresenter presenter,
Action<TPresenter> action, params object[] parameters)
where TPresenter : Presenter;
}
Przykładowy test
[Test]
public void LogInWithCorrentUsernameAndPassword()
{
DynamicMock controllerMock = new DynamicMock(
typeof(ILoginPresenterController));
DynamicMock viewMock = new DynamicMock(
typeof(ILoginView));
LoginPresenter presenter = new LoginPresenter(
(ILoginPresenterController)controllerMock.MockInstance,
(ILoginView)viewMock.MockInstance);
viewMock.ExpectAndReturn("get_Username", "admin");
viewMock.ExpectAndReturn("get_Password", "1415");
controllerMock.Expect("OnLogIn");
base.InvokeMethod(presenter, "LogIn");
viewMock.Verify();
controllerMock.Verify();
}
Więcej informacji







http://www.codeplex.com/smartclient
http://www.dofactory.com
http://codebetter.com/blogs/jeremy.miller
http://www.springframework.net/
http://martinfowler.com/articles/injection.html
http://martinfowler.com/eaaDev/uiArchs.html
http://www.amazon.com/Applying-DomainDriven-Design-PatternsExamples/dp/0321268202
Dziękuję za uwagę

Można zadawać pytania...
Download