Klassehiërarchieontwerp, waarbij downcasts van basisklasse naar afgeleide klasse worden vermeden

Ik ben bezig met het ontwerpen van een DNS-parsing-bibliotheek in C ++. Een DNS-pakket heeft een set standaardvelden gevolgd door een lijst met bronrecords die weer een set standaardvelden heeft gevolgd door een RData-veld. Het RData-veld wordt geparseerd op basis van het typeveld. Nu geef ik een hiërarchie op voor DNSRData, om verschillende typen te verwerken. De code ziet er ongeveer zo uit:

class DNSRData {
  virtual void ToString() = 0;
  virtual void Parse() = 0;
}

class DNSRData_A : public DNSRData {
  void ToString();
  void Parse();
  uint32_t GetIP();
}

class DNSRData_CNAME : public DNSRData {
  void ToString();
  void Parse();
   const char* GetAlias();
}

class DNSResourceRecord {
  /* Standard Fields 
   ..... */
  int      type_;//Specifies the format for rdata_
  DNSRData *rdata_; 
}

class DNSPacket {
  /* Standard Fields
  ....*/
  vector rr_list_;
}

Nu dit het probleem is dat ik heb, kan elk DNSRData-record verschillende velden hebben. Ik wil geen accessors toevoegen voor alle velden in de Base-klasse, omdat ze aanwezig zijn in een bepaalde afgeleide klasse en niet in andere, bijvoorbeeld IP-adres is alleen aanwezig in DNSRData_A en niet in een ander.

Dus als ik bewerkingen op de DNSRData wil uitvoeren, zoek ik het type op en voer ik een downcast uit van DNSRData * naar DNSRData_A *.

DNSRData *rdata = packet->GetResourceRecord().front(); //not really necessary for this example
if(resource_record.type == RR_CNAME) {
  DNSRData_CNAME *cname = (DNSRData_CNAME*)rdata;
} 

Dit kan later tonnen problemen veroorzaken en naarmate we meer soorten toevoegen, wordt het al snel een onheilspellende rommel. Om het even welke ideeën op hoe te om dit probleem op te lossen zonder alle accessors aan de klasse van de Basis toe te voegen?

EDIT:

Wat meer context, dit maakt deel uit van een high-performance DNS trace pars-bibliotheek. Veel van de bewerkingen worden gedaan omdat we pakketten op de draad zien. Dus, wat zou een operatie zijn die het ontwerp verprutst, laten we zeggen dat we een DNSPacket krijgen en we ontleden het nu we willen beslissen hoe we het verder moeten verwerken op basis van het type.

if(type == RR_CNAME) {
  DNSRData_CNAME *cname = dynamic_cast(&rdata);
  char *alias = cname->GetAlias();
}else if (type = RR_A) {
  DNSRData_A *a = dynamic_cast(&rdata);
  uint32_t ip = a->GetIP();
}

Zoals u ziet, is er een downcast bij betrokken, van het basistype RData tot een meer specifiek RData-type. Ik wil deze downcast vermijden en misschien een ontwerppatroon gebruiken om dit probleem op te lossen.

1

2 antwoord

als ik je goed begrijp, denk ik dat het bezoekerspatroon is waarnaar je op zoek bent

Visitor pattern.

class GetInfoVisitor
  {
    void visit(DNSRData_A* a)
    {
        uint32_t ip = a->GetIP();
    }
    void visit(DNSRData_CNAME * cname) 
    {        
        char *alias = cname->GetAlias();
    }   
  }

class DNSRData
{

    void action(Visitor& visitor) 
    {
        visitor.visit(this);
    }
}

int main()
{
 ...
 GetInfoVisitor getInfoVisitor;
 DNSRData *rdata = packet->GetResourceRecord().front();
 rdata->action(getInfoVisitor);
}
4
toegevoegd
jojo genageld, ik ben er vrij zeker van dat die bezoeker precies is waarnaar je op zoek bent. Vermijd die virtueel ongeldige ToString() en Parse() lidfuncties volledig, vervang ze door ToStringVisitor en ParseVisitor klassen .
toegevoegd de auteur mergeconflict, de bron
@shrin ik heb wat code toegevoegd, ik hoop dat het helpt
toegevoegd de auteur jojo, de bron
@shrin moet u de GetInfo-methode toevoegen aan GetInfoVisitor voor elk van de verschillende typen die u kiest. op dezelfde manier als je zou hebben gedaan met een schakelaar. het verschil is dat als je een van de typen niet afhandelt, je een compileerfout krijgt. dit is veel beter houdbaar. daarnaast kun je van bezoeker wisselen om ander gedrag te krijgen
toegevoegd de auteur jojo, de bron
Hoe werkt een bezoeker wanneer er methoden aanwezig zijn in bepaalde afgeleide klassen en niet in andere. voorbeeld: GetIP is alleen DNSRData_A en GetAlias ​​is alleen in DNSRData_CNAME en beide zijn afgeleid van de basis DNSRData
toegevoegd de auteur creatiwit, de bron
@jojo Ik ben nog steeds niet duidelijk over hoe dit zal werken. Stel dat ik het pakket wil analyseren en ik zoek naar het CNAME-type, ik roep GetInfoVisitor, maar hoe krijg ik toegang tot de velden voor CNAME. Moet ik een bezoekersklasse maken voor elke opera die ik wil uitvoeren?
toegevoegd de auteur creatiwit, de bron
@jojo dat werkt, bedankt
toegevoegd de auteur creatiwit, de bron

Allereerst zou u misschien dynamic_cast willen overwegen in plaats van de c-stijl cast die u in het voorbeeld gebruikte. Hiermee kunt u controleren of de cast is gelukt, wat mogelijk ernstige fouten kan voorkomen.

Ten tweede denk ik dat er meer context nodig kan zijn om uw vraag te beantwoorden. Bijna altijd kan een goed gebruik van een ontwerppatroon u toestaan ​​om deze situatie te vermijden. Gezien de beschikbare informatie, zou ik echter willen voorstellen om zelfs een abstracte virtuele functie te creëren, genaamd opereren in de bovenliggende klasse, die vervolgens kan worden overschreven om de speciale logica van interesse te implementeren. Dit zou je in staat stellen om het probleem te overwegen wanneer iemand de basisklasse opheft, zodat de code beter onderhouden kan worden en tijd zou besparen omdat je het opzoeken van het type zou vermijden.

0
toegevoegd
ik gebruik wel dynamic_cast, dat was een ruw voorbeeld dat ik heb geschetst. Ik heb 10 miljoen records ontleed in 5 minuten en na verloop van tijd vertraagt ​​dynamic_cast de prestaties. Ik voeg wat meer context toe aan de vraag om uit te leggen wat het probleem is.
toegevoegd de auteur creatiwit, de bron