Accoppiamento della logica aziendale con le definizioni di classe

2

Forse è il mio background di scripting, ma la mia prospettiva è che lo scopo di un oggetto dovrebbe essere quello di contenere i dati. Le classi sono necessarie in quanto forniscono modelli e metodi standard per classificare e costruire oggetti con particolari attributi di dati. Il protocollo standard nella progettazione orientata agli oggetti sembra includere la logica di business nelle definizioni di classe.

In un sistema che dipende dallo stato, comprendo la motivazione alla base dei metodi di associazione con il solo scopo di alterare o recuperare lo stato di un oggetto alla definizione della classe. Quindi escludiamo semplici getter, setter e costruttori dall'ambito della conversazione.

I problemi, come li vedo, iniziano quando processi sono associati a un oggetto.

Considera un semplice metodo "string.equals ()" che accetta una stringa come parametro e restituisce un valore booleano che indica se le stringhe sono equivalenti. Penso che questo semplice metodo possa causare enormi problemi.

Diciamo che ho una classe LocationString ereditata da String, dove equals () è sovrascritto come segue:

Boolean equals(LocationString otherLocation) {
    return extractCountry().equals(otherLocation.extractCountry());
}

String extractCountry(){
    return this.getCountry();
}

Ora, supponiamo di avere un migliaio di oggetti così diversi con diverse definizioni di equals (), alcuni dei quali sono gli stessi, e alcuni dei quali sono molto diversi. All'improvviso, la definizione di uguaglianza cambia per alcune delle mie classi. Diventa estremamente doloroso capire che le definizioni uguali devono essere cambiate. Diventa doloroso capire se altri processi interni che hanno chiamato il metodo degli uguali devono essere cambiati. Alla fine, man mano che il sistema si espande, più ore vengono sprecate cercando nel codice piuttosto che analizzarlo.

In quanto tale, per me ha molto più senso definire una regola generica uguale che modifica il suo comportamento in modo polimorfico a seconda degli input che riceve. Considera la seguente definizione:

public static Boolean equals(Object a, Object b) {
    List locationClasses = Arrays.asList("LocationString")
    if (locationClasses.contains(a.getClass().getName()) && locationClasses.contains(b.getClass().getName()){
        return equals(a.extractCountry(), b.extractCountry());
    } 
    else {
        return StringUtils.equals(a,b);
    }
}

Se tutti i processi fossero definiti esternamente, un cambiamento di processo non sarebbe tanto difficile da correggere e, a condizione che queste regole più generiche potessero essere archiviate e classificate logicamente, sarebbero facili da trovare e sarebbe necessario cambiare solo una volta modifiche della logica di business.

Questo approccio sembra relativamente semplice e diretto. Perché allora, è stato generalmente respinto a favore del tipico accoppiamento orientato agli oggetti dei dati con i metodi?

    
posta Master_Yoda 09.07.2014 - 00:04
fonte

5 risposte

2

In primo luogo, ciò che descrivi sembra piuttosto un approccio funzionale alle cose. Separare i dati e le funzioni che operano su quei dati è modus operandi di tutto il linguaggio funzionale. Prova ad ispirare Haskell.

La seconda cosa che vedo è che sembra che manchi il concetto di astrazione. Se hai identificato più classi, che hanno comportamenti identici o simili, dovrebbe essere la tua priorità principale rifattorizzare questo comportamento in una forma riutilizzabile in ereditarietà o composizione. Questa è in realtà la parte più difficile del software e del design OO.

E perché la separazione dei dati e il comportamento sono disapprovati nella progettazione OO? Innanzitutto, incapsulamento. Assicurandosi che ci sia un ambito limitato, che può accedere e modificare uno stato, semplifica il ragionamento sul codice, perché non devi preoccuparti di cambiare le variabili a tua insaputa. Avendo le funzioni esterne accedere a questi dati, significa che l'incapsulamento è rotto. In secondo luogo ci sono metodi virtuali. I metodi virtuali sono modi per ottenere un legame tardivo nelle lingue OO. Non puoi avere metodi virtuali se non sono parti della classe.

E l'ultima cosa, ogni volta che vedo qualcuno che parla di OO design, non posso aiutarmi e collegarmi a questo excersise .

E alcune parole finali: solo perché un approccio potrebbe sembrare semplice e diretto su un esempio semplice e diretto non significa che funzionerà su un esempio complesso ed enorme. Prova ad applicare il tuo approccio al progetto che ha migliaia di classi con milioni di funzioni e una logica aziendale estremamente complessa. Presto ti renderai conto che è tutt'altro che semplice.

    
risposta data 09.07.2014 - 08:39
fonte
1

Ci sono due punti da dire su questo.

  1. In alcuni casi pensare ai processi come appartenenti ad oggetti rende la comprensione del sistema davvero più semplice. Gli oggetti diventano responsabili dei propri processi.
  2. Purtroppo la maggior parte dei linguaggi di programmazione orientati agli oggetti non supporta i multi-metodi, cioè l'invio è dinamico solo per un parametro (questo).

Ma, ovviamente, un approccio orientato agli oggetti non è sempre adatto a tutti i tipi di sistemi. Personalmente, penso, per lo più paga i sistemi transazionali con regole aziendali molto complicate. La logica di business può quindi essere distribuita su più classi e, assumendo che il sistema sia ben progettato, anche se non si capisce tutto il sistema in una volta, è ancora possibile mantenerlo lavorando con un numero limitato di classi (ciascuna incapsulata comportamento specifico) alla volta.

    
risposta data 09.07.2014 - 18:54
fonte
0

Per me, ogni oggetto dato ha una sorta di profilo comportamentale che è il dominio e / o l'ambito delle varie funzioni a cui può partecipare. L'ampiezza e il carattere di questo profilo comportamentale varieranno ampiamente a seconda dell'oggetto. Il design orientato agli oggetti ha certamente qualche relazione con la scienza cognitiva, poiché riguarda il modo in cui il programmatore modella lo spazio del problema dato. Potresti considerare questo profilo comportamentale come una collezione di affordances: link , ma su un dominio più astratto piuttosto che concreto.

Con quella nozione a bordo, direi che la decisione di accoppiare dati e comportamenti dipende da una valutazione del dominio delle potenzialità economiche per l'oggetto. A volte le proprietà di un dato oggetto sono così azioni che implicano su un dominio comparativamente limitato che ha senso accoppiare strettamente i dati e le funzionalità. In altri casi, i dati sono più astratti e l'ambito delle possibili funzioni è ampio.

In termini pragmatici, preferisco decisamente tenere separati dati e comportamenti se possibile. Sono parziale nell'usare semplici classi di soli dati insieme a un uso salutare di interfacce fluenti generiche, e in particolare funzioni di ordine superiore / costrutti monadici. Questa è la mia risposta breve ..

    
risposta data 09.07.2014 - 05:48
fonte
0

Capisco che il caso dei pari è solo un esempio e che il tuo approccio riguarda qualsiasi logica oltre ai semplici getter e setter.

Vedo alcuni problemi / limitazioni gravi con ciò che proponi:

  • Il metodo "generale uguale" deve risiedere in una classe di utilità.

  • Quella classe dovrebbe essere cambiata ogni volta che una classe viene aggiunta al sistema (se avrai mai bisogno di confrontarli)

  • Molte API ti richiedono di sovrascrivere gli uguali affinché le raccolte generiche funzionino con le tue classi in sort operazioni. In nessun modo le API guarderanno il metodo equals della classe di utilità per confrontare gli oggetti.

  • Non diversamente dall'esempio di API che ho fornito sopra, le architetture di plugin, che consentono l'escalabilità e l'estensibilità, non esisterebbero come lo conosciamo. Spiegazione: il tuo approccio è stato utilizzato nello sviluppo di, diciamo, Firefox, quindi un plug-in funzionerebbe solo se la sua esistenza fosse nota agli sviluppatori di Firefox e avessero aggiunto il caso corrispondente. Poiché questo approccio non è stato utilizzato, un plugin può funzionare anche quando lo sviluppatore di Firefox non sapeva nulla dell'esistenza di un tale plugin. Anche i plug-in sviluppati dopo una data versione di Firefox è stata rilasciata, avrebbero funzionato con esso.

  • Inoltre sembra implicare che ogni logica all'interno di un metodo sia "business logic". Questa non è la canonica definizione di business logic .

risposta data 09.07.2014 - 18:23
fonte
0

my perspective is that the purpose of an object should be to contain data.

Sì, ma non è tutto.

Standard protocol in object oriented design seems to be to include business logic in class definitions.

Non protocollo. È fondamentale per l'Orientamento agli oggetti. Dati + Operazioni.

The problems, as I see them, start when processes are bound to an object.

Stai toccando un problema centrale: quali dati e quali operazioni appartengono a una determinata classe? Come linea guida generale voglio operazioni "atomiche" - non riesco a pensare a una parola migliore - a quella classe.

Consider a simple String.Equals()

Non stai pensando orientato agli oggetti!

// this is C#
public class Location {
    public string Country { get; set; }

    public override bool Equals(object other) {
        if(other == null)
            return false;
        if(! other is Location)
            return false;

        return this.Country == other.Country;  // we're taking advantage of String.Equals(). cool.
    }
}

// sample use
// Given: Odessa, OdessaUSA, OdessaRussia are Location objects.

if(Odessa.Equals(OdessaUSA))
    Console.WriteLine ("Hot damn! I'm in Texas");

if(Odessa.Equals(OdessaRussia))
    Console.WriteLine ("Hot damn! Cheap Vodka");

... And eventually as the system expands, more hours are wasted searching through the code than analyzing it.

Il modo in cui lo stai presentando, sono d'accordo. Questo è un buon esempio del perché vorresti scrivere Object Oriented.

it makes far more sense that a generic equals rule is defined that modifies its behavior polymorphically depending on the inputs it receives.

No, questo è un anatema per l'Orientamento agli oggetti. Ogni classe ha la propria definizione "uguale". Qualsiasi modifica interessa solo gli oggetti di quella classe. Non ha effetto su ogni proprietà stringa, o metodo uguale, di ogni classe. Una caratteristica essenziale di una classe è che incapsula i propri dati e le proprie operazioni. Le modifiche a una classe non ne influenzano un'altra.

Now, assume I have a thousand such different objects with different equals() definitions, some of which are the same, and some of which are vastly different.

Tutti gli oggetti della stessa classe hanno la stessa definizione di equals (). In OO non è possibile confrontare oggetti di classi diverse, ad esempio tipi. Non ha senso più che cercare di eseguire equals su una stringa su un valore numerico.

    
risposta data 10.07.2014 - 03:13
fonte