Dividi la classe in due o parti come una sola

1

Ho la seguente classe:

internal class LeaveRequest : ServiceBase
{

    private const string InvalidRequestMessage = "Specified Request does not exist";
    private const string InvalidApproverMessage = "You are not an approver for the specified user";

    private const string RequestWrongStatusMessage = "The request status cannot be changed due to the current status";

    private const string CancelRequestIncludes = "LeaveRequestStatuses";

    internal LeaveRequest(DataAccess.BookItContext context) : base(context) { }

    private static IQueryable<Model.LeaveRequest> ProcessIncludeProperties(string includeProperties,
                                                                           IQueryable<Model.LeaveRequest> query)
    {
        foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }
        return query;
    }

    private static IQueryable<Model.LeaveRequest> ProcessFilter(Expression<Func<Model.LeaveRequest, bool>> filter,
                                                                IQueryable<Model.LeaveRequest> query)
    {
        if (filter != null)
        {
            query = query.Where(filter);
        }
        return query;
    }

    private ICollection<Model.LeaveRequest> Find(Expression<Func<Model.LeaveRequest, bool>> filter = null,
                                                 string includeProperties = "")
    {
        var requestQuery = Context.LeaveRequests.AsQueryable();
        requestQuery = ProcessFilter(filter, requestQuery);
        requestQuery = ProcessIncludeProperties(includeProperties, requestQuery);

        return requestQuery.ToList();
    }

    private Model.LeaveRequest FindByID(int id,
                                        string includeProperties = "")
    {
        var requestQuery = Context.LeaveRequests.AsQueryable();
        requestQuery = ProcessFilter(r => r.ID == id, requestQuery);
        requestQuery = ProcessIncludeProperties(includeProperties, requestQuery);

        return requestQuery.FirstOrDefault();
    }

    private Model.LeaveRequestStatus CreateStatus(RequestStatuses status,
                                                  int approverID,
                                                  string notes,
                                                  DateTime editDate)
    {
        return new Model.LeaveRequestStatus()
        {
            IsCurrent = true,
            Notes = notes,
            StatusID = (int)status,
            UserID = approverID,
            StatusDate = editDate
        };
    }

    private void SetNewRequestStatus(Model.LeaveRequest request,
                                     Model.LeaveRequestStatus currentStatus,
                                     RequestStatuses status,
                                     int approverID,
                                     string notes,
                                     DateTime editDate)
    {
        currentStatus.IsCurrent = false;
        Context.Entry(currentStatus).State = EntityState.Modified;

        var newStatus = CreateStatus(status, approverID, notes, editDate);
        request.LeaveRequestStatuses.Add(currentStatus);
        Context.Entry(currentStatus).State = EntityState.Added;
    }

    internal OperationResult CancelRequest(int requestID, int userID, string notes)
    {
        var result = new OperationResult() { Success = true };

        DateTime editDate = DateTime.UtcNow;

        var request = this.FindByID(requestID, CancelRequestIncludes);

        if (request == null)
        {
            throw new ArgumentException(InvalidRequestMessage);
        }

        var currentStatus = request.LeaveRequestStatuses.Where(s => s.IsCurrent).FirstOrDefault();

        if (currentStatus.StatusID == (int)RequestStatuses.RequestPending || currentStatus.StatusID == (int)RequestStatuses.RequestApproved)
        {
            if (currentStatus.StatusID == (int)RequestStatuses.RequestPending)
            {
                SetNewRequestStatus(request, currentStatus, RequestStatuses.CancellationApproved, userID, notes, editDate);
            }
            else if (currentStatus.StatusID == (int)RequestStatuses.RequestApproved)
            {
                var approver = new Approver(Context);

                if (approver.Validate(request.UserID, userID))
                {
                    SetNewRequestStatus(request, currentStatus, RequestStatuses.CancellationPending, userID, notes, editDate);
                }
                else
                {
                    throw new InvalidApproverException(InvalidApproverMessage);
                }

                Context.SaveChanges();
            }
        }
        else
        {
            result.Success = false;
            result.Message = RequestWrongStatusMessage;
        }

        return result;
    }
}

Usa EF per accedere ai dati. La mia domanda dovrebbe essere divisa in due classi?

Sto iniziando a pensare che forse dovrebbe essere diviso in una classe per gestire le varie convalide (regole di business) e un'altra per fare effettivamente la creazione del grafico dell'oggetto corretto e impostare lo stato dell'entità corretta e fare le modifiche di salvataggio. La mia unica preoccupazione per la suddivisione in due classi, potrei finire con vari metodi "pass-through".

Inoltre, se divido in due classi, non sono sicuro se dovrei utilizzare due spazi dei nomi, ad es. Business.LeaveRequest e Service.LeaveRequest o se dovrei nominare le classi LeaveRequest e LeaveRequestDB.

    
posta James Culshaw 26.03.2016 - 20:22
fonte

1 risposta

2

Considerando il principio di responsabilità singola , varrebbe la pena di esaminare ciascuna delle diverse preoccupazioni che la tua classe sta trattando .

(Vale anche la pena leggere: Segregazione di responsabilità della query di comando ).

esaminando il tuo codice, posso visualizzare 4 potenziali responsabilità nella tua classe LeaveRequest :

  • Creazione di query DB in base all'elenco delle proprietà.
  • Operazioni di CRUD di base in / out del DB, inclusa l'esecuzione della query.
  • Convalida richiesta
  • Gestione delle richieste di servizio in entrata.

Ognuno di questi sembra logicamente separato da me, quindi potresti prendere in considerazione l'idea di abbatterlo in tante classi.

Non è una brutta cosa per la classe che gestisce le richieste in entrata per essere molto sottile / leggera. Se la classe di servizio è ridotta a essere solo "colla", dovresti essere in grado di separare tutti gli altri l'uno dall'altro.

Se la tua classe di servizio è responsabile solo per "essere il servizio" e si comporta come una colla, allora dovrebbe essere più facile arrivare a un design liberamente accoppiato, con piccoli oggetti autosufficienti che richiedono poca o nessuna interazione tra loro .

Spesso è più facile vedere come le classi dovrebbero essere divise osservandole dall'esterno; cioè Inputs-vs-Outputs e cercando di minimizzare ogni effetto collaterale all'interno di ogni classe (l'immutabilità è una buona cosa - cioè se si passa un argomento ad una classe / metodo, provare ad evitare la classe / metodo che modifica quell'argomento).

Ad esempio; senza suggerire se questo potrebbe essere un progetto corretto, più per illustrare come potrebbe apparire la classe di servizio quando tutte le altre aree sono disaccoppiate l'una dall'altra:

internal class LeaveRequestService : ServiceBase
{
    private QueryBuilder _queryBuilder;
    private DataLayer _dataLayer;
    private StatusUpdater _statusUpdater;

    internal OperationResult CancelRequest(int requestID, int userID, string notes)
    {
        var query = _queryBuilder.CreateQuery(requestID, userID, notes);
        var queryResult = _dataLayer.RunQuery(query);
        var validator = new ValidatingStatusAnalyser(queryResult);
        var operationResult = _statusUpdater.Update(_dataLayer, validator);
        return operationResult;
    }
}

Potresti finire con diverse classi che hanno solo 1 o 2 metodi - di solito è un segno che il tuo design è allineato con SRP, anche se devi fare una sentenza per decidere se vuoi quel livello di disaccoppiamento. per esempio. Se stai mirando alla copertura completa del test unitario, mantenere oggetti molto piccoli come questo ha molti vantaggi.

    
risposta data 26.03.2016 - 23:56
fonte

Leggi altre domande sui tag