Composition vs. Ereditarietà [duplicato]

4

Ecco cosa viene fornito:

public interface Request {}

// there are 20 subclasses of Request
public class CreateUserRequest implements Request {
  @NotEmpty
  public String userName;
}

// request processor is a thing that aimed to process requests
public interface RequestProcessor<TRequest extends Request> {
  boolean processRequest(TRequest request);
}

public class ServiceFacade {
  // 20 processors like this one
  private final RequestProcessor<CreateUserRequest> createUserRequestProcessor;

  public ServiceFacade(
    RequestProcessor<CreateUserRequest> createUserRequestProcessor) {
    this.createUserRequestProcessor = createUserRequestProcessor;
  }

  // 20 methods like this one
  public boolean createUser(CreateUserRequest request) {
    createUserRequestProcessor.processRequest(request);
  }
}

Un oggetto che implementa RequestProcessor<T> ha lo scopo di elaborare richieste di tipo T . Inoltre, è responsabile della gestione di tutti gli errori correlati (come richiesta non valida, errore di accesso al database e così via). Pertanto, il flusso di lavoro per CreateUserRequest dovrebbe essere simile a questo:

request
validate/throw
make sure user doesn't exist yet/throw
create user

Come al solito, altri tipi di richieste potrebbero richiedere ulteriori passaggi da eseguire.

Come illustrazione : in caso di AddUserAsFriendRequest dovremo verificare che entrambi gli utenti esistano e che il candidato amico abbia permesso ad altri di aggiungerlo come amico. Oh, e anche, se un utente aggiunge più di 10 amici in un giorno, è limitato a 1 amico per 2 ore per il resto della giornata.

La domanda è , quale gerarchia / struttura / approccio dovrei usare per poter estendere questa funzionalità sia in "numero di tipi di richieste" che in "flusso di lavoro del processo di richiesta".

Ci sono solo 2 approcci che vedo.

Inheritance

public abstract class ValidatingRequestProcessor<TRequest extends Request>
implements RequestProcessor<TRequest> {
  protected final Validator validator;

  public ValidatingRequestProcessor(Validator validator) {
    this.validator = validator;
  }

  public boolean processRequest(TRequest request) {
    Set<ConstraintViolation<TRequest>> violations = 
      validator.validate(request);
    if(!violations.isEmpty()) {
      return false;
    }

    return processValidatedRequest(request);        
  }

  protected abstract boolean processValidatedRequest(TRequest request);      
}

public class CreateUserValidatingRequestProcessor
extends ValidatingRequestProcessor<CreateUserRequest> {
  public CreateUserValidatingRequestProcessor(Validator validator) {
    super(validator);
  }

  protected boolean processValidatedRequest(TRequest request) {
    // request is valid, so I can try to create user here
  }
}

e così via qui. In caso di N "steps logici", avrò (N-1) nidificato extends e N classi:

class A implements RequestProcessor {}
class B extends A {}
class C extends B {}
class D extends C {}
...
class Z extends Y {}

RequestProcessor requestProcessor = new Z(); // TADA!

Composizione

public class ChainingRequestProcessor<TRequest extends Request>
implements RequestProcessor<TRequest> {
  private final RequestProcessor<TRequest> processorA;
  private final RequestProcessor<TRequest> processorB;

  public ChainingRequestProcessor(
    RequestProcessor<TRequest> processorA,
    RequestProcessor<TRequest> processorB) {      
    this.processorA = processorA;
    this.processorB = processorB;
  }

  public boolean processRequest(TRequest request) { 
    return processorA.processRequest(request) &&
      processorB.processRequest(request);
  }
}

public RequestValidatorProcessor<TRequest extends Request>
implements RequestProcessor<TRequest> {
  private final Validator validator;

  public RequestValidatorProcessor(Validator validator) {
    this.validator = validator;
  }

  public boolean processRequest(TRequest request) { 
    return !validator.validate(request).isEmpty();
  }
}

public CreateUserProcessor
implements RequestProcessor<CreateUserRequest> {
  public boolean processRequest(CreateUserRequest request) { 
    // not sure whether request is valid or not
    // can try to create user here
  }
}

e così via. In caso di N "steps logici" avrò solo N classes. Quindi:

class A implements RequestProcessor {}
class B implements RequestProcessor {}
class C implements RequestProcessor {}
class D implements RequestProcessor {}
...
class Z implements RequestProcessor {}

// just guess
RequestProcessor requestProcessor = new A(new B(), new C(new D())); // TADA!

Quale approccio è migliore per questo compito? Meglio significa:

  • Più facile modificare la logica
  • Più facile aggiungere logica
  • Più facile da testare
posta Andrey Agibalov 12.07.2012 - 21:20
fonte

1 risposta

7

Sulla base del tuo esempio, sceglierei sicuramente la composizione per l'ereditarietà. Sarebbe molto più facile riorganizzare l'ordine in cui i processori sono eseguiti usando la composizione, dando così la modificabilità logica. Per quanto riguarda l'aggiunta di logica, è possibile implementare facilmente un nuovo RequestProcessor e iniettarlo in qualsiasi punto della catena del processore che si desidera senza dover districare un albero di ereditarietà profonda. Per il test, sarà molto più semplice isolare un singolo processore per test quando non estenderà un mucchio di altri processori.

Se si intende utilizzare la composizione, non è consigliabile che i processori utilizzino altri processori nei loro costruttori. Questo livello di accoppiamento può probabilmente essere evitato. Al contrario, disporre di una raccolta di processori e scorrere semplicemente i processori per elaborare la richiesta. Se un processore ha bisogno di dati da un altro processore per fare il suo lavoro, lo rappresenta con un dizionario dei sacchetti di stato o un oggetto di contesto. Evitare di unire insieme processori diversi migliorerà anche la testabilità.

    
risposta data 12.07.2012 - 21:38
fonte

Leggi altre domande sui tag