En klasse A skal holde et array, jobs, av pekere til ulike
"jobbklasser".
En "jobbklasse" er en klasse som utfører en spesifikk arbeids-
oppgave og har sitt spesifikke grensesnitt (set-, get- og execute-
metoder).
Siden hver "jobbklasse" skal kunne henge på arrayet def. i A må
jobbklassene arve en felles baseklasse, JobbBase.
Arrayet jobs blir dermed slik:
JobbBase* jobs[10];
Nå nærmer vi oss spørsmålet:
Klassen A mottar ulike meldinger med kommandoer/verdier
for de ulike jobbklassene. Hvordan bør disse verdiene overføres
til jobbklassene? Dvs. hva er "penest" for å få aksess til
grensesnittet til hver jobbklasse:
Metode 1:
-----------------
Hver jobbklasse får definert en grensesnittklasse, som arves
av baseklassen JobbBase, f.eks.
class Ifc_Jobb1
{
virtual void SetLength(int length) { <error-msg>};
};
class Ifc_Jobb2
{
virtual void SetSize(int size) { <error-msg>};
};
class JobbBase : public Ifc_Jobb1,
Ifc_Jobb2
{
};
class Jobb1 : public JobbBase
{
void SetLength(int length) {.....}
}
class Jobb2 : public JobbBase
{
void SetSize(int size) {.....}
}
Vi kan forutsette at vi har kontroll på hva slags jobbtype-klasse
som henger på hvert arrayelement.
Vi kan dermed skrive:
jobs[3]->SetSize(5);
Dersom jobs[3] er av typen Jobb2 burde dette gå greit, men dersom
jobb[3] er av en annen type (noe som ikke skal skje), er det den
virtuelle funksjonen i Ifc_Jobb2 som trigges og gir feilmelding.
Ulempen med dette konseptet er at det blir MASSE multiple arv
i klassen JobbBase - nemlig hver grensesnittklasse.
I tillegg må hver grensesnittfunksjon i grensesnittklassene være
unike (ikke to like navn og signaturer), ellers vet man ikke hvilken
som skal trigges når den kalles.
Metode 2
--------------
En annen metode er å ikke la JobbBase arve grensesnittene slik
at vi kun får
class JobbBase
{
};
Når man skal aksessere et objekt på arrayet må vi typecaste til
rett type ala:
(Jobb2*)jobs[3]->SetSize(5);
Hvis jobs[3] nå IKKE er av typen Jobb2 er det deep shit.
Ulempen her er at det blir masse typecasting og mangel på
å fange opp feil typecasting.
Noen som har synspunkter???
________
HL
Er det virkelig ikke mulig å generalisere grensesnittet til jobbklassene
slik at de ser like ut utenfra ? For eksempel definere klassen 'JobParams'
som holder alle de relevante argumentene, og la 'Job' være en abstrakt klasse
inneholdende den abstrakte metoden 'SetParams(JobParams)'. Da kan du la de
ulike jobbklassene arve fra Job, og du slipper at A trenger å vite hvilken
som er hva.
-Tore
Dine "jobb-klasser" høres ut som kommando-objekter (Command-pattern), bortsett
fra at set- og get- ikke hører helt hjemme.
Slik jeg ser det er ikke ulempen multippel arv, men fordelingen av kunnskap i de
ulike delene av koden.
JobbBase har ideellt ikke noe med å kjenne til de ulike konkrete jobbklassene.
Enda verre: klasse A har idellt ikke noe med å kjenne til de ulike konkrete
jobbklassene.
Ved å gi JobbBase og A slik kunnskap må JobbBase og A oppdateres når det
innføres eller fjernes en konkret jobbklasse, og det blir sannsynlig at kode som
er spesifikk for en konkret jobbklasse befinner seg fordelt i den jobbklassen, A
og JobbBase, slik at man må forholde seg til alle tre klassene for å gjøre noe
med den koden.
Det gir god klarhet å tenke på fordeling av kunnskap og ansvar: hvem (kodesnutt)
vet hva og har ansvar for hva, hva trengs å vites for å utøve ansvaret?
> Metode 2
> --------------
> En annen metode er å ikke la JobbBase arve grensesnittene slik
> at vi kun får
>
> class JobbBase
> {
> };
Goodie.
> Når man skal aksessere et objekt på arrayet må vi typecaste til
> rett type ala:
>
> (Jobb2*)jobs[3]->SetSize(5);
Ugh.
> Hvis jobs[3] nå IKKE er av typen Jobb2 er det deep shit.
>
> Ulempen her er at det blir masse typecasting og mangel på
> å fange opp feil typecasting.
Vel, det er ulempen med å bruke typeomkasting, at man får typeomkasting. :-)
Men typeomkasting behøves ikke.
Det er vanskelig å foreslå noe konkret design uten kjenne til mer, slik som
hvordan du får inn jobb-spesifikasjoner i klassen A, men for eksempel:
<code>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <iostream>
#include <ostream>
void say( char const s[] ) { std::cout << s << std::endl; }
class Job
{
public:
typedef boost::shared_ptr<Job> Ptr;
virtual ~Job() {}
virtual void execute() = 0;
};
class Job1: public Job
{
private:
int myLength;
public:
Job1( int length ): myLength( length ) {}
virtual void execute() { say( "1" ); }
};
class Job2: public Job
{
private:
int mySize;
public:
Job2( int size ): mySize( size ) {}
virtual void execute() { say( "2" ); }
};
class A
{
private:
std::vector<Job::Ptr> myJobs;
public:
void add( Job::Ptr aJob ) { myJobs.push_back( aJob ); }
void executeAll() const
{
for( size_t i = 0; i < myJobs.size(); ++i )
{
myJobs.at( i )->execute();
}
}
};
int main()
{
A a;
a.add( Job::Ptr( new Job1( 123 ) ) ); // Length
a.add( Job::Ptr( new Job2( 456 ) ) ); // Size, whatever.
a.executeAll();
}
</code>
Cheers, & hdhd.,
- Alf
PS: Som sagt, vanskelig å si noe mer konkret uten å vite mer, for eksempel kan
det være at du trenger visitor-pattern eller noe slikt. Men bare spør. DS.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
>Slik jeg ser det er ikke ulempen multippel arv, men fordelingen av kunnskap i de
>ulike delene av koden.
>
>JobbBase har ideellt ikke noe med å kjenne til de ulike konkrete jobbklassene.
Det er riktig. Dette er prisen jeg betaler for at hver jobb som henger
på arrayet skal "godta" enhver kommando. Dvs. gi error dersom det ikke
henger et arvet objekt under som realiserer det virtuelle
grensesnittet.
>Enda verre: klasse A har idellt ikke noe med å kjenne til de ulike konkrete
>jobbklassene.
>Ved å gi JobbBase og A slik kunnskap må JobbBase og A oppdateres når det
>innføres eller fjernes en konkret jobbklasse, og det blir sannsynlig at kode som
>er spesifikk for en konkret jobbklasse befinner seg fordelt i den jobbklassen, A
>og JobbBase, slik at man må forholde seg til alle tre klassene for å gjøre noe
>med den koden.
Her tror jeg du misforstår. Hele ideen med dette konseptet er nettopp
at A IKKE trenger å ha kjenskap til hva som henger på hvert
arrayelement.
For å spe på med litt mer info (problemet med å stille slike spørsmål
er å ta med nok, men heller ikke mer:-): A vil typisk motta meldinger
der det i melding ligger lagret hvilket arrayelement meldingen gjelder
og med tilhørende meldingsinnhold. Det eneste A da skal gjøre er
å kalle rette funksjon i rett arrayelement med de medfølgende
meldingsdataene. Dette medfører ingen omprogrammering av A
dersom grensesnittene til job-klassene endres eller nye jobb-klasser
opprettes. Hvis, derimot konseptet med casting benyttes må jeg inn
i A og oppdatere.
>Det gir god klarhet å tenke på fordeling av kunnskap og ansvar: hvem (kodesnutt)
>vet hva og har ansvar for hva, hva trengs å vites for å utøve ansvaret?
>
>
>> Metode 2
>> --------------
>> En annen metode er å ikke la JobbBase arve grensesnittene slik
>> at vi kun får
>>
>> class JobbBase
>> {
>> };
>
>Goodie.
>
>
>> Når man skal aksessere et objekt på arrayet må vi typecaste til
>> rett type ala:
>>
>> (Jobb2*)jobs[3]->SetSize(5);
>
>Ugh.
Enig.
>> Hvis jobs[3] nå IKKE er av typen Jobb2 er det deep shit.
>>
>> Ulempen her er at det blir masse typecasting og mangel på
>> å fange opp feil typecasting.
>
>Vel, det er ulempen med å bruke typeomkasting, at man får typeomkasting. :-)
>
>Men typeomkasting behøves ikke.
Det må vel det dersom JobbBase ikke inneholder SetSize?
>Det er vanskelig å foreslå noe konkret design uten kjenne til mer, slik som
>hvordan du får inn jobb-spesifikasjoner i klassen A, men for eksempel:
Skjønner. Se over.
Som sagt ovenfor er min situasjon at variabelen a av typen A vil motta
ulike meldinger. Innholdet fra meldingene skal "pipes" videre til rett
jobb på arrayet.
________
HL
>Er det virkelig ikke mulig å generalisere grensesnittet til jobbklassene
>slik at de ser like ut utenfra ? For eksempel definere klassen 'JobParams'
>som holder alle de relevante argumentene, og la 'Job' være en abstrakt klasse
>inneholdende den abstrakte metoden 'SetParams(JobParams)'. Da kan du la de
>ulike jobbklassene arve fra Job, og du slipper at A trenger å vite hvilken
>som er hva.
Jo, det går, men da vil du altså benytte JobParams som en stor
struktur der kun en liten del blir benyttet for overføring av data til
en spesifikk jobb-klasse (hvis jeg skjønner deg rett). Jeg synes ikke
dette er mer elegant enn å bruke multiple arve eller la JobbBase
inneholde samtlige grensesnittfunksjoner og gjøre disse virtuelle.
Ideen med multiple arv av grensesnittklasser var å la JobbBase og A
vite minst mulig om grensesnittene til hver jobb-klasse.
________
HL
Det kommer hovedsaklig av mangel på spørsmål, så det trenger du ikke å
bekymre deg om.
> men jeg gjør et forsøk:
Jeg finner ikke noe konkret å svare på i forsøket ditt, så jeg kutter
detaljene.
Sånn som jeg ser det er du helt på bærtur, både med tanke på multiple
inheritance og andre idéer.
Ut fra beskrivelsen virker det som om du ønsker å sette og lese diverse
attributter i hver enkelt jobbklasse. Det er få eller ingen fellestrekk
mellom klassene, ut over at man skal kunne sette og lese attributter, og
utføre et verb.
Det nærmeste du da kommer en intelligent implementering er å generalisere
attributtene. Set(), Get() og Execute() blir da virtuelle metoder i
baseklassen din. F.eks.
class JobbBase
{
public:
virtual void Set(string name, string value);
virtual string Get(string name);
virtual void Execute();
};
Implementeringen av disse i denne og/eller avledede klasser burde gi seg
selv.
string er ikke nødvendigvis det beste valget - har du en felles baseklasse
som alle klasser arver fra kan det være naturlig å bruke den for verdiene og
kanskje også navn/id. En slik felles baseklasse har typisk navnet CObject,
Object e.l. men man kan jo bruke hva som helst bare den er tilstrekkelig
generisk (for formålet)
Ønsker man typesjekking så bruker man run-time type info eller implementerer
et "billigere" opplegg for å oppnå det samme (typisk kalt IsKindOf(), IsA()
e.l.)
Sigurd
Denne beskrivelsen kan passe på så mangt, for eksempel Windows-meldinger eller
en thread-pool, men uansett der et A-objekt fungerer som dispatcher. Et viktig
punkt er hvilken rolle jobbobjektene har, hvilket ansvar et jobbobjekt har. For
eksempel kan det hende at jobbobjektene er totalt overflødige! Eller det kan
hende at de er GUI-elementer. Et annet viktig punkt er hvorfor disse
handlingene er sentralisert gjennom en meldingsbasert arkitektur. For eksempel
kan det hende at koden som produserer meldingene, og i din skisserte løsning må
kjenne et jobbobjekts array indeks (id) og type for å kunne produsere en
passende melding, heller kunne identifisert jobbobjektet med en peker av riktig
type og bare anropt en passende rutine helt direkte, uten noen typeomkasting.
Idéene ovenfor er på design-nivået.
Det du spør om ser derimot ut til å være: innenfor det eksisterende
meldingsbaserte designet, der det /uansett/ er en mulighet for kjøretids
"type-mismatch" mellom melding og jobb-objekt, hvordan kan denne kjøretids
typesjekkingen, som ikke kan unngås, best realiseres rent teknisk?
Det generelle svaret, gitt begrensningen å arbeide innenfor eksisterende design,
er å sentralisere den og å bruke C++ sin støtte for den slags typesjekking,
nemlig dynamic_cast og typeid, generelt kalt RTTI (run-time type information).
Hvordan du best kan gjøre det rent konkret avhenger av hva du har og
begrensningen på hva du kan forutsette. For eksempel, hvis du har mulighet for
å la meldingene være litt aktive objekter så kan du gjøre noe à la
class IMessage
{
public:
virtual ~IMessage() {}
virtual void executeOn( Job& ) = 0;
};
class SizeMessage: public IMessage
{
public:
virtual void executeOn( Job& aJob )
{
executeOn( dynamic_cast<SizerJob&>( aJob ) ); // Throws if not OK.
}
void executeOn( SizerJob& aJob )
{
sizerJob.setSize( 42 );
}
};
Men det er ikke vanskelig å gjøre det (tilsynelatende) mer komplisert. ;-)
Fra et kunnskapsorientert synspunkt er det meldingen som har kunnskap om hva
slags jobbobjekt den passer for: et gitt meldingsobjekt krever et bestemt
jobbobjekt grensesnitt, mens slik jeg forstår det kan det finnes mange ulike
meldingstyper for et gitt jobbobjekt grensesnitt. Dermed er det ideellt
meldingsobjektet som bør utføre dynamic_cast eller eventuelt mer primitiv men
effektiv (men mer skjør) sjekking med typeid. Og det gjøres enklest og best i
en virtuell medlemsfunksjon i meldingsobjektet, for eksempel executeOn(job), der
denne funksjonen får inn en referanse til et Job-objekt. Etter å ha funnet at
jobbobjektet har passende grensesnitt (konkret klasse eller interface-klasse)
kan executeOn så gjøre hva som helst: en sekvens av handlinger spesifikke for
det grensesnittet, eller å gi seg selv som argument til en litt mer generell
execute() medlemsfunksjon i jobbobjektet, hva enn som passer (det er ikke nok
informasjon om hva du prøver å gjøre til å si noe mer detaljert).
Essensielt er dette en kombinasjon av Command-pattern og Visitor-pattern.
Det kan hende at dine innkommende meldinger er rene data-objekter. I så fall er
det et separat og lignende problem å omforme dem til litt mer aktive
meldingsobjekter som vet hva de selv er. En grei løsning på det er å bruke en
kolleksjon av fabrikk-funksjoner der data-meldingens identifisering av sin egen
type (id, whatever) velger en funksjon som gitt data-meldingen som argument
produserer et tilsvarende mer aktivt meldingsobjekt som du så kan anrope
executeOn på med jobb-objekt referanse som argument. Kunnskapsfordelingen er da
at A kjenner til generell abstrakt meldingstype M, generell abstrakt jobbtype J
og kolleksjon av meldingsobjekt fabrikkfunksjoner F; F kjenner til abstrakt
meldingstype M (og muligens abstrakt jobbtype J, pga. executeOn i objektene som
produseres); en gitt konkret meldingstype Mk kjenner til M og det
korresponderende påkrevde jobbobjekt grensesnittet Jk (eller konkret klasse) og
til kolleksjonen av fabrikkfunksjoner F; og hver konkret jobbobjekt-klasse Jk
kjenner ikke nødvendigvis til noe som helst annet enn J.
/---------------> M <== Mk -->\
/ / // |
A -----> F <=============// |
\ \? |
\----------------> J <==\\ |
\\ |
Jk <--/
Dette kan sikkert se komplisert ut, men hvis du tar en kikk på koden ovenfor
igjen, så er det ikke mer komplisert enn som så.
Cheers, & hdhd.,
- Alf
PS: Hva er "cheers" (som hilsning) på norsk?
>> men jeg gjør et forsøk:
>
>Jeg finner ikke noe konkret å svare på i forsøket ditt, så jeg kutter
>detaljene.
>Sånn som jeg ser det er du helt på bærtur, både med tanke på multiple
>inheritance og andre idéer.
Dette er like sakelig som å si "du er dum". Dette er muligens korrekt,
men det hadde hjulpet om du kunne konkretisere hva du mener.
>Ut fra beskrivelsen virker det som om du ønsker å sette og lese diverse
>attributter i hver enkelt jobbklasse. Det er få eller ingen fellestrekk
>mellom klassene, ut over at man skal kunne sette og lese attributter, og
>utføre et verb.
Ja, det er riktig. Det de har felles er at de alle skal utføre en jobb
(på et bilde).
>Det nærmeste du da kommer en intelligent implementering er å generalisere
>attributtene. Set(), Get() og Execute() blir da virtuelle metoder i
>baseklassen din. F.eks.
>
>class JobbBase
>{
> public:
> virtual void Set(string name, string value);
> virtual string Get(string name);
> virtual void Execute();
>};
Mener du å benytte kun én Set- Get- og Executefunksjon og dekode
f.eks. name i realiseringen av de virtuelle funksjonen for å se om
name gir mening i dette objektet? (Dvs. i motsetning til å ha en rekke
Set- og Get-funksjoner)
Hvis så, hvorfor er dette en mer intelligent løsning enn den jeg
foreslo i metode 1?
>Implementeringen av disse i denne og/eller avledede klasser burde gi seg
>selv.
>
>string er ikke nødvendigvis det beste valget - har du en felles baseklasse
>som alle klasser arver fra kan det være naturlig å bruke den for verdiene og
>kanskje også navn/id. En slik felles baseklasse har typisk navnet CObject,
>Object e.l. men man kan jo bruke hva som helst bare den er tilstrekkelig
>generisk (for formålet)
Skjønner ikke helt hva du mener. Du mener vel ikke at CObject skal
holde samtlige attributter som de arvede klassene trenger og kun legge
funksjonaliteten i de arvede klassene?
>Ønsker man typesjekking så bruker man run-time type info eller implementerer
>et "billigere" opplegg for å oppnå det samme (typisk kalt IsKindOf(), IsA()
>e.l.)
Ja, dette kunne jo implementeres sammen med metode 2 dersom casting
benyttes, men nå da må altså både min A-klasse og jobb-klassene
vedlikeholdes mhp. grensesnitt.
________
HL
>...
>Fra et kunnskapsorientert synspunkt er det meldingen som har kunnskap om hva
>slags jobbobjekt den passer for: et gitt meldingsobjekt krever et bestemt
>jobbobjekt grensesnitt, mens slik jeg forstår det kan det finnes mange ulike
>meldingstyper for et gitt jobbobjekt grensesnitt. Dermed er det ideellt
>meldingsobjektet som bør utføre dynamic_cast eller eventuelt mer primitiv men
>effektiv (men mer skjør) sjekking med typeid. Og det gjøres enklest og best i
>en virtuell medlemsfunksjon i meldingsobjektet, for eksempel executeOn(job), der
>denne funksjonen får inn en referanse til et Job-objekt. Etter å ha funnet at
>jobbobjektet har passende grensesnitt (konkret klasse eller interface-klasse)
>kan executeOn så gjøre hva som helst: en sekvens av handlinger spesifikke for
>det grensesnittet, eller å gi seg selv som argument til en litt mer generell
>execute() medlemsfunksjon i jobbobjektet, hva enn som passer (det er ikke nok
>informasjon om hva du prøver å gjøre til å si noe mer detaljert)
Konkret består applikasjonen av følgende:
Jeg har en videosekvens der jeg under runtime skal kunne manipulere
signalet. Tanken har da vært å opprette en prosesseringskjede (ulike
bildemanipulatorer) som brukes på videosignalet (dvs. hver enkelt
frame) i sekvens. Prosesseringskjeden er arrayet jobs, og dette
arrayet ligger i klassen A. For hver frame som mottas kjøres først
jobs[0], så jobs[1] osv. Under runtime skal enkelte attributter i en
Jobb kunne endres og nye Jobb-objekter skal kunne opprettes. Under
opprettingen tilordnes Jobb-objektet en unik ID som bla. angir hvilken
arrayindex den henger på. Denne ID'en returneres til senderen som ba
om å få opprettet jobb-objektet. Senere endringsmeldinger som mottas
inneholder bla. ID'en til Job-objektet som skal endres. Dermed kan jeg
dekode hvilket arrayelement det er snakk om. I teorien skal dette være
idiotsikkert, men for sikkerhets skyld vil jeg legge inn sikkerhet
slik at ting ikke kneler dersom ID og meldingsinnhold ikke stemmer
overens.
Enkelte jobb-objekter er svært enkle med kun én Set-funksjon som tar
ett argument, mens andre jobb-objekter kan være kompliserte og ta en
rekke Set-funksjoner med flere argumenter.
Som du skrev i ditt innlegg har meldingene kunnskap om hva slags
type jobb-objekt de er myntet på. Og, som du også skriver, ett
jobb-objekt skal kunne motta flere ulike meldinger. Dette er tilfelle
der et Jobb-objekt har flere Set-funksjoner.
I utgangspunktet kan vi anta at jeg har kontroll alt vedr. denne
applikasjonen (meldingsstruktur + øvrig applik.design).
Mitt innledende spørsmål gjaldt altså hvordan man på best mulig vis
kan overføre meldingsinnhold til et jobb-objekt som ligger på
arrayet jobs i klassen A.
>Essensielt er dette en kombinasjon av Command-pattern og Visitor-pattern.
>
>Det kan hende at dine innkommende meldinger er rene data-objekter.
Ja, i utgangspunktet.
>I så fall er
>det et separat og lignende problem å omforme dem til litt mer aktive
>meldingsobjekter som vet hva de selv er. En grei løsning på det er å bruke en
>kolleksjon av fabrikk-funksjoner der data-meldingens identifisering av sin egen
>type (id, whatever) velger en funksjon som gitt data-meldingen som argument
>produserer et tilsvarende mer aktivt meldingsobjekt som du så kan anrope
>executeOn på med jobb-objekt referanse som argument. Kunnskapsfordelingen er da
>at A kjenner til generell abstrakt meldingstype M, generell abstrakt jobbtype J
>og kolleksjon av meldingsobjekt fabrikkfunksjoner F; F kjenner til abstrakt
>meldingstype M (og muligens abstrakt jobbtype J, pga. executeOn i objektene som
>produseres); en gitt konkret meldingstype Mk kjenner til M og det
>korresponderende påkrevde jobbobjekt grensesnittet Jk (eller konkret klasse) og
>til kolleksjonen av fabrikkfunksjoner F; og hver konkret jobbobjekt-klasse Jk
>kjenner ikke nødvendigvis til noe som helst annet enn J.
>
> /---------------> M <== Mk -->\
> / / // |
> A -----> F <=============// |
> \ \? |
> \----------------> J <==\\ |
> \\ |
> Jk <--/
>
>Dette kan sikkert se komplisert ut, men hvis du tar en kikk på koden ovenfor
>igjen, så er det ikke mer komplisert enn som så.
>
________
HL
Det oppleves kanskje som usaklig når noen setter hale på grisen, men det er
tross alt mange som har vært på bærtur uten at den har noe med deres mentale
kapasitet å gjøre. På feil bærtur, til og med.
> Dette er muligens korrekt,
> men det hadde hjulpet om du kunne konkretisere hva du mener.
Det var jo det jeg gjorde...
>>Ut fra beskrivelsen virker det som om du ønsker å sette og lese diverse
>>attributter i hver enkelt jobbklasse. Det er få eller ingen fellestrekk
>>mellom klassene, ut over at man skal kunne sette og lese attributter, og
>>utføre et verb.
>
> Ja, det er riktig. Det de har felles er at de alle skal utføre en jobb
> (på et bilde).
>
>>Det nærmeste du da kommer en intelligent implementering er å generalisere
>>attributtene. Set(), Get() og Execute() blir da virtuelle metoder i
>>baseklassen din. F.eks.
>>
>>class JobbBase
>>{
>> public:
>> virtual void Set(string name, string value);
>> virtual string Get(string name);
>> virtual void Execute();
>>};
>
> Mener du å benytte kun én Set- Get- og Executefunksjon og dekode
> f.eks. name i realiseringen av de virtuelle funksjonen for å se om
> name gir mening i dette objektet? (Dvs. i motsetning til å ha en rekke
> Set- og Get-funksjoner)
Ja.
> Hvis så, hvorfor er dette en mer intelligent løsning enn den jeg
> foreslo i metode 1?
Er den koden du foreslo i metode 1 mulig å få kompilert ?
Og, hvis det mot formodning lar seg gjøre - du har generalisert at en bil
med forbrenningsmotor og en appelsin har det til felles at de har et visst
sylindervolum på motoren. Hvor har du lært å generalisere på den måten ?
>>Implementeringen av disse i denne og/eller avledede klasser burde gi seg
>>selv.
>>
>>string er ikke nødvendigvis det beste valget - har du en felles baseklasse
>>som alle klasser arver fra kan det være naturlig å bruke den for verdiene
>>og
>>kanskje også navn/id. En slik felles baseklasse har typisk navnet
>>CObject,
>>Object e.l. men man kan jo bruke hva som helst bare den er tilstrekkelig
>>generisk (for formålet)
>
> Skjønner ikke helt hva du mener.
Det er åpenbart. Du har mye å hente på å tenke litt enklere enn du
tilsynelatende gjør.
> Du mener vel ikke at CObject skal
> holde samtlige attributter som de arvede klassene trenger og kun legge
> funksjonaliteten i de arvede klassene?
Nei, selvfølgelig mener jeg ikke det. Det var heller ikke det jeg foreslo.
I utgangspunktet ønsker du sikkert at klassene skal holde rede på det de
trenger av informasjon. Denne informasjonen kan lagres av hver enkelt
klasse, og typesjekkingen kan utføres ved setting av hvert enkelt attributt.
Alternativt kan du lagre informasjonen i generisk form i en collection i
baseklassen, men typesjekkingen må foregå i hver enkelt klasse med mindre du
implementerer et opplegg som forteller baseklassen hva slags attributter
denne klassen trenger og akspeterer.
>>Ønsker man typesjekking så bruker man run-time type info eller
>>implementerer
>>et "billigere" opplegg for å oppnå det samme (typisk kalt IsKindOf(),
>>IsA()
>>e.l.)
>
> Ja, dette kunne jo implementeres sammen med metode 2 dersom casting
> benyttes, men nå da må altså både min A-klasse og jobb-klassene
> vedlikeholdes mhp. grensesnitt.
Med andre ord - styr unna...
Keep it simple.
Sigurd
Vel, ut fra den beskrivelsen er det 100% uklart hva i all verden som er
hensikten med å gjøre det meldingsbasert. Det eneste fornuftige jeg kan tenke
meg er at hensikten med det er å implementere en slags enkel synkronisering for
samtidige utføringstråder. Uten at noe slikt ligger bak må jeg si som Sigurd at
du er på bærtur og bør prøve å holde det enkelt: koden som i din idé kjenner til
typen jobb og aktuelt jobb-objekt og genererer en melding for å få endret
parametre i det objektet, bør heller direkte endre parametre i det objektet.
Da har du en grei ansvarsfordeling: klasse A har ansvaret for å kjøre
prosesseringen, uten å kjenne noe til parametre for jobbene, og hva det enn er
som du tenker deg genererer meldinger (GUI?) har ansvaret for å sette parametre.
Og hvis det er synkronisering som er tanken bak, så finnes det andre måter enn å
prøve å finne opp the krutt på nytt...
Cheers, & hdhd.,
- Alf
Ja. Du kan ha én enkelt instans av JobParams i programmet ditt som aksesseres
av alle jobbklassene. Når parametre endrer seg oppdaterer du bare
felter i denne instansen. Hvis det er viktig å ha ulike (mindre)
parameterstrukturer for hver jobbklasse, kan løsningen kanskje være å
definere en generell 'JobParams' klasse, gjerne som et helt abstrakt
grensesnitt, som jobbklassespesifikke parameterklasser arver fra.
| Jeg synes ikke dette er mer elegant enn å bruke multiple arve eller
| la JobbBase inneholde samtlige grensesnittfunksjoner og gjøre disse
| virtuelle.
Det synes jeg. Det sparer A fra å måtte vite hvilken grensesnittfunksjon
den skal kalle.
| Ideen med multiple arv av grensesnittklasser var å la JobbBase og A
| vite minst mulig om grensesnittene til hver jobb-klasse.
I så fall virker det som en dårlig idé. Det gjør ingen forskjell for A
om grensesnittfunksjonene i en jobbklasse er arvet (i ett eller to steg),
eller definert i jobbklassen selv.
-Tore
> "Alf P. Steinbach" <al...@start.no> wrote:
<klippet bort>
Eksempel med baseklassen Job og subklassene Job1 og Job2.
Job har en generell execute()-metode uten parametre, og subklasser har
spesifikke metoder for å sette nødvendige parametre.
</klippet bort>
> Som sagt ovenfor er min situasjon at variabelen a av typen A vil motta
> ulike meldinger. Innholdet fra meldingene skal "pipes" videre til rett
> jobb på arrayet.
Det høres ut som at du har en bestemt mening om hvordan designet skal
være, men det er vanskelig å se noen grunn til å ha det sånn. Du kan
bestemme deg for å frikoble A fra de spesifikke jobbene fullstendig, og i
det tilfellet høres det ut som en bra løsning å legge generelle jobber i
et array, slik som eksempelet. Du sier imidlertid at A skal "pipe"
meldinger til rett jobb, og da tyder det på at A må vite et eller annet om
jobbene. Derfor blir generaliseringene dine lite effektive, og det blir
vanskelig å forstå hva du mener. (At ingen her ser ut til å skjønne hva du
mener sier sannsynligvis noe om forbedringspotensialet til designet ditt
;P )
Tror at Alfs eksempel i langt større grad er fornuftig retning for et
design enn dine egne forslag har vært så langt. Det er ikke spesielt
vanskelig å utvide eksempelet slik at jobbene på arrayet kan endres
dynamisk. Det enkleste er kanskje å konstruere jobbene med de parameterne
som trengs, og å bytte ut Job-objektene for å endre parameterne.
--
Lars Christian Jensen
>Vel, ut fra den beskrivelsen er det 100% uklart hva i all verden som er
>hensikten med å gjøre det meldingsbasert. Det eneste fornuftige jeg kan tenke
>meg er at hensikten med det er å implementere en slags enkel synkronisering for
>samtidige utføringstråder.
>Uten at noe slikt ligger bak må jeg si som Sigurd at
>du er på bærtur og bør prøve å holde det enkelt: koden som i din idé kjenner til
>typen jobb og aktuelt jobb-objekt og genererer en melding for å få endret
>parametre i det objektet, bør heller direkte endre parametre i det objektet.
All styring skjer fra ekstern HW. Meldinger er følgelig VELDIG
fornuftig.
>Da har du en grei ansvarsfordeling: klasse A har ansvaret for å kjøre
>prosesseringen, uten å kjenne noe til parametre for jobbene, og hva det enn er
>som du tenker deg genererer meldinger (GUI?) har ansvaret for å sette parametre.
>
>Og hvis det er synkronisering som er tanken bak, så finnes det andre måter enn å
>prøve å finne opp the krutt på nytt...
>
Takk for bidrag. Som jeg tidlig skrev er jeg helt klar på at klassen A
ikke bør, og ikke trenger, han noen kjenskap til
grensesnittene til de ulike jobb-klassene. Dette er i og for seg
jordet. Det dette så koker ned til er hvorvidt jobb-klassene skal arve
en baseklasse som beskriver samtlige jobbklasseinterfacene, eller
om hver jobb-klasse selv skal sjekke om mottatt melding er gyldig
for seg.
Dvs. meldingene kan være på formen
// Def. av meldingstype
#define MSG_UNDEF 0;
#define MSG_SETXXX 1;
#define MSG_SETYYY 2;
#........
class MsgHeader
{
MsgHeader(MessageType mtype) {msg_type = mtype;}
MessageType GetMsgType() {return msg_type;}
int msg_id :
MessageType msg_type;
}
class MsgSetXXX : public MsgHeader
{
MsgSetXXX() : MsgHeader(MSG_SETXXX) {}
void SetXXX(int value) {xxx_value = value;}
int GetXXX() {return xxx_value;}
int xxx_value;
.....
}
Hver Jobb-klasse kan dermed ha et grensesnitt ala:
class JobBase
{
int job_id;
}
class Job1 : public JobBase
{
void SetXXX(MsgHeader* msg);
int xxx_value;
}
void Job1::SetXXX(MsgHeader* msg)
{
if (msg->GetMsgType() == MSG_SETXXX)
xxx_value = ((MsgSetXXX*)msg)->GetXXX();
else
<Errro. Feil meldingstype>
}
Spørsmålet er: Er denne Job1::SetXXX stygg (hvis så, hvorfor)?
Nå kunne jo Job1::SetXXX ta const MsgSetXXX& som argument,
og så sløyfe test av meldingstype og casting, men da måtte
jeg i klassen A, som holder arrayet av jobber, først sjekke at
jobben på angitt index var av typen Job1.
Annet alternativ er som før nevn å la JobBase inneholde eller arve
samtlige jobb-grensesnitt.
________
HL
Hvorfor er det så lurt å selv gå ned til det nivået? For eksempel, hvorfor er
RPC ikke mer fornuftig? Siden det ser ut til å være det du prøve å finne opp på
nytt?
>> Da har du en grei ansvarsfordeling: klasse A har ansvaret for å kjøre
>> prosesseringen, uten å kjenne noe til parametre for jobbene, og hva det enn er
>> som du tenker deg genererer meldinger (GUI?) har ansvaret for å sette parametre.
>>
>> Og hvis det er synkronisering som er tanken bak, så finnes det andre måter enn å
>> prøve å finne opp the krutt på nytt...
>>
>
>
> Takk for bidrag. Som jeg tidlig skrev er jeg helt klar på at klassen A
> ikke bør, og ikke trenger, han noen kjenskap til
> grensesnittene til de ulike jobb-klassene. Dette er i og for seg
> jordet. Det dette så koker ned til er hvorvidt jobb-klassene skal arve
> en baseklasse som beskriver samtlige jobbklasseinterfacene, eller
> om hver jobb-klasse selv skal sjekke om mottatt melding er gyldig
> for seg.
Hvorfor ser du for deg kun de alternativene, og ignorerer det du har fått av råd
om det?
> Dvs. meldingene kan være på formen
>
> // Def. av meldingstype
> #define MSG_UNDEF 0;
> #define MSG_SETXXX 1;
> #define MSG_SETYYY 2;
> #........
I C++, bruk 'const', ikke '#define'.
Dermed unngår du problemer med at for eksempel en eller annen Windows-header kan
finne på å definere makroer med samme navn.
Makroer er nødvendige her og der, men ikke for konstanter.
> class MsgHeader
> {
Ser ut som om du har glemt en liten 'public:' her.
> MsgHeader(MessageType mtype) {msg_type = mtype;}
Typer representeres best som typer, ikke som verdier.
Om du absolutt ønsker en type-id som ikke er standard C++ 'typeid', så i det
minste la den komme fra en virtuell funksjon, ikke noe som settes i basisklasse.
> MessageType GetMsgType() {return msg_type;}
>
> int msg_id :
> MessageType msg_type;
> }
Semikolon.
> class MsgSetXXX : public MsgHeader
> {
> MsgSetXXX() : MsgHeader(MSG_SETXXX) {}
>
> void SetXXX(int value) {xxx_value = value;}
Hvorfor er det aktuelt å endre verdien i en melding etter initialisering?
Stil: god idé å venne seg til å bruke initialiseringslister heller enn tilordninger.
> int GetXXX() {return xxx_value;}
Hvorfor skal GetXXX ha anledning til å modifisere meldingsobjektet?
Hvis ikke, deklarer den som 'const'.
>
> int xxx_value;
> .....
> }
Semikolon.
> Hver Jobb-klasse kan dermed ha et grensesnitt ala:
>
> class JobBase
> {
'public:'
> int job_id;
> }
Semikolon.
> class Job1 : public JobBase
> {
> void SetXXX(MsgHeader* msg);
>
> int xxx_value;
> }
Semikolon.
> void Job1::SetXXX(MsgHeader* msg)
> {
> if (msg->GetMsgType() == MSG_SETXXX)
> xxx_value = ((MsgSetXXX*)msg)->GetXXX();
> else
> <Errro. Feil meldingstype>
> }
>
> Spørsmålet er: Er denne Job1::SetXXX stygg (hvis så, hvorfor)?
Ja, den er særs stygg.
For det første, rent C++-teknisk, så fremt du ikke ønsker å støtte nullpekere
som argument bør argumentet være en referanse, ikke en peker.
For det andre, fremdeles rent C++-teknisk, sjekking av type gjøres best med
dynamic_cast eller eventuelt typeid, ikke med å finne opp kruttet på nytt.
For det tredje, som nevnt i en tidligere posting i denne tråden er det
designmessig mest sannsynlig beste å sentralisere den dynamiske typesjekkingen i
meldingsobjektene, ikke å ha den i jobbobjektene.
Jeg vet ikke helt om du forstod det, for selv om du skrev da (så vidt jeg
husker) at du var enig, så gjør du det motsatte her.
Grunn-idéen er å lage klasser som det er enkelt å bruke korrekt, og vanskelig å
bruke feil, og det betyr å bruke statisk typesjekking i den grad det er mulig
(det er da også hele poenget med C++ over C, nemlig bedre støtte for statisk
typesjekking).
> Nå kunne jo Job1::SetXXX ta const MsgSetXXX& som argument,
> og så sløyfe test av meldingstype og casting, men da måtte
> jeg i klassen A, som holder arrayet av jobber, først sjekke at
> jobben på angitt index var av typen Job1.
Se ovenfor eller tidligere.
> Annet alternativ er som før nevn å la JobBase inneholde eller arve
> samtlige jobb-grensesnitt.
Uh.
>> All styring skjer fra ekstern HW. Meldinger er følgelig VELDIG
>> fornuftig.
>
>Hvorfor er det så lurt å selv gå ned til det nivået? For eksempel, hvorfor er
>RPC ikke mer fornuftig? Siden det ser ut til å være det du prøve å finne opp på
>nytt?
Jeg har et system bestående av to adskilte HW-enheter A, og B, som
kommuniserer etter RPC-prinsippet. På klientsiden (B), der bla. mine
jobb-prosesser mm. vil ligge, vil det kun eksistere et interface som A
ser (et sett av tilgjengelige RPC) som må omkodes til et eget
meldingsformat for den øvrige koden på B.
>> Takk for bidrag. Som jeg tidlig skrev er jeg helt klar på at klassen A
>> ikke bør, og ikke trenger, han noen kjenskap til
>> grensesnittene til de ulike jobb-klassene. Dette er i og for seg
>> jordet. Det dette så koker ned til er hvorvidt jobb-klassene skal arve
>> en baseklasse som beskriver samtlige jobbklasseinterfacene, eller
>> om hver jobb-klasse selv skal sjekke om mottatt melding er gyldig
>> for seg.
>
>Hvorfor ser du for deg kun de alternativene, og ignorerer det du har fått av råd
>om det?
Tenker du på konseptet med å la meldingene foreta sjekken? Se under.
>
>> Dvs. meldingene kan være på formen
>>
>> // Def. av meldingstype
>> #define MSG_UNDEF 0;
>> #define MSG_SETXXX 1;
>> #define MSG_SETYYY 2;
>> #........
>
>I C++, bruk 'const', ikke '#define'.
>
>Dermed unngår du problemer med at for eksempel en eller annen Windows-header kan
>finne på å definere makroer med samme navn.
>
Dette er ingen Windows applic, men sikkert fornuftig med const
uansett.
>Makroer er nødvendige her og der, men ikke for konstanter.
>
>> class MsgHeader
>> {
>
>Ser ut som om du har glemt en liten 'public:' her.
Nei, kuttet ut kode for å få frem det essensielle.
>
>
>> MsgHeader(MessageType mtype) {msg_type = mtype;}
>
>Typer representeres best som typer, ikke som verdier.
>
>Om du absolutt ønsker en type-id som ikke er standard C++ 'typeid', så i det
>minste la den komme fra en virtuell funksjon, ikke noe som settes i basisklasse.
Hvorfor ikke la denne ligge i baseklassen? Tenker du på at denne
verdien bør plasseres der den strengt tatt blir konkretisert, dvs i de
arvede meldingsklassene?
>
>> class MsgSetXXX : public MsgHeader
>> {
>> MsgSetXXX() : MsgHeader(MSG_SETXXX) {}
>>
>> void SetXXX(int value) {xxx_value = value;}
>
>Hvorfor er det aktuelt å endre verdien i en melding etter initialisering?
>
>Stil: god idé å venne seg til å bruke initialiseringslister heller enn tilordninger.
Hva mener du med initialiseringslister her? Kan du gi et eks?
>> void Job1::SetXXX(MsgHeader* msg)
>> {
>> if (msg->GetMsgType() == MSG_SETXXX)
>> xxx_value = ((MsgSetXXX*)msg)->GetXXX();
>> else
>> <Errro. Feil meldingstype>
>> }
>>
>> Spørsmålet er: Er denne Job1::SetXXX stygg (hvis så, hvorfor)?
>
>Ja, den er særs stygg.
>
>For det første, rent C++-teknisk, så fremt du ikke ønsker å støtte nullpekere
>som argument bør argumentet være en referanse, ikke en peker.
>
>For det andre, fremdeles rent C++-teknisk, sjekking av type gjøres best med
>dynamic_cast eller eventuelt typeid, ikke med å finne opp kruttet på nytt.
>
Særs? Pga. argumenttype og/eller mangel på bruk av dynamic_cast eller
pga. punktet under? Dette er "details". Jeg har litt problemer med å
tolke deg (og særlig S.Stenersen) når dere uten unntak benytter total
fordømmelse av koding som ikke er optimal. Pedagogisk hadde det vært
ok å gradere noe. Bruk av "Bærtur" og "særs stygg" osv. når man er
(slik jeg ser det) relativt nærme mål, skaper kun forvirring. Det sies
at "det beste er det godes verste fiende". Men, for all del, jeg har
intet imot å se hva "det beste" er.
Uansett, punktet nedenfor er langt viktigere designmessig:
>For det tredje, som nevnt i en tidligere posting i denne tråden er det
>designmessig mest sannsynlig beste å sentralisere den dynamiske typesjekkingen i
>meldingsobjektene, ikke å ha den i jobbobjektene.
>
>Jeg vet ikke helt om du forstod det, for selv om du skrev da (så vidt jeg
>husker) at du var enig, så gjør du det motsatte her.
Jo, jeg tror jeg forsto hva du mente. En glipp at dette konseptet ikke
ble tatt med ovenfor (og som sikkert vil bli benyttet). Men jeg har
litt problemer med å se den store vesensforskjellen mellom å
la meldingen sjekke om jobb-objektet passer kontra å la jobb-objektet
sjekke om meldingen passer.
Uansett, takk for at du/dere tar dere tid til dette.
________
HL
Ja. Rent bortsett fra at verdien er 100% overflødig. Og derfor helst ikke bør
være der i det hele tatt.
>>> class MsgSetXXX : public MsgHeader
>>> {
>>> MsgSetXXX() : MsgHeader(MSG_SETXXX) {}
>>>
>>> void SetXXX(int value) {xxx_value = value;}
>> Hvorfor er det aktuelt å endre verdien i en melding etter initialisering?
>>
>> Stil: god idé å venne seg til å bruke initialiseringslister heller enn tilordninger.
>
> Hva mener du med initialiseringslister her? Kan du gi et eks?
>
>
>>> void Job1::SetXXX(MsgHeader* msg)
>>> {
>>> if (msg->GetMsgType() == MSG_SETXXX)
>>> xxx_value = ((MsgSetXXX*)msg)->GetXXX();
>>> else
>>> <Errro. Feil meldingstype>
>>> }
>>>
>>> Spørsmålet er: Er denne Job1::SetXXX stygg (hvis så, hvorfor)?
>> Ja, den er særs stygg.
>>
>> For det første, rent C++-teknisk, så fremt du ikke ønsker å støtte nullpekere
>> som argument bør argumentet være en referanse, ikke en peker.
>>
>> For det andre, fremdeles rent C++-teknisk, sjekking av type gjøres best med
>> dynamic_cast eller eventuelt typeid, ikke med å finne opp kruttet på nytt.
>>
>
> Særs?
Vel, ja. Å gjøre /alt/ som kan gjøres feil, feil, kvalifiserer til "særs". :-)
Jeg kan jo nevne i tillegg til de 3 punktene allerede nevnt, (4) manglende
krøllparenteser, (5) manglende const på meldingen, (6) bruk av global errno til
å rapportere feil, og (7) C-stil ubegrenset typeomkasting.
[snip]
> Uansett, punktet nedenfor er langt viktigere designmessig:
>
>
>> For det tredje, som nevnt i en tidligere posting i denne tråden er det
>> designmessig mest sannsynlig beste å sentralisere den dynamiske typesjekkingen i
>> meldingsobjektene, ikke å ha den i jobbobjektene.
>>
>> Jeg vet ikke helt om du forstod det, for selv om du skrev da (så vidt jeg
>> husker) at du var enig, så gjør du det motsatte her.
>
> Jo, jeg tror jeg forsto hva du mente. En glipp at dette konseptet ikke
> ble tatt med ovenfor (og som sikkert vil bli benyttet). Men jeg har
> litt problemer med å se den store vesensforskjellen mellom å
> la meldingen sjekke om jobb-objektet passer kontra å la jobb-objektet
> sjekke om meldingen passer.
>
> Uansett, takk for at du/dere tar dere tid til dette.
Som det heter på engelsk, "you're welcome". ;-)
>Vel, ja. Å gjøre /alt/ som kan gjøres feil, feil, kvalifiserer til "særs". :-)
> Jeg kan jo nevne i tillegg til de 3 punktene allerede nevnt, (4) manglende
>krøllparenteser, (5) manglende const på meldingen, (6) bruk av global errno til
>å rapportere feil, og (7) C-stil ubegrenset typeomkasting.
Dette er en vurdering jeg gjør når jeg poster et innlegg. Det blir en
rekke short cuts i koden, forhåpentligvis utenom det vesentlige vedr.
spørsmålet. I mitt tilfelle gjaldt dette programdesignet. I endelig
kode vil selvsagt parenteser, semikolon osv. måtte være på plass (det
gir seg jo selv). Men også const'er og dynamic_cast, static_cast,
public-, protected- og privateinndeling osv. Når alt tas med blir det
fort mye kode å lese. Tolkningen av deres svar blir derfor vanskelig
dersom karakteristikken "alt", "bærtur", "svært" dannes på bakgrunn av
uferdig eksempelkode med manglende semikolon osv. når mitt fokus
gjelder design.
________
HL