Dovrei provare a separare lo stato dall'implementazione?

5

In questo momento sto lavorando con un codice che combina stato e operazioni.

Sembra qualcosa del genere (nota: in realtà non si occupa di Cars / Trucks, sto astragendo la logica del business qui, e mi scuso in anticipo che l'analogia non ha assolutamente senso)

TruckImpl {
    // Fields/Properties
    int Mileage
    bool MissionAccomplished
    // ... and many, many more

    // Constructor
    TruckImpl TruckImpl(IRentalAgreement agreement)

    // methods
    void Drive() {
        // Sets a whole bunch of fields/properties which are subsequently used to make decisions in Park()
        GetCarFromDealer()
        Haul()
    }
    void GetCarFromDealer()
    void Haul()

    // Return to rental company if done, otherwise park at home
    void Park()
}


CarImpl {
    // Fields
    int Mileage
    bool MissionAccomplished

    // Constructor
    CarImpl CarImpl(IRentalAgreement agreement)

    // methods
    // Sets a whole bunch of fields/properties which are subsequently used to make decisions in Park()
    void Drive() {
        // Sets a whole bunch of fields/properties which are subsequently used to make decisions in Park()
        GetCarFromDealer()
        Race()
    }
    void GetCarFromDealer()
    void Race()

    // Return to rental company if done, otherwise park at home    
    void Park()
}

È usato in un codice che assomiglia al seguente pseudocodice:

RentalVehicleHandler {
    IVehicleImplFactory factory;

    Handle(IRentalAgreement agreement) {
        impl = factory.Create(agreement)

        impl.Drive()
        impl.Park()
    }
}

Main() {
    while(true) {
    var agreements = GetAgreements(); 

    foreach(var agreement in agreements) {
        handler.Handle(agreement)
    }

}

Nel codice sopra riportato, riceviamo batch di "Affitti in affitto", ma continuiamo a elaborarli singolarmente. Ora, voglio gestire Auto e Camion leggermente separatamente. Per i camion, guiderò ancora () poi parcheggia () singolarmente. Per le automobili, voglio GetCarFromDealer () individualmente, ma poi Race () l'intero lotto di macchine in una volta prima di parcheggiarle. Potrei voler fare qualcosa di simile per Trucks in futuro, ma forse no.

Ho pensato che sarebbe stato utile separare il mio stato dalla mia implementazione come di seguito: (per iniziare, non sono state ancora aggiunte informazioni relative al batch):

// One per rental agreement
RentalVehicle {
    #Properties
    RentalAgreement agreement
    int Mileage
    bool MissionAccomplished
    //(...all of the TruckImpl propertiies?)
}

// Can handle any number of RentalAgreements, maybe multiple
CarDriver {

    RentalVehicle Drive(RentalVehicle vehicle)
    RentalVehicle Park(RentalVehicle vehicle)
}

TruckDriver {
    RentalVehicle Drive(RentalVehicle vehicle)
    RentalVehicle Park(RentalVehicle vehicle)
}

È una cosa utile / ragionevole da fare? O è solo un mucchio di lavoro extra che non mi procurerà alcun vantaggio? Penso che mi aiuterà sia con il problema a portata di mano che mi aiuterà con ulteriori refactoring, ma ho anche provato un paio di altre cose che sono dei vicoli ciechi e non voglio passare un po 'di tempo a refactoring di questo ragione.

    
posta sapphiremirage 05.06.2015 - 07:49
fonte

1 risposta

1

Questo è perfettamente ragionevole. In realtà, quel cambiamento è un esempio da manuale del paradigma della programmazione funzionale, che sta diventando gradualmente più popolare in questi giorni.

Le idee chiave della programmazione funzionale qui rilevanti sono che dovresti cercare di avere principalmente pure funzioni senza stato o effetti collaterali e soprattutto variabili immutabili che vengono definite una volta e mai cambiare. Quando cerchi questo, è tipico che finisca per scrivere un codice come State finalState = changeStuff(initialState) , che è sostanzialmente quello che hai nel tuo ultimo frammento. Se lo fai, vorrei andare fino in fondo e rendere RentalVehicle un tipo immutabile (che penso in C # è fatto rendendo le sue proprietà readonly ).

Il vantaggio di cose immutabili è semplicemente che sono molto più facili da ragionare rispetto a cose mutevoli con effetti collaterali. Se una funzione non ha effetti collaterali e il suo valore di ritorno dipende esclusivamente da quali argomenti lo passi, allora non ti sorprenderà molto spesso. Lo svantaggio principale (se ignoriamo le preoccupazioni relative alle prestazioni) è che il mondo reale è statico, e lo sono anche molte delle cose che il nostro codice deve fare. Essere funzionanti al 100% per tutte le logiche di noleggio, di trasporto e di regata non è probabilmente una buona idea se non si ha abbastanza esperienza di programmazione funzionale per trovare un tale codice intuitivo (o si sta lavorando in un linguaggio funzionale che ha strumenti come le monadi per renderlo Più facile). Tuttavia, suddividere il codice in parti "pure" e "impure" è spesso una buona idea, non importa su cosa stai lavorando, poiché limita i luoghi in cui devi preoccuparti di manipolazioni dello stato mutabili soggette a errori (anche in questo caso, assumendo qualsiasi potenziale perdita di prestazioni è un non-problema).

Non posso dirti se quella parte particolare della base di codice potrebbe effettivamente beneficiare di una maggiore purezza, ma non è certamente una cosa irragionevole da provare.

    
risposta data 05.06.2015 - 20:42
fonte

Leggi altre domande sui tag