Zpět | Obsah | Další |
Ve dnešním dílu našeho seriálu o programování v Cocoa se samotným prostředím Cocoa ani tolik zabývat nebudeme: namísto toho se podíváme na některá základní pravidla návrhu a struktury (nejen) objektových aplikací. Samozřejmě, kdykoli budeme naše povídání doprovázet konkrétními příklady, použijeme API Cocoa; to, o čem si dnes budeme povídat však je mnohem obecnější a platí v jakémkoli prostředí.
Model, View, Controller
Základem kvalitního objektového designu je dělení na tři přinejmenším logicky nezávislé moduly: ty se tradičně nazývají "model", "view" (uživatelské rozhraní) a "controller" (správce); pro aplikační strukturu, založenou na těchto třech modulech, se často používá zkratka složená z jejich prvních písmen: MVC.
Každý z modulů má jasně stanovenou úlohu:
Při tomto návrhu získáme nejvyšší flexibilitu a vícenásobnou použitelnost kódu: model není nijak závislý na konkrétní aplikační logice, můžeme jej beze změny a snadno využít v jakékoli jiné aplikaci, jež pracuje se shodnými daty. Podobně controller nijak nezávisí na konkrétním vzhledu grafického uživatelského rozhraní — to můžeme podle potřeby (a podle požadavků zákazníků) snadno měnit, aniž bychom museli zasahovat do kódu controlleru (natož pak modelu).
Jednoduchý příklad
Ukažme si pro lepší ilustraci jednoduchý příklad: aplikaci, která zobrazí v grafickém uživatelském rozhraní obsah archivu typu TAR nebo TAR.GZ.
Model
Nejprve navrhneme datový model — objekt (či lépe třídu objektů), representující data, s nimiž pracujeme: v našem případě tedy archivy. Se zkušenostmi, jež máme z minulých dílů s obecnými službami knihovny Foundation, je to jednoduché — API našeho modelu by mohlo vypadat třeba takto:
@interface Archiver
+(Archive)archiveWithContentsOfFile:(NSString*)file;
-(NSArray*)items; // pole NSDictionaries, obsahujících
extern NSString *AName; // jméno souboru
extern NSString *ASize; // velikost
@end
Implementací se již podrobně zabývat nebudeme — programování se službami Foundation jsme si vysvětlili v předcházejících dílech. Zájemci se mohou na zdrojový kód implementace modulu Archive podívat na konci textu.
View
Máme-li model, můžeme se pustit do návrhu grafického uživatelského rozhraní. Stejně dobře jsme ovšem mohli postupovat opačně — nejprve sestavit rozhraní aplikace, a potom připravovat model: to je právě jednou z hlavních výhod MVC. View a model jsou na sobě naprosto nezávislé, a můžeme je sestavovat zcela samostatně — u větších projektů může jeden programátor nebo tým pracovat na modelu, a jiný na rozhraní.
V prostředí Cocoa ovšem pro návrh view využijeme InterfaceBuilder (připomeňme posledních několik dílů našeho seriálu, kde jsme si vysvětlovali jak tato geniální aplikace funguje). Rozhraní bude jednoduché: jediné okno, obsahující tabulku se dvěma sloupci (jméno souboru a jeho velikost), a tlačítko pro otevření nového souboru. Okno již máme připravené ze standardního projektového template; jen do něj uložíme tabulku a tlačítko, a nastavíme jejich atributy. Prozatím není třeba "drátovat" žádné akce ani outlety.
Controller
V takhle jednoduchém případě je nejsnazší připravit jak třídu pro controller tak i její instanci přímo v InterfaceBuilderu. Přejdeme do panelu "Classes", vybereme třídu NSObject, vyžádáme si vytvoření nové podtřídy, a v okně inspektoru nastavíme její atributy — speciálně přidáme outlet "table" třídy NSTableView a akci "openNewArchive". To vidíme na druhém obrázku:
Nyní si vyžádáme vytvoření instance třídy Controller ("Instantiate" z pop-up nabídky), a zajistíme propojení mezi controllerem a uživatelským rozhraním: outlet "table" nové instance "nadrátujeme" na tabulku, a tlačítko "nadrátujeme" na akci "openNewArchive" controlleru.
Poslední nutné nastavení je vazba tabulky na controller. Ta vyžaduje dva kroky:
Tím jsme v InterfaceBuilderu hotovi; ještě si jen vyžádáme vygenerování základních zdrojových souborů pro třídu Controller, a půjdeme do ProjectBuilderu doplnit její zdrojový kód.
Zdrojový kód Controlleru už je velmi jednoduchý: nejprve mezi jeho instanční proměnné přidáme model:
#import <Cocoa/Cocoa.h>
#import "Archiver.h"
@interface Controller : NSObject {
IBOutlet NSTableView *table;
Archiver *model;
}
- (IBAction)openNewArchive:(id)sender;
@end
a pak implementujeme dvě jednoduché metody, které využívá tabulka pro získání svého obsahu — první určí počet řádků, druhá vrátí hodnotu pro daný řádek (identifikovaný pořadovým číslem) a sloupec (identifikovaný právě tím identifikátorem, který jsme zadali v inspektoru v InterfaceBuilderu):
@implementation Controller
-(int)numberOfRowsInTableView:(NSTableView*)tv {
return [[model items] count];
}
-(id)tableView:(NSTableView*)tv objectValueForTableColumn:(NSTableColumn*)col row:(int)row {
return [[[model items] objectAtIndex:row] objectForKey:[col identifier]];
}
...
Vidíme, že jsme si trochu usnadnili práci tím, že jsme použili jako identifikátory přímo konstanty, representující údaje ve slovnících modelu. Kdyby tomu tak nebylo, museli bychom buď použít řadu "ifů", nebo — lépe — vlastní překladový slovník z identifikátorů tabulky na klíče modelu (s ním by kód vypadal nějak takto: "...objectForKey:[translation objectForKey:[col identifier]]];").
Poslední co je třeba udělat je implementace akce "openNewArchive": zde jen použijeme standardní panel pro výběr souborů, a pokud jej uživatel uzavře tlačítkem "OK" (tj. metoda runModalForTypes: vrátí pravdivou hodnotu), vytvoříme nový model, odpovídající zvolenému souboru (jeho jméno nám vrátí panel na základě zprávy "filename"). Nakonec tabulce pošleme zprávu "reloadData", aby nové údaje zobrazila:
...
-(IBAction)openNewArchive:(id)sender {
NSOpenPanel *panel=[NSOpenPanel openPanel];
if ([panel runModalForTypes:[NSArray arrayWithObject:@"gz"]]) {
[model release];
model=[[Archiver archiverWithContentsOfFile:[panel filename]] retain];
[table reloadData];
}
}
@end
To je opravdu vše: stačí aplikaci přeložit a spustit, a hned funguje — to ilustruje poslední obrázek:
Zdrojový kód modelu
#import "Archiver.h"
@interface TarArchiver:Archiver {
NSMutableArray *items;
}
@end
@implementation TarArchiver
-(void)parseOutput:(NSString*)s {
NSEnumerator *en=[[s componentsSeparatedByString:@"\n"] objectEnumerator];
items=[[NSMutableArray alloc] init];
while (s=[en nextObject]) if ([s length]) {
NSArray *a=[s componentsSeparatedByString:@" "];
[items addObject:[NSDictionary dictionaryWithObjectsAndKeys:
[a lastObject],AName,
[a objectAtIndex:[a count]-4],ASize,
nil]];
}
}
-(void)readArchive:(NSString*)file {
NSTask *task=[[[NSTask alloc] init] autorelease];
NSPipe *pipe=[NSPipe pipe];
NSFileHandle *fh=[pipe fileHandleForReading];
NSMutableData *buf=[NSMutableData data];
NSData *d;
[items release]; items=nil;
[task setStandardOutput:pipe];
[task setLaunchPath:@"/usr/bin/gnutar"];
[task setArguments:[NSArray arrayWithObjects:@"tzvf",file,nil]];
[task launch];
while ((d=[fh availableData]) && [d length]) [buf appendData:d];
[task waitUntilExit];
if ([task terminationStatus]==0)
[self parseOutput:[[[NSString alloc] initWithData:buf
encoding:NSUTF8StringEncoding] autorelease]];
}
-initWithContentsOfFile:(NSString*)file {
[self=[super init] readArchive:file];
return self;
}
-(void)dealloc {
[items release];
[super dealloc];
}
+(Archiver*)archiverWithContentsOfFile:(NSString*)file {
return [[[self alloc] initWithContentsOfFile:file] autorelease];
}
-(NSArray*)items {
return items;
}
@end
Zpět | Obsah | Další |
Copyright © Chip, O. Čada 2000-2003